You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A REST read that projects a file field without id answers {id, metadataRefused: true} for a caller that may read the file #26672
What happens.GET /api/v1/data/clm_contract_type?select=template_file, as an app admin (hotclm clm.admin, not the platform admin), answers template_file: {id, metadataRefused: true}. The same request with select=id,template_file answers the full {name, size, mimeType, url}. The platform admin gets the full value either way.
Reach. REST and API callers that project narrowly. The Console always selects id, so no screen shows this. The verdict looks like it is derived from the parent record, and without id in the projection the hydration seems unable to find the parent, so a permitted reader is told the metadata is refused.
Seam. The runtime's file-field read expansion (the parent-derived verdict of #22637) → a consumer projecting without id.
Expected. The verdict does not depend on whether the caller listed id: either the hydration fetches the key it needs, or the projection adds it internally and trims it back.
Dedupe: searched metadataRefused / file field / select projection. The nearest are #22637 and #22593 (closed, the rules this follows).
Triage: audit line · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-27T13:01Z
⛔ Not a claim, ⛔ not a dispatch.
Triage: lands in the file-field projection on the REST read; rationale: p3 · domain:server (lane merge: domain:engine → domain:server) · area:files. A caller who may read the file gets the same value with or without id in select. The refusal applies only where the read rule refuses. ⛔ Do not widen what an unauthorised caller sees.
Claim: PM loop round 9 · card thread · a file-field read not depending on whether the projection lists id
Session: session_01R1q2xdtYgU6FKrZ7LgjPCC (the seat); the card thread is session_01FEkQ5nso8iiU7cfQSEAkX5
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-dywp1n (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#1
Ruling-ref: triage 6442544054 (p3, under Leehom's 「p1 p2 处理完了,就可以处理 p3」 2026-10-26T10:29Z): a caller who may read the file gets the same value with or without id in select; a refused caller stays refused. Intake split with domain:server#2 for the 18.2.0 wave-2 cards (#25807).
File surface: the runtime file-field read hydration (packages/runtime/**, packages/rest/** or packages/objectql/**, wherever it lives), its tests and a patch changeset. ⛔ No packages/spec/**, ⛔ no ui/**. ⛔ No packages/spec/** (lane red line).
Container & model: S, mode:thread, default model and effort (set at thread open)
Clause-②: no — the dev measures it on the diff
Metadata-protocol: none
Responsibility: an API caller projecting narrowly is told file metadata is refused when it may read it (hotclm#19)
Thread-read: 6442544054
Premise: holds (read 2026-10-27T20:54Z at origin/main1708115e2f: card open, pm:queue, no live claim)
Serial constraints cleared: none known on these paths; the dev re-measures open PRs on the files it touches before pr_create.
os-dev-report
{
"issue": 26672,
"status": "done",
"branch": "claude/project-thread-dywp1n",
"pr": "#26938",
"session": "session_01FEkQ5nso8iiU7cfQSEAkX5",
"premise_still_valid": true,
"summary": "Measured on main 1708115: a reader refused sys_file read whose field-owned verdict allows the file got {id, metadataRefused: true} for select=FILE_FIELD and the full value for select=id,FILE_FIELD, on REST list, query and by-id. Fix in packages/objectql/src/engine.ts: withFileOwnerKey runs after planFormulaProjection in find and findOne; an explicit projection naming a file-reference field and not id reads id too, and the existing widening cut (withoutFormulaWidening, after the middleware chain) trims it, so the answer keeps exactly the named columns. The verdict, the refused marker and the caller own sys_file read are unchanged; a refused reader stays refused with or without id. Every door shares it because all read through engine.find/findOne: REST GET /data/:object, POST /data/:object/query, GET /data/:object/:id, the dispatcher data domain (callData then protocol.findData/getData), and expand sub-reads (which already added the join key id). The objectql case that pinned the old answer (a read without id keeps the marker) was rewritten to pin the new one.",
"tests": "Red first: rest data-file-field-projection-without-id.test.ts on main (objectql dist built from main) 3 failed / 3 passed, the 3 reds the id-less doors (list, query, by-id); after fix 6/6. Ablation: engine.ts swapped to its main blob under a restoring trap, objectql engine-file-hydrate-parent-derived.test.ts 1 failed / 16 passed (red = id-less case, refused control green), restore proven by HEAD blob hash + empty git diff HEAD. Package suites on 845325d: objectql 475/475 files, 8850/8850 tests; rest 329/329 files, 6519 passed 326 skipped; service-storage 70/70 files, 1258/1258 tests; typecheck objectql+rest+service-storage exit 0; eslint --no-inline-config on touched dirs exit 0. Final head 6ea02bc only removes one as-any from the new rest test: rest typecheck exit 0 and the file 6/6 re-run there.",
"gates": "dispatch-gates --commands on the diff: 67 families, all run on final head 6ea02bc, 67/67 exit 0 (codes captured before any pipe); --ran reconcile: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN. First run on 845325d: check:query-options-erasure exit 1 (new test as-any, test surface 236 to 237), fixed; check-plugin-teardown-shape --self-test exit 3 (shallow clone), cleared by fetching 621a487, exit 0 on rerun. test:repo not run locally (no repo-tests file touched, not derived): CI Test Core. PR check-runs: in_progress at report time.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "4 — mcp__github__issue_read get x1, issue_read get_comments x2, pull_request_read get x1; all reads, no write tool",
"api_writes": "2 relay writes as objectstack-fleet[bot] — pr_create (#26938, via fleet-write dispatch run 40593501734) and this os-dev-report issue comment (post-stamped); plus 2 git pushes (not REST)",
"open_questions": [],
"out_of_scope_findings": []
}
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: audit line · triage seat ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T13:01Z⛔ Not a claim, ⛔ not a dispatch.
domain:server(lane merge:domain:engine→domain:server) ·area:files. A caller who may read the file gets the same value with or withoutidinselect. The refusal applies only where the read rule refuses. ⛔ Do not widen what an unauthorised caller sees.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 9 · card thread · a file-field read not depending on whether the projection lists
idSession:
session_01R1q2xdtYgU6FKrZ7LgjPCC(the seat); the card thread issession_01FEkQ5nso8iiU7cfQSEAkX5Account:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-dywp1n(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#1Ruling-ref: triage
6442544054(p3, under Leehom's 「p1 p2 处理完了,就可以处理 p3」 2026-10-26T10:29Z): a caller who may read the file gets the same value with or withoutidinselect; a refused caller stays refused. Intake split withdomain:server#2for the 18.2.0 wave-2 cards (#25807).File surface: the runtime file-field read hydration (
packages/runtime/**,packages/rest/**orpackages/objectql/**, wherever it lives), its tests and apatchchangeset. ⛔ Nopackages/spec/**, ⛔ noui/**. ⛔ Nopackages/spec/**(lane red line).Container & model:
S,mode:thread, default model and effort (set at thread open)Clause-②: no — the dev measures it on the diff
Metadata-protocol: none
Responsibility: an API caller projecting narrowly is told file metadata is refused when it may read it (hotclm#19)
Thread-read: 6442544054
Premise: holds (read 2026-10-27T20:54Z at
origin/main1708115e2f: card open,pm:queue, no live claim)Serial constraints cleared: none known on these paths; the dev re-measures open PRs on the files it touches before
pr_create.objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{
"issue": 26672,
"status": "done",
"branch": "claude/project-thread-dywp1n",
"pr": "#26938",
"session": "session_01FEkQ5nso8iiU7cfQSEAkX5",
"premise_still_valid": true,
"summary": "Measured on main 1708115: a reader refused sys_file read whose field-owned verdict allows the file got {id, metadataRefused: true} for select=FILE_FIELD and the full value for select=id,FILE_FIELD, on REST list, query and by-id. Fix in packages/objectql/src/engine.ts: withFileOwnerKey runs after planFormulaProjection in find and findOne; an explicit projection naming a file-reference field and not id reads id too, and the existing widening cut (withoutFormulaWidening, after the middleware chain) trims it, so the answer keeps exactly the named columns. The verdict, the refused marker and the caller own sys_file read are unchanged; a refused reader stays refused with or without id. Every door shares it because all read through engine.find/findOne: REST GET /data/:object, POST /data/:object/query, GET /data/:object/:id, the dispatcher data domain (callData then protocol.findData/getData), and expand sub-reads (which already added the join key id). The objectql case that pinned the old answer (a read without id keeps the marker) was rewritten to pin the new one.",
"tests": "Red first: rest data-file-field-projection-without-id.test.ts on main (objectql dist built from main) 3 failed / 3 passed, the 3 reds the id-less doors (list, query, by-id); after fix 6/6. Ablation: engine.ts swapped to its main blob under a restoring trap, objectql engine-file-hydrate-parent-derived.test.ts 1 failed / 16 passed (red = id-less case, refused control green), restore proven by HEAD blob hash + empty git diff HEAD. Package suites on 845325d: objectql 475/475 files, 8850/8850 tests; rest 329/329 files, 6519 passed 326 skipped; service-storage 70/70 files, 1258/1258 tests; typecheck objectql+rest+service-storage exit 0; eslint --no-inline-config on touched dirs exit 0. Final head 6ea02bc only removes one as-any from the new rest test: rest typecheck exit 0 and the file 6/6 re-run there.",
"gates": "dispatch-gates --commands on the diff: 67 families, all run on final head 6ea02bc, 67/67 exit 0 (codes captured before any pipe); --ran reconcile: 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN. First run on 845325d: check:query-options-erasure exit 1 (new test as-any, test surface 236 to 237), fixed; check-plugin-teardown-shape --self-test exit 3 (shallow clone), cleared by fetching 621a487, exit 0 on rerun. test:repo not run locally (no repo-tests file touched, not derived): CI Test Core. PR check-runs: in_progress at report time.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "4 — mcp__github__issue_read get x1, issue_read get_comments x2, pull_request_read get x1; all reads, no write tool",
"api_writes": "2 relay writes as objectstack-fleet[bot] — pr_create (#26938, via fleet-write dispatch run 40593501734) and this os-dev-report issue comment (post-stamped); plus 2 git pushes (not REST)",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Landed
PR #26938 merged at 2026-10-27T23:08Z by the merge queue as
1f1eda4dafonmain(Fixes #26672; ACCEPT6455423361).git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26938-*'origin/mainby contentgit cat-file -e origin/main:packages/rest/src/data-file-field-projection-without-id.test.tsgit merge-base --is-ancestor 1f1eda4daf origin/mainState: this card closed by
Fixes(completed);pm:dispatchedremoved.Generated by Claude Code