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
public forms: an authorization-adjacent gap in how a submission's file field is handled (detail withheld pending maintainer) #25622
Filing gate ①, the "may leak data" exception (filing-gate: reach is measured first, not before filing). Found by the contract-tier review of PR #25619 (#25572 phase 1), from source reading on origin/maine649c2d91, with no reproduction. Filed by domain:server seat 2 (seat post #20966) · sunhaolin · session session_01JVTd3PprStGa26aez5omLB. ⛔ Not a claim. Domain, grade and route are triage's.
Disclosure: the mechanism and the path are withheld from every public surface under the services lane's security-family discipline. The seat holds them and has handed them to the maintainer in the seat's session. This card is the gap's owner record, so it is not lost.
What can be said: the gap sits on the existing anonymous public-form submit door (ADR-0138 D2 class 1) for a form that declares a file field. It exists on main today and does not depend on #25572's amendment. Whichever way the maintainer decides #25572 (guest upload door A/B, or no file fields on public forms), the fix has to close it, so its route follows that decision.
Reader who acts: the maintainer first, for the disclosure channel and the route; then the lane triage names.
⛔ Not a claim, ⛔ not a dispatch. ⛔ Nothing about the mechanism or the path goes on this card or any public surface (#9387).
p1, provisional: the door is the anonymous public-form submit door, it is on main today, and the card files it under "may leak data". This seat has not seen the detail, so the maintainer re-grades once they have it.
Maintainer-action: take the withheld detail from the filing session (session_01JVTd3PprStGa26aez5omLB), choose the private channel, and hand it to a domain:server dev session off GitHub — done when a claim comment on this card says the detail was received off GitHub
Claim: PM loop round 1 · fold of #25622 (chain head) and #25572 (server half)
Session: session_01JVTd3PprStGa26aez5omLB
Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-25622-guest-submit-file-refusal
Worktree: objectstack-issue-25622
Domain: domain:server
Seat: domain:server#2
File surface: the anonymous public-form submit door in packages/rest/src/form-routes.ts and the file-reference claim path in packages/objectql / packages/core the dev measures, their tests, one changeset. ⛔ No mechanism or path detail on any public surface (#9387) (stop on breach; explain in the report)
Container & model: M, security, mode:subagent (disclosure discipline: the detail stays in the seat's session), model: TIER_DEFAULT (dispatch-gates --tier: "no path-derived mandate … the tier stays the PM's per-card judgment call"; a CONTRACT_REVIEW_TIER review runs before landing), effort: xhigh
Clause-②: no
Metadata-protocol: none
Responsibility: the anonymous public-form submit path, detail withheld | ruling C on #25572 (an anonymous submission carries no file) is the route that closes it | any anonymous caller of a public form that declares a file field; reach measured by source reading, the dev measures it at the door
Thread-read: 6385106262
Serial constraints cleared: open PR #25641 (domain:spec's #25574, ADR-0138 D5) edits packages/rest/src/form-routes.ts (+51/-5). The dev reads its diff before choosing the edit site; if the regions meet, whichever PR lands second merges main. No other open PR touches the form doors or the file-reference claim files (open PRs read this fire).
Premise: the maintainer's action named on this card (Maintainer-action: in 6383334528) is done. The withheld detail was received off GitHub, in this seat's session, where the maintainer read it; the maintainer chose that channel and approved this folded dispatch in the same session, answering 「可以,合并派发(推荐)」. The route is ruling C on #25572 (「25619 C 25495 B」).
Fold: one branch and one PR, Fixes #25622 and Fixes #25572 on separate lines. Members are both graded, share one fix (the anonymous submit door refuses a file field's value) and are each checkable on their own. ⛔ Excluded: #25650 (the publish-time refusal, domain:spec), and any guest upload door (ruled out by C).
{
"issue": 25622,
"status": "done",
"branch": "claude/issue-25622-guest-submit-file-refusal",
"pr": "https://github.com/objectstack-ai/objectstack/pull/25682",
"session": "session_01JVTd3PprStGa26aez5omLB — a subagent run, so the parent's harness-stamped id; claims 6385258213 (#25622) and 6385262132 (#25572) name it and this branch",
"premise_still_valid": true,
"summary": "Folded dispatch: one draft PR, whose first two body lines carry the closing declarations for both cards. Ruling C's rule, an anonymous public-form submission carries no file: POST /forms/:slug/submit refuses a value in a field whose declared type is in FILE_REFERENCE_TYPES, naming the field, before anything is written. It answers the door's existing 400 VALIDATION_FAILED envelope with fields[] code invalid_value (ADR-0114), in one envelope with the form's required judgement; no error code is added. An empty or absent file field submits as before, and only declared fields are judged. The file-reference claim path in service-storage now looks a referenced file up within the writing organization, through the channel that already stamps a copy for it, so the driver's tenant scope for the posture in force decides. The measurement behind that change, and every further finding, went to the seat off GitHub under the security-family disclosure discipline. A1: PR #25641's hunks in form-routes.ts and this edit are disjoint; git merge-tree of this branch with its head 08b020c9d3 exits 0 with no conflicted path.",
"tests": "Head 80aab79ea2, each exit captured separately. pnpm --filter @objectstack/rest test: Test Files 321 passed (321), Tests 6417 passed | 326 skipped (6743); rest typecheck exit 0, and its test-layer program compiles both rest test files touched (--listFiles count 2). pnpm --filter @objectstack/service-storage test: Test Files 63 passed (63), Tests 1137 passed (1137); typecheck exit 0, with the new test in its test-layer program (--listFiles count 1). New suites: public-form-file-field.test.ts 11/11 and file-reference-organization-scope.test.ts 6/6. The refusal pins assert code VALIDATION_FAILED, status 400, and each fields[] entry's field, code and label; message wording is not pinned. eslint --no-inline-config with the root config over packages/rest/src and packages/services/service-storage/src: exit 0. Ablations, all through scripts/ablation-replace.mjs (anchor hit verified on disk, blob changed, restore equal to the HEAD blob with git diff HEAD empty). Both subjects resolve through relative src imports in their suites, so no dist is involved. (a) The door's file-type judgement replaced by continue: 6 of 11 redden (the door answers 201), the 5 controls and boundary hold; restored to blob e6855e87be17 == HEAD. (b1) The claim path's pre-write lookup context undone: exactly 1 of 6 reddens (the copy pin). (b2) The claim's own lookup context undone: exactly 2 of 6 redden (the insert and update claim pins). Both restored to blob 319ad0b2f3f2 == HEAD.",
"gates": "Local, derived on final head 80aab79ea2 by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths): 73 commands, each exit captured with output redirected before any pipe. 72 exited 0 in sequence. pnpm check:type-check-debt first exited 3 (its closure build got no turn on the shared lock: queue-timeout 99) and its rerun under os-verify-lock exited 0 ('none above its recorded number'). check:dual-build-cjs-loads and check:i18n had exited 3 on an earlier preview run and exited 0 here after their closures were built. --ran: '73 derived, 73 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. check-adr-0087-registration reads the changeset as [BREAKING+bang+clause-②-narrowing] with not-required (no-migration-prescription) accepted, and check-changeset-no-major reports no major bump. Outside the derived list: eslint over the two touched packages, exit 0; the full `pnpm lint` is CI's. CI on PR 25682 head 80aab79ea2 at report time, one read: 40 check-runs, 30 success, 7 skipped, 3 in_progress, 0 failure. Required contexts: Lint & Repo Gates success, TypeScript Type Check success, Build Core success, Governed Surface Queue Guard success; Test Core shards 1, 2 and 6 success, 3, 4 and 5 in_progress. Convergence is the seat's.",
"dogfood_battery": "80aab79ea2 · os-verify-lock: VERDICT command-exit 0 (held 22m31s, shared box) · `pnpm --filter @objectstack/dogfood test`, no OS_TEST_SHARD, no path, after the closure (dogfood^... plus check:i18n's) was rebuilt on this head · Test Files 294 passed | 3 skipped (297) · Tests 2516 passed | 18 skipped (2534) · no failures",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "2 — (1) relay op pr_create: POST /repos/objectstack-ai/objectstack/dispatches (fleet-write run 40108414677), which opened PR 25682 as a draft with assignee sunhaolin and no labels; the read-back reported 8166 bytes sent and 8166 stored, identical; (2) this os-dev-report comment on #25622 via scripts/pm/post-stamped.mjs. git push is not counted. Everything else was a REST read.",
"files_changed": [
"packages/rest/src/form-routes.ts (+50 / -7)",
"packages/rest/src/public-form-file-field.test.ts (+201, new)",
"packages/rest/src/public-form-intake-availability.test.ts (+7 / -3)",
"packages/services/service-storage/src/file-reference-lifecycle.ts (+39 / -6)",
"packages/services/service-storage/src/file-reference-organization-scope.test.ts (+208, new)",
".changeset/25572-public-form-submit-carries-no-file.md (+24, new)"
],
"line_budget": "n/a — no skills/** file and no ratcheted ledger touched; 545 changed lines (+529 / -16, 6 files), under the 3000 human-merge threshold; no governed surface",
"deviations": "(1) The Clause-② arm: both claims carry `Clause-②: no` with no arm. The dispatch asked for the arm to be judged, and AGENTS.md Post-Task Checklist step 3 names `(narrowing)` for a door that now refuses what it accepted, so the PR body and the changeset carry `Clause-②: no (narrowing)` (BREAKING, `fix(rest)!:`, ADR-0087 `not-required (no-migration-prescription)`), and the full dogfood battery ran for that arm. The claim lines were not copied verbatim. (2) One existing pin changed: public-form-intake-availability.test.ts's single-posture control asserted the whole submit reads only view metadata. It now asserts ['view', 'object']: one object read, by the submit's field judgement, and none for the predicate, so a predicate read still turns it red. (3) The derived gate pnpm check:type-check-debt first exited 3 (its closure build got no turn on the shared lock) and was rerun under the lock to exit 0. (4) origin/main was merged twice before the PR opened. The second merge came because main had touched packages/rest and the tenant-audit census script. The final head 80aab79ea2 was 7 commits behind origin/main when the gates ran (refactor moves in objectql, driver-sql and metadata-protocol, plus template pins). One derivation input, scripts/sync-template-versions.mjs, changed upstream, and the derived list was byte-identical across both merges. (5) The commit trailers are the model-free pair AGENTS.md requires (the pre-push hook refuses a model id), not the harness reminder's Co-Authored-By line; the PR footer is AGENTS.md's session-URL form.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: the seat (domain:server seat 2), off GitHub · noted, not filed. Every finding of this dispatch's measurement beyond the rule above went to the seat's private channel under the security-family disclosure discipline. None is written here."
]
}
merged: true, merge commit e2b40a264bc3dec751bfda500571d3edbcb161c1, merged 2026-10-25T04:00:16Z through the merge queue
origin/main
e2b40a264 is origin/main's tip, single parent
queue branch
no gh-readonly-queue/main/* head for PR 25682 remains
State: closed completed by Fixes #25622 and Fixes #25572 on merge (the folded dispatch). pm:dispatched stripped from both cards in this act.
Delivered (the rule only): under ruling C, an anonymous public-form submission carries no file: the submit door refuses a value in a declared file field, naming it, before anything is written; and a record write takes up a referenced file only within the organization it acts in. Declared Clause-②: no (narrowing), patch, BREAKING; the CONTRACT_REVIEW_TIER review was the director seat's (record 6386760176, PASS).
Activity
objectstack-fleet commentedon Oct 24, 2026
Triage: first grade —
bug·security·priority:p1·domain:server·area:files·pm:awaiting-maintainer· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-24T10:53Z⛔ Not a claim, ⛔ not a dispatch. ⛔ Nothing about the mechanism or the path goes on this card or any public surface (#9387).
maintoday, and the card files it under "may leak data". This seat has not seen the detail, so the maintainer re-grades once they have it.domain:server, with the route still open. Whichever way storage/public forms: an anonymous public-form submission cannot carry a file —/storage/uploadanswers 404 to the guest principal, so a required attachment field is impossible on a public form #25572 goes (it ispm:dispatched, ADR amendment PR docs(adr): ADR-0138 amendment — D2 class 7, the guest upload door bound to a public form #25619 is a draft), the fix must close this gap, so the route follows that decision.Maintainer-action: take the withheld detail from the filing session (
session_01JVTd3PprStGa26aez5omLB), choose the private channel, and hand it to adomain:serverdev session off GitHub — done when a claim comment on this card says the detail was received off GitHubGenerated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Route update · triage seat ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-24T13:59Z⛔ Nothing about the mechanism goes on this card (#9387). The state stays
pm:awaiting-maintainer, and theMaintainer-action:line stands./storage/uploadanswers 404 to the guest principal, so a required attachment field is impossible on a public form #25572 was ruled C at 2026-10-24T13:04Z (「25619 C」): no guest upload door. The anonymous submit door refuses a file field's value (storage/public forms: an anonymous public-form submission cannot carry a file —/storage/uploadanswers 404 to the guest principal, so a required attachment field is impossible on a public form #25572's server half), and publish refuses a public form that declares a file field (spec(ui): a form view open to anonymous intake (sharing.allowAnonymous) that declares a file field is refused at publish — the protocol half of #25572 (ruling C) #25650)./storage/uploadanswers 404 to the guest principal, so a required attachment field is impossible on a public form #25572's server half closes this gap is for the maintainer to confirm, since only the maintainer holds the detail. If it does, this card closes with storage/public forms: an anonymous public-form submission cannot carry a file —/storage/uploadanswers 404 to the guest principal, so a required attachment field is impossible on a public form #25572's PR. If it does not, the dev who receives the detail gets this card on its own.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Claim: PM loop round 1 · fold of #25622 (chain head) and #25572 (server half)
Session:
session_01JVTd3PprStGa26aez5omLBAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-25622-guest-submit-file-refusalWorktree:
objectstack-issue-25622Domain:
domain:serverSeat:
domain:server#2File surface: the anonymous public-form submit door in
packages/rest/src/form-routes.tsand the file-reference claim path inpackages/objectql/packages/corethe dev measures, their tests, one changeset. ⛔ No mechanism or path detail on any public surface (#9387) (stop on breach; explain in the report)Container & model:
M,security,mode:subagent(disclosure discipline: the detail stays in the seat's session),model: TIER_DEFAULT(dispatch-gates --tier: "no path-derived mandate … the tier stays the PM's per-card judgment call"; aCONTRACT_REVIEW_TIERreview runs before landing),effort: xhighClause-②: no
Metadata-protocol: none
Responsibility: the anonymous public-form submit path, detail withheld | ruling C on #25572 (an anonymous submission carries no file) is the route that closes it | any anonymous caller of a public form that declares a file field; reach measured by source reading, the dev measures it at the door
Thread-read: 6385106262
Serial constraints cleared: open PR #25641 (
domain:spec's #25574, ADR-0138 D5) editspackages/rest/src/form-routes.ts(+51/-5). The dev reads its diff before choosing the edit site; if the regions meet, whichever PR lands second mergesmain. No other open PR touches the form doors or the file-reference claim files (open PRs read this fire).Premise: the maintainer's action named on this card (
Maintainer-action:in 6383334528) is done. The withheld detail was received off GitHub, in this seat's session, where the maintainer read it; the maintainer chose that channel and approved this folded dispatch in the same session, answering 「可以,合并派发(推荐)」. The route is ruling C on #25572 (「25619 C 25495 B」).Fold: one branch and one PR,
Fixes #25622andFixes #25572on separate lines. Members are both graded, share one fix (the anonymous submit door refuses a file field's value) and are each checkable on their own. ⛔ Excluded: #25650 (the publish-time refusal,domain:spec), and any guest upload door (ruled out by C).Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
os-dev-report
{ "issue": 25622, "status": "done", "branch": "claude/issue-25622-guest-submit-file-refusal", "pr": "https://github.com/objectstack-ai/objectstack/pull/25682", "session": "session_01JVTd3PprStGa26aez5omLB — a subagent run, so the parent's harness-stamped id; claims 6385258213 (#25622) and 6385262132 (#25572) name it and this branch", "premise_still_valid": true, "summary": "Folded dispatch: one draft PR, whose first two body lines carry the closing declarations for both cards. Ruling C's rule, an anonymous public-form submission carries no file: POST /forms/:slug/submit refuses a value in a field whose declared type is in FILE_REFERENCE_TYPES, naming the field, before anything is written. It answers the door's existing 400 VALIDATION_FAILED envelope with fields[] code invalid_value (ADR-0114), in one envelope with the form's required judgement; no error code is added. An empty or absent file field submits as before, and only declared fields are judged. The file-reference claim path in service-storage now looks a referenced file up within the writing organization, through the channel that already stamps a copy for it, so the driver's tenant scope for the posture in force decides. The measurement behind that change, and every further finding, went to the seat off GitHub under the security-family disclosure discipline. A1: PR #25641's hunks in form-routes.ts and this edit are disjoint; git merge-tree of this branch with its head 08b020c9d3 exits 0 with no conflicted path.", "tests": "Head 80aab79ea2, each exit captured separately. pnpm --filter @objectstack/rest test: Test Files 321 passed (321), Tests 6417 passed | 326 skipped (6743); rest typecheck exit 0, and its test-layer program compiles both rest test files touched (--listFiles count 2). pnpm --filter @objectstack/service-storage test: Test Files 63 passed (63), Tests 1137 passed (1137); typecheck exit 0, with the new test in its test-layer program (--listFiles count 1). New suites: public-form-file-field.test.ts 11/11 and file-reference-organization-scope.test.ts 6/6. The refusal pins assert code VALIDATION_FAILED, status 400, and each fields[] entry's field, code and label; message wording is not pinned. eslint --no-inline-config with the root config over packages/rest/src and packages/services/service-storage/src: exit 0. Ablations, all through scripts/ablation-replace.mjs (anchor hit verified on disk, blob changed, restore equal to the HEAD blob with git diff HEAD empty). Both subjects resolve through relative src imports in their suites, so no dist is involved. (a) The door's file-type judgement replaced by continue: 6 of 11 redden (the door answers 201), the 5 controls and boundary hold; restored to blob e6855e87be17 == HEAD. (b1) The claim path's pre-write lookup context undone: exactly 1 of 6 reddens (the copy pin). (b2) The claim's own lookup context undone: exactly 2 of 6 redden (the insert and update claim pins). Both restored to blob 319ad0b2f3f2 == HEAD.", "gates": "Local, derived on final head 80aab79ea2 by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths): 73 commands, each exit captured with output redirected before any pipe. 72 exited 0 in sequence. pnpm check:type-check-debt first exited 3 (its closure build got no turn on the shared lock: queue-timeout 99) and its rerun under os-verify-lock exited 0 ('none above its recorded number'). check:dual-build-cjs-loads and check:i18n had exited 3 on an earlier preview run and exited 0 here after their closures were built. --ran: '73 derived, 73 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. check-adr-0087-registration reads the changeset as [BREAKING+bang+clause-②-narrowing] with not-required (no-migration-prescription) accepted, and check-changeset-no-major reports no major bump. Outside the derived list: eslint over the two touched packages, exit 0; the full `pnpm lint` is CI's. CI on PR 25682 head 80aab79ea2 at report time, one read: 40 check-runs, 30 success, 7 skipped, 3 in_progress, 0 failure. Required contexts: Lint & Repo Gates success, TypeScript Type Check success, Build Core success, Governed Surface Queue Guard success; Test Core shards 1, 2 and 6 success, 3, 4 and 5 in_progress. Convergence is the seat's.", "dogfood_battery": "80aab79ea2 · os-verify-lock: VERDICT command-exit 0 (held 22m31s, shared box) · `pnpm --filter @objectstack/dogfood test`, no OS_TEST_SHARD, no path, after the closure (dogfood^... plus check:i18n's) was rebuilt on this head · Test Files 294 passed | 3 skipped (297) · Tests 2516 passed | 18 skipped (2534) · no failures", "mcp_calls": "0 — no MCP GitHub tool was called", "api_writes": "2 — (1) relay op pr_create: POST /repos/objectstack-ai/objectstack/dispatches (fleet-write run 40108414677), which opened PR 25682 as a draft with assignee sunhaolin and no labels; the read-back reported 8166 bytes sent and 8166 stored, identical; (2) this os-dev-report comment on #25622 via scripts/pm/post-stamped.mjs. git push is not counted. Everything else was a REST read.", "files_changed": [ "packages/rest/src/form-routes.ts (+50 / -7)", "packages/rest/src/public-form-file-field.test.ts (+201, new)", "packages/rest/src/public-form-intake-availability.test.ts (+7 / -3)", "packages/services/service-storage/src/file-reference-lifecycle.ts (+39 / -6)", "packages/services/service-storage/src/file-reference-organization-scope.test.ts (+208, new)", ".changeset/25572-public-form-submit-carries-no-file.md (+24, new)" ], "line_budget": "n/a — no skills/** file and no ratcheted ledger touched; 545 changed lines (+529 / -16, 6 files), under the 3000 human-merge threshold; no governed surface", "deviations": "(1) The Clause-② arm: both claims carry `Clause-②: no` with no arm. The dispatch asked for the arm to be judged, and AGENTS.md Post-Task Checklist step 3 names `(narrowing)` for a door that now refuses what it accepted, so the PR body and the changeset carry `Clause-②: no (narrowing)` (BREAKING, `fix(rest)!:`, ADR-0087 `not-required (no-migration-prescription)`), and the full dogfood battery ran for that arm. The claim lines were not copied verbatim. (2) One existing pin changed: public-form-intake-availability.test.ts's single-posture control asserted the whole submit reads only view metadata. It now asserts ['view', 'object']: one object read, by the submit's field judgement, and none for the predicate, so a predicate read still turns it red. (3) The derived gate pnpm check:type-check-debt first exited 3 (its closure build got no turn on the shared lock) and was rerun under the lock to exit 0. (4) origin/main was merged twice before the PR opened. The second merge came because main had touched packages/rest and the tenant-audit census script. The final head 80aab79ea2 was 7 commits behind origin/main when the gates ran (refactor moves in objectql, driver-sql and metadata-protocol, plus template pins). One derivation input, scripts/sync-template-versions.mjs, changed upstream, and the derived list was byte-identical across both merges. (5) The commit trailers are the model-free pair AGENTS.md requires (the pre-push hook refuses a model id), not the harness reminder's Co-Authored-By line; the PR footer is AGENTS.md's session-URL form.", "open_questions": [], "out_of_scope_findings": [ "carrier: the seat (domain:server seat 2), off GitHub · noted, not filed. Every finding of this dispatch's measurement beyond the rule above went to the seat's private channel under the security-family disclosure discipline. None is written here." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
Landed: PR #25682 →
e2b40a264·domain:serverseat 2 ·sunhaolin·session_01JVTd3PprStGa26aez5omLB· 2026-10-25T04:01Zmerged: true, merge commite2b40a264bc3dec751bfda500571d3edbcb161c1, merged 2026-10-25T04:00:16Z through the merge queueorigin/maine2b40a264isorigin/main's tip, single parentgh-readonly-queue/main/*head for PR 25682 remainscompletedbyFixes #25622andFixes #25572on merge (the folded dispatch).pm:dispatchedstripped from both cards in this act.Clause-②: no (narrowing),patch, BREAKING; theCONTRACT_REVIEW_TIERreview was the director seat's (record 6386760176, PASS).sharing.allowAnonymous) that declares a file field is refused at publish — the protocol half of #25572 (ruling C) #25650,domain:spec). Further findings went to the maintainer off GitHub and are carried on their own cards.Generated by Claude Code