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
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
Carries the protocol half of #25572 under ruling C; #25572 keeps the server half (the anonymous submit door refuses a file field's value) and does not wait on this card.
Filing gate ③, a task the maintainer set. The maintainer, verbatim, in domain:server seat 2's session (session_01JVTd3PprStGa26aez5omLB), 2026-10-24: 「25619 C 25495 B」. C is the option the seat record on PR #25619 (6383110676) names, in its words: 访客表单不收附件。供应商先注册账号、登录后再填表上传…发布时响亮拒绝「公开表单声明了文件字段」. The ruling record is on PR #25619. Filed by domain:server seat 2 (seat post #20966) · sunhaolin · session session_01JVTd3PprStGa26aez5omLB. ⛔ Not a claim. Domain and grade are triage's.
Business reason: a form open to anonymous intake cannot take a file: there is no guest upload door, and ruling C keeps it that way. Today an author (or an AI) can still declare a required attachment on such a form, which no guest can ever submit. That is declared-but-impossible, and it should fail loudly at publish with a prescription: "have the supplier register and sign in, then fill the form".
Reader who acts: the domain:spec seat, one PR.
Measured on origin/main
A form view is open to anonymous intake by sharing.allowAnonymous (packages/spec/src/ui/anonymous-form-intake.ts, with anonymousFormIntakeUnavailability). packages/spec/src/api/rest-server.zod.ts names that spelling as the way to publish a public form.
File-typed fields are the FILE_REFERENCE_TYPES set (packages/spec/src/data/field-value.zod.ts).
This card
The refusal: a form view with sharing.allowAnonymous: true whose fields include a field of a FILE_REFERENCE_TYPES type (as a direct field, or reachable in its sections) is refused at publish and in lint, naming the field. The prescription names the route ruling C chose (a registered, signed-in user), not a guest upload.
The docs page for public forms states the same rule and the register-first route for file intake.
Whatever the spec lane's process owes a new refusal (the refusal ledger, generated artefacts), by the spec lane's own rules.
Expected declarations: Clause-②: no with the arm the spec seat judges (a narrowing of what publish accepts), Metadata-protocol: narrowed. The spec seat confirms them.
The open and closed issues created since 2026-10-23T20:00Z, matched on allowAnonymous|public form|file field|25572: only #25572, #25619 (closed) and #25622. New.
Note for this card's dev, from domain:server seat 2 · sunhaolin · session_01JVTd3PprStGa26aez5omLB · 2026-10-24T16:40Z · ⛔ not a claim
PR #25682 (the server half of ruling C, Fixes #25572) makes POST /forms/:slug/submit refuse a value in a declared file field (400 VALIDATION_FAILED, fields[] code invalid_value). It does not change GET /forms/:slug, which still serves a declared file field in the form's sections, so a renderer may show an upload control a visitor cannot use. Measured by that PR's dev on the diff; objectui's renderer was not checked. The publish-time refusal this card owns is what removes such a form; until it lands, forms published earlier keep the field and the submit door's refusal is what a visitor meets.
Claim: PM loop round 3
Session: session_01PC4NPXPgPmpDRG9DdDnyXT
Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-25650-anonymous-form-file-refusal
Worktree: objectstack-issue-25650
Domain: domain:spec
Seat: domain:spec#2 (seat post #18549)
File surface: the publish-time and lint refusal of a form view open to anonymous intake (sharing.allowAnonymous: true, packages/spec/src/ui/anonymous-form-intake.ts) that declares a field whose type is in FILE_REFERENCE_TYPES (packages/spec/src/data/field-value.zod.ts:306), directly or in its sections. The refusal names the field and prescribes ruling C's route: the visitor registers and signs in, then fills the form. Field types live on the object, not the view, so the dev locates the stack-level door that sees both (the authoring rules that validate, compile and the publish gate run) and names it. Also its tests, the hand-written public-forms docs page, regenerated ui shards and references, and one changeset .changeset/25650-anonymous-form-file-refusal.md. The server half (the submit door's refusal) is #25572 / PR #25682, not this card. (stop on breach; explain in the report)
Container & model: M, mode:cloud (packages/spec/src/** plus packages/lint: build-heavy, one card per container), model: default (TIER_DEFAULT; dispatch-gates --tier for this surface: no path-derived mandate), effort: high. A narrowing ⇒ a CONTRACT_REVIEW_TIER contract review is owed before the queue.
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Responsibility: n/a — not a defect card
Ruling-ref: ruling C on #25572 (the maintainer, 「25619 C 25495 B」, 2026-10-24T13:04Z, recorded on PR #25619 with the seat record 6383110676); triage first grade 6385114404
Thread-read: 6386812660
Serial constraints cleared: none. No open PR touches these files (all 18 open PRs' file lists read 2026-10-24T22:27Z), and no open pm:dispatched claim declares them.
Premise: holds on origin/main47af3dde42. anonymous-form-intake.ts decides when a form is open to anonymous intake, and FILE_REFERENCE_TYPES names the file types. No refusal anywhere in packages/spec/src/ui or packages/lint/src reads FILE_REFERENCE_TYPES (0 hits), so a public form that declares a file field publishes clean today.
Back end:mode:cloud, cloud session session_01UDTkSkW5Hks1YsBoNomhDR (created by this seat's session session_01PC4NPXPgPmpDRG9DdDnyXT through create_session), on branch claude/issue-25650-anonymous-form-file-refusal. The claim 6390330367 stays the identity.
Order given: ruling C verbatim (the seat record 6383110676), triage 6385114404, the lead that the door must see both the view's sharing and the object's field types, the PR fix(rest)!: an anonymous public-form submission carries no file #25682 server-half note, the producer measurement, the Writes: line.
Round-2 lessons carried in the order: the cli integration project for migration entries; no tracker ids in printed guidance; an ablation on every new refusal branch; downstream producers (objectui, HotCRM) measured before a refusal or a removal; governed paths in their own PR.
os-dev-report
{
"issue": 25650,
"status": "done",
"branch": "claude/issue-25650-anonymous-form-file-refusal",
"pr": "#25737",
"session": "session_01UDTkSkW5Hks1YsBoNomhDR (executor; dispatched from session_01PC4NPXPgPmpDRG9DdDnyXT)",
"premise_still_valid": true,
"summary": "Premise holds on origin/main 47af3dd: no refusal read FILE_REFERENCE_TYPES against a public form. The door that sees both halves is the stack-level authoring registry AUTHORING_RULES (packages/lint/src/authoring-rules.ts): os validate/build/lint run it, and the runtime publish gate runs it on a view write, whose snapshot carries objects as context (buildRuntimeWriteSnapshotSet). New gating rule validatePublicFormFileFields (rule id public-form-file-field) takes the open forms from anonymousFormIntakeCandidates, the one rule both anonymous doors serve. It covers form / formViews.KEY / viewKind form config, sections and the legacy groups bucket, and bare and {field} entries. It refuses any declared field whose type on the bound object is in FILE_REFERENCE_TYPES, and prescribes ruling C's route (register and sign in, then a non-public form). Lead corrections: open-to-anonymous needs enabled + allowAnonymous + publicLink, not allowAnonymous alone; anonymousFormIntakeUnavailability lives in metadata-core, not the spec module; FormViewSchema has no direct fields key, and both doors read sections[].fields only. Producers: 0. Not BREAKING for any named producer (examples, platform-objects, HotCRM a4b3560, objectui a3c0da9 all measured).",
"tests": "Pinned to f6099df unless noted. pnpm --filter @objectstack/lint test: 162 files / 7293 tests passed (the new validate-public-form-file-fields.test.ts has 13 tests; view-rule roster pins updated in 3 files). pnpm --filter @objectstack/metadata-protocol test (e0aa3cb): 263 / 21489 passed, including 4 new door pins (422 INVALID_METADATA + nothing stored; draft promotion refused; door == os build message and hint; control saves). pnpm --filter @objectstack/cli test, unit + integration (d4c18b2): 500 / 6291 passed. Consumer registry files: objectql 3/29, platform-objects 1/2, plugin-approvals 2/24, service-mailbox 1/4 passed. typecheck lint + metadata-protocol exit 0. test:repo: none of lint, metadata-protocol or cli declares one (the 6 packages that do are core, objectql, rest, runtime, spec and types; none reads this rule's view roster). Ablation via ablation-replace on the committed fix: guard made always-skip with marker ablation_25650_never, lint rebuilt, ablation-dist-preflight found the marker in 4 dist files. Predicted 8/13 lint red and 3/4 door red; observed 8 failed / 5 passed and 3 failed / 1 passed, the same tests. Restored blob 3325e760befb == HEAD, git diff HEAD empty. Restore leg: rebuild, preflight --absent ok, 13/13 and 4/4 green.",
"gates": "dispatch-gates --commands on f6099df: 97 commands, each run with its exit code captured to a file before any pipe; 97 exit 0. First pass: check:dts-closure and check:dual-build-cjs-loads exit 1 on plugin-auth dist being rewritten by the concurrent dogfood closure build (not touched by this diff); both re-run exit 0. Earlier rounds: check:docs-transcript-drift exit 1 was a real finding (CLI rule count 52 to 53 in 4 docs), fixed in this PR. Three exit-3 prerequisite refusals (skill-examples, dual-build-cjs-loads, lean-entry-closure: no dist) were cleared by pnpm build and re-run exit 0. check-plugin-teardown-shape --self-test exit 3 (shallow clone) was cleared by fetching 621a487 and re-run exit 0. --ran reconciliation: 97 derived, 97 run, 0 NOT-MEASURED, 0 UNRUN. CI check-runs: in_progress at report time (not awaited, per the role file).",
"dogfood_battery": "e0aa3cb98→f6099df44 closure (build + test launched on f6099df) · os-verify-lock VERDICT command-exit 0 · Test Files 295 passed / 3 skipped (298) · Tests 2520 passed / 18 skipped (2538) · no failures",
"mcp_calls": "2: mcp__github__issue_read (get, get_comments) on #25650. No write tool.",
"api_writes": "2 relay strokes: fleet-write pr_create (POST /repos/objectstack-ai/objectstack/pulls draft, plus POST /issues/25737/assignees; run 40158335052, body read back byte-identical); this os-dev-report comment via post-stamped.mjs (POST /issues/25650/comments).",
"open_questions": [
{ "question": "Deviation from the dispatch, role file wins: the role file says not to wait on or subscribe to the PR's CI (the dispatch contract overrides the platform's PR-subscription posture), so this session did not subscribe to #25737 and reports with CI in_progress.", "options": ["A. As done", "B. The seat subscribes or watches CI"], "recommendation": "A, because the role file states it and the seat's §10 recheck owns CI convergence." },
{ "question": "Worktree: the cloud session's primary checkout already held the branch. I detached that checkout's HEAD and added a linked worktree at ../objectstack-issue-25650 on the same branch, the worktree the claim names, so every edit happened in a linked worktree as the hook requires. Recorded as a procedure note.", "options": ["A. As done"], "recommendation": "A" },
{ "question": "Object resolution: the rule spells anonymousFormObjectName's rung order (form binding, view list block, view default form, view) over lint's viewObjectName, because lint cannot import @objectstack/metadata-core. Should the helper move to @objectstack/spec/ui beside the candidates half, so there is one copy?", "options": ["A. Leave as is (two readings of one order; disagree only when a view's list and its own object name different objects)", "B. A follow-up card moves anonymousFormObjectName into spec/ui; metadata-core re-exports it (outside this card's declared file surface)"], "recommendation": "B, on the four axes. Business need: no producer hits the divergence today (measured 0), so it is not urgent. Long-term: one rule, one copy (contract-first). AI-mistake axis: one reading cannot drift. Startup focus: a small follow-up, not this PR." },
{ "question": "Object-write gap: an object write that retypes a field to a file type under a published public form is not refused at the object door (the snapshot carries no views). It is caught by validate, compile and the view's next publish.", "options": ["A. Accept (noted in Acceptance notes)", "B. Widen the object-write snapshot to carry views (new gate surface)"], "recommendation": "A. No producer does this today, and a new gate surface defaults to no without a maintainer naming it." }
],
"out_of_scope_findings": [
"class: none (observation) · the object-write gap above · carrier: none, recorded only in PR #25737's Acceptance notes · dedupe words: public form file field object write retype, runtimeTypes object views snapshot"
]
}
Claim addendum (amends the file surface of 6390330367, round 3) · domain:spec seat 2 · 2026-10-25T04:25Z
PR #25737 lands the refusal where the claim said to look, the stack-level authoring rules. It turns out to be packages/lint alone, with no packages/spec change:
packages/lint/src/validate-public-form-file-fields.ts (new rule public-form-file-field), wired in authoring-rules.ts and index.ts, with three roster pins updated.
packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts: four door pins (422 at publish, draft promotion refused, door message equals os build, control saves).
Pages the change made false or incomplete: content/docs/ui/forms.mdx, ui/public-data-collection.mdx, deployment/validating-metadata.mdx, and the CLI rule count (52 → 53) in deployment/cli.mdx, getting-started/build-with-claude-code.mdx and ui/react-pages.mdx.
None of these is a governed path (check-governed-merges --pr 25737: NOT governed).
os-dev-report
{
"issue": 25650,
"status": "done",
"branch": "claude/issue-25650-anonymous-form-file-refusal",
"pr": "#25737",
"session": "session_01UDTkSkW5Hks1YsBoNomhDR (executor; dispatched from session_01PC4NPXPgPmpDRG9DdDnyXT)",
"premise_still_valid": true,
"summary": "Supersedes the earlier os-dev-report on this card: head moved to e316504 after merging origin/main (11 commits, including e5cf9f0, which moved the runtime authoring gate, and f6ba134, a lint refusal). The merge was clean, with no generated-file conflict. Premise holds on origin/main 47af3dd: no refusal read FILE_REFERENCE_TYPES against a public form. The door that sees both halves is the stack-level authoring registry AUTHORING_RULES (packages/lint/src/authoring-rules.ts): os validate/build/lint run it, and the runtime publish gate runs it on a view write, whose snapshot carries objects as context (buildRuntimeWriteSnapshotSet). New gating rule validatePublicFormFileFields (rule id public-form-file-field) takes the open forms from anonymousFormIntakeCandidates, the one rule both anonymous doors serve. It covers form / formViews.KEY / viewKind form config, sections and the legacy groups bucket, and bare and {field} entries. It refuses any declared field whose type on the bound object is in FILE_REFERENCE_TYPES, and prescribes ruling C's route (register and sign in, then a non-public form). Lead corrections: open-to-anonymous needs enabled + allowAnonymous + publicLink, not allowAnonymous alone; anonymousFormIntakeUnavailability lives in metadata-core, not the spec module; FormViewSchema has no direct fields key, and both doors read sections[].fields only. Producers: 0. Not BREAKING for any named producer (examples, platform-objects, HotCRM a4b3560, objectui a3c0da9 all measured).",
"tests": "On e316504: pnpm --filter @objectstack/lint test 162 files / 7298 tests passed; pnpm --filter @objectstack/metadata-protocol test 263 / 21489 passed (4 door pins included). Earlier, unchanged by the merge: CLI unit + integration 500 / 6291 passed on d4c18b2; objectql 3/29, platform-objects 1/2, plugin-approvals 2/24, service-mailbox 1/4 passed; ablation via ablation-replace (guard made always-skip, marker proved in 4 dist files; predicted and observed 8/13 lint and 3/4 door red; restored blob 3325e760befb == HEAD; restore leg rebuilt, --absent ok, 13/13 and 4/4 green).",
"gates": "dispatch-gates --commands on e316504: 97 commands, each exit code captured to a file before any pipe; --ran reconciliation 97 derived / 97 run / 0 NOT-MEASURED / 0 UNRUN. 94 exit 0 in the run. 3 exit-3 prerequisite refusals (skill-examples, dual-build-cjs-loads, lean-entry-closure: the fresh worktree had no dist) were re-run exit 0 after pnpm build. check:docs-transcript-drift exit 0 (the CLI rule count stays 53 after the merge). CI check-runs: not awaited, per the role file.",
"dogfood_battery": "e3165041a · os-verify-lock VERDICT command-exit 0 · Test Files 295 passed / 3 skipped (298) · Tests 2520 passed / 18 skipped (2538) · no failures",
"mcp_calls": "2: mcp__github__issue_read (get, get_comments). No write tool.",
"api_writes": "3 relay strokes: fleet-write pr_create (POST /repos/objectstack-ai/objectstack/pulls draft, plus POST /issues/25737/assignees); the first os-dev-report and this superseding one (POST /issues/25650/comments, via post-stamped.mjs).",
"open_questions": [
{
"question": "Deviation from the dispatch, role file wins: the role file says not to wait on or subscribe to the PR's CI (the dispatch contract overrides the platform's PR-subscription posture), so this session did not subscribe to #25737 and reports with CI in_progress.",
"options": [
"A. As done",
"B. The seat subscribes or watches CI"
],
"recommendation": "A, because the role file states it and the seat's §10 recheck owns CI convergence."
},
{
"question": "Worktree: the cloud session's primary checkout already held the branch. I detached that checkout's HEAD and added a linked worktree at ../objectstack-issue-25650 on the same branch, the worktree the claim names, so every edit happened in a linked worktree as the hook requires. Recorded as a procedure note.",
"options": [
"A. As done"
],
"recommendation": "A"
},
{
"question": "Object resolution: the rule spells anonymousFormObjectName's rung order (form binding, view list block, view default form, view) over lint's viewObjectName, because lint cannot import @objectstack/metadata-core. Should the helper move to @objectstack/spec/ui beside the candidates half, so there is one copy?",
"options": [
"A. Leave as is (two readings of one order; disagree only when a view's list and its own object name different objects)",
"B. A follow-up card moves anonymousFormObjectName into spec/ui; metadata-core re-exports it (outside this card's declared file surface)"
],
"recommendation": "B, on the four axes. Business need: no producer hits the divergence today (measured 0), so it is not urgent. Long-term: one rule, one copy (contract-first). AI-mistake axis: one reading cannot drift. Startup focus: a small follow-up, not this PR."
},
{
"question": "Object-write gap: an object write that retypes a field to a file type under a published public form is not refused at the object door (the snapshot carries no views). It is caught by validate, compile and the view's next publish.",
"options": [
"A. Accept (noted in Acceptance notes)",
"B. Widen the object-write snapshot to carry views (new gate surface)"
],
"recommendation": "A. No producer does this today, and a new gate surface defaults to no without a maintainer naming it."
}
],
"out_of_scope_findings": [
"class: none (observation) · the object-write gap above · carrier: none, recorded only in PR #25737's Acceptance notes · dedupe words: public form file field object write retype, runtimeTypes object views snapshot"
]
}
MERGED through the queue at 2026-10-25T05:57:37Z; merge commit 6ad226aa2a. ACCEPT 6393424180; contract review PASS 6393521439, with a concurring seat-chain record after it, both on head e3165041aa (the landing head)
State: the card closed completed on the merge (Fixes #25650). pm:dispatched is removed in this stroke, and the dev's cloud session session_01UDTkSkW5Hks1YsBoNomhDR is archived.
Accepted gap, no carrier, per ACCEPT item 5 (4, A): an object write that retypes a field to a file type under a published public form is caught by validate / compile and by the view's next publish, not at the object door.
Activity
objectstack-fleet commentedon Oct 24, 2026
Triage: first grade —
enhancement·priority:p2·domain:spec·area:files·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-24T14:00Z⛔ Not a claim, ⛔ not a dispatch.
/storage/uploadanswers 404 to the guest principal, so a required attachment field is impossible on a public form #25572 (the maintainer, at 2026-10-24T13:04Z, 「25619 C」): no guest upload door, and a public form that declares a file field is refused loudly at publish./storage/uploadanswers 404 to the guest principal, so a required attachment field is impossible on a public form #25572, is p2 too. The two do not wait on each other.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Note for this card's dev, from
domain:serverseat 2 ·sunhaolin·session_01JVTd3PprStGa26aez5omLB· 2026-10-24T16:40Z · ⛔ not a claimPR #25682 (the server half of ruling C,
Fixes #25572) makesPOST /forms/:slug/submitrefuse a value in a declared file field (400 VALIDATION_FAILED,fields[]codeinvalid_value). It does not changeGET /forms/:slug, which still serves a declared file field in the form's sections, so a renderer may show an upload control a visitor cannot use. Measured by that PR's dev on the diff; objectui's renderer was not checked. The publish-time refusal this card owns is what removes such a form; until it lands, forms published earlier keep the field and the submit door's refusal is what a visitor meets.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Claim: PM loop round 3
Session:
session_01PC4NPXPgPmpDRG9DdDnyXTAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-25650-anonymous-form-file-refusalWorktree:
objectstack-issue-25650Domain:
domain:specSeat:
domain:spec#2(seat post #18549)File surface: the publish-time and lint refusal of a form view open to anonymous intake (
sharing.allowAnonymous: true,packages/spec/src/ui/anonymous-form-intake.ts) that declares a field whose type is inFILE_REFERENCE_TYPES(packages/spec/src/data/field-value.zod.ts:306), directly or in its sections. The refusal names the field and prescribes ruling C's route: the visitor registers and signs in, then fills the form. Field types live on the object, not the view, so the dev locates the stack-level door that sees both (the authoring rules thatvalidate,compileand the publish gate run) and names it. Also its tests, the hand-written public-forms docs page, regenerateduishards and references, and one changeset.changeset/25650-anonymous-form-file-refusal.md. The server half (the submit door's refusal) is #25572 / PR #25682, not this card. (stop on breach; explain in the report)Container & model:
M,mode:cloud(packages/spec/src/**pluspackages/lint: build-heavy, one card per container),model: default (TIER_DEFAULT; dispatch-gates --tier for this surface: no path-derived mandate),effort: high. A narrowing ⇒ aCONTRACT_REVIEW_TIERcontract review is owed before the queue.Clause-②: no (narrowing)
Metadata-protocol: narrowed
Responsibility: n/a — not a defect card
Ruling-ref: ruling C on #25572 (the maintainer, 「25619 C 25495 B」, 2026-10-24T13:04Z, recorded on PR #25619 with the seat record
6383110676); triage first grade6385114404Thread-read: 6386812660
Serial constraints cleared: none. No open PR touches these files (all 18 open PRs' file lists read 2026-10-24T22:27Z), and no open
pm:dispatchedclaim declares them.Premise: holds on
origin/main47af3dde42.anonymous-form-intake.tsdecides when a form is open to anonymous intake, andFILE_REFERENCE_TYPESnames the file types. No refusal anywhere inpackages/spec/src/uiorpackages/lint/srcreadsFILE_REFERENCE_TYPES(0 hits), so a public form that declares a file field publishes clean today.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Dispatched · round 3 ·
domain:specseat 2 · 2026-10-24T22:33Zmode:cloud, cloud sessionsession_01UDTkSkW5Hks1YsBoNomhDR(created by this seat's sessionsession_01PC4NPXPgPmpDRG9DdDnyXTthroughcreate_session), on branchclaude/issue-25650-anonymous-form-file-refusal. The claim6390330367stays the identity.6383110676), triage6385114404, the lead that the door must see both the view'ssharingand the object's field types, the PR fix(rest)!: an anonymous public-form submission carries no file #25682 server-half note, the producer measurement, theWrites:line.Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
os-dev-report
{
"issue": 25650,
"status": "done",
"branch": "claude/issue-25650-anonymous-form-file-refusal",
"pr": "#25737",
"session": "session_01UDTkSkW5Hks1YsBoNomhDR (executor; dispatched from session_01PC4NPXPgPmpDRG9DdDnyXT)",
"premise_still_valid": true,
"summary": "Premise holds on origin/main 47af3dd: no refusal read FILE_REFERENCE_TYPES against a public form. The door that sees both halves is the stack-level authoring registry AUTHORING_RULES (packages/lint/src/authoring-rules.ts): os validate/build/lint run it, and the runtime publish gate runs it on a view write, whose snapshot carries objects as context (buildRuntimeWriteSnapshotSet). New gating rule validatePublicFormFileFields (rule id public-form-file-field) takes the open forms from anonymousFormIntakeCandidates, the one rule both anonymous doors serve. It covers form / formViews.KEY / viewKind form config, sections and the legacy groups bucket, and bare and {field} entries. It refuses any declared field whose type on the bound object is in FILE_REFERENCE_TYPES, and prescribes ruling C's route (register and sign in, then a non-public form). Lead corrections: open-to-anonymous needs enabled + allowAnonymous + publicLink, not allowAnonymous alone; anonymousFormIntakeUnavailability lives in metadata-core, not the spec module; FormViewSchema has no direct fields key, and both doors read sections[].fields only. Producers: 0. Not BREAKING for any named producer (examples, platform-objects, HotCRM a4b3560, objectui a3c0da9 all measured).",
"tests": "Pinned to f6099df unless noted. pnpm --filter @objectstack/lint test: 162 files / 7293 tests passed (the new validate-public-form-file-fields.test.ts has 13 tests; view-rule roster pins updated in 3 files). pnpm --filter @objectstack/metadata-protocol test (e0aa3cb): 263 / 21489 passed, including 4 new door pins (422 INVALID_METADATA + nothing stored; draft promotion refused; door == os build message and hint; control saves). pnpm --filter @objectstack/cli test, unit + integration (d4c18b2): 500 / 6291 passed. Consumer registry files: objectql 3/29, platform-objects 1/2, plugin-approvals 2/24, service-mailbox 1/4 passed. typecheck lint + metadata-protocol exit 0. test:repo: none of lint, metadata-protocol or cli declares one (the 6 packages that do are core, objectql, rest, runtime, spec and types; none reads this rule's view roster). Ablation via ablation-replace on the committed fix: guard made always-skip with marker ablation_25650_never, lint rebuilt, ablation-dist-preflight found the marker in 4 dist files. Predicted 8/13 lint red and 3/4 door red; observed 8 failed / 5 passed and 3 failed / 1 passed, the same tests. Restored blob 3325e760befb == HEAD, git diff HEAD empty. Restore leg: rebuild, preflight --absent ok, 13/13 and 4/4 green.",
"gates": "dispatch-gates --commands on f6099df: 97 commands, each run with its exit code captured to a file before any pipe; 97 exit 0. First pass: check:dts-closure and check:dual-build-cjs-loads exit 1 on plugin-auth dist being rewritten by the concurrent dogfood closure build (not touched by this diff); both re-run exit 0. Earlier rounds: check:docs-transcript-drift exit 1 was a real finding (CLI rule count 52 to 53 in 4 docs), fixed in this PR. Three exit-3 prerequisite refusals (skill-examples, dual-build-cjs-loads, lean-entry-closure: no dist) were cleared by pnpm build and re-run exit 0. check-plugin-teardown-shape --self-test exit 3 (shallow clone) was cleared by fetching 621a487 and re-run exit 0. --ran reconciliation: 97 derived, 97 run, 0 NOT-MEASURED, 0 UNRUN. CI check-runs: in_progress at report time (not awaited, per the role file).",
"dogfood_battery": "e0aa3cb98→f6099df44 closure (build + test launched on f6099df) · os-verify-lock VERDICT command-exit 0 · Test Files 295 passed / 3 skipped (298) · Tests 2520 passed / 18 skipped (2538) · no failures",
"mcp_calls": "2: mcp__github__issue_read (get, get_comments) on #25650. No write tool.",
"api_writes": "2 relay strokes: fleet-write pr_create (POST /repos/objectstack-ai/objectstack/pulls draft, plus POST /issues/25737/assignees; run 40158335052, body read back byte-identical); this os-dev-report comment via post-stamped.mjs (POST /issues/25650/comments).",
"open_questions": [
{ "question": "Deviation from the dispatch, role file wins: the role file says not to wait on or subscribe to the PR's CI (the dispatch contract overrides the platform's PR-subscription posture), so this session did not subscribe to #25737 and reports with CI in_progress.", "options": ["A. As done", "B. The seat subscribes or watches CI"], "recommendation": "A, because the role file states it and the seat's §10 recheck owns CI convergence." },
{ "question": "Worktree: the cloud session's primary checkout already held the branch. I detached that checkout's HEAD and added a linked worktree at ../objectstack-issue-25650 on the same branch, the worktree the claim names, so every edit happened in a linked worktree as the hook requires. Recorded as a procedure note.", "options": ["A. As done"], "recommendation": "A" },
{ "question": "Object resolution: the rule spells anonymousFormObjectName's rung order (form binding, view list block, view default form, view) over lint's viewObjectName, because lint cannot import @objectstack/metadata-core. Should the helper move to @objectstack/spec/ui beside the candidates half, so there is one copy?", "options": ["A. Leave as is (two readings of one order; disagree only when a view's list and its own object name different objects)", "B. A follow-up card moves anonymousFormObjectName into spec/ui; metadata-core re-exports it (outside this card's declared file surface)"], "recommendation": "B, on the four axes. Business need: no producer hits the divergence today (measured 0), so it is not urgent. Long-term: one rule, one copy (contract-first). AI-mistake axis: one reading cannot drift. Startup focus: a small follow-up, not this PR." },
{ "question": "Object-write gap: an object write that retypes a field to a file type under a published public form is not refused at the object door (the snapshot carries no views). It is caught by validate, compile and the view's next publish.", "options": ["A. Accept (noted in Acceptance notes)", "B. Widen the object-write snapshot to carry views (new gate surface)"], "recommendation": "A. No producer does this today, and a new gate surface defaults to no without a maintainer naming it." }
],
"out_of_scope_findings": [
"class: none (observation) · the object-write gap above · carrier: none, recorded only in PR #25737's Acceptance notes · dedupe words: public form file field object write retype, runtimeTypes object views snapshot"
]
}
objectstack-fleet commentedon Oct 25, 2026
Claim addendum (amends the file surface of
6390330367, round 3) ·domain:specseat 2 · 2026-10-25T04:25ZPR #25737 lands the refusal where the claim said to look, the stack-level authoring rules. It turns out to be
packages/lintalone, with nopackages/specchange:packages/lint/src/validate-public-form-file-fields.ts(new rulepublic-form-file-field), wired inauthoring-rules.tsandindex.ts, with three roster pins updated.packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts: four door pins (422 at publish, draft promotion refused, door message equalsos build, control saves).content/docs/ui/forms.mdx,ui/public-data-collection.mdx,deployment/validating-metadata.mdx, and the CLI rule count (52 → 53) indeployment/cli.mdx,getting-started/build-with-claude-code.mdxandui/react-pages.mdx.None of these is a governed path (
check-governed-merges --pr 25737: NOT governed).Generated by Claude Code
objectstack-fleet commentedon Oct 25, 2026
os-dev-report
{
"issue": 25650,
"status": "done",
"branch": "claude/issue-25650-anonymous-form-file-refusal",
"pr": "#25737",
"session": "session_01UDTkSkW5Hks1YsBoNomhDR (executor; dispatched from session_01PC4NPXPgPmpDRG9DdDnyXT)",
"premise_still_valid": true,
"summary": "Supersedes the earlier os-dev-report on this card: head moved to e316504 after merging origin/main (11 commits, including e5cf9f0, which moved the runtime authoring gate, and f6ba134, a lint refusal). The merge was clean, with no generated-file conflict. Premise holds on origin/main 47af3dd: no refusal read FILE_REFERENCE_TYPES against a public form. The door that sees both halves is the stack-level authoring registry AUTHORING_RULES (packages/lint/src/authoring-rules.ts): os validate/build/lint run it, and the runtime publish gate runs it on a view write, whose snapshot carries objects as context (buildRuntimeWriteSnapshotSet). New gating rule validatePublicFormFileFields (rule id public-form-file-field) takes the open forms from anonymousFormIntakeCandidates, the one rule both anonymous doors serve. It covers form / formViews.KEY / viewKind form config, sections and the legacy groups bucket, and bare and {field} entries. It refuses any declared field whose type on the bound object is in FILE_REFERENCE_TYPES, and prescribes ruling C's route (register and sign in, then a non-public form). Lead corrections: open-to-anonymous needs enabled + allowAnonymous + publicLink, not allowAnonymous alone; anonymousFormIntakeUnavailability lives in metadata-core, not the spec module; FormViewSchema has no direct fields key, and both doors read sections[].fields only. Producers: 0. Not BREAKING for any named producer (examples, platform-objects, HotCRM a4b3560, objectui a3c0da9 all measured).",
"tests": "On e316504: pnpm --filter @objectstack/lint test 162 files / 7298 tests passed; pnpm --filter @objectstack/metadata-protocol test 263 / 21489 passed (4 door pins included). Earlier, unchanged by the merge: CLI unit + integration 500 / 6291 passed on d4c18b2; objectql 3/29, platform-objects 1/2, plugin-approvals 2/24, service-mailbox 1/4 passed; ablation via ablation-replace (guard made always-skip, marker proved in 4 dist files; predicted and observed 8/13 lint and 3/4 door red; restored blob 3325e760befb == HEAD; restore leg rebuilt, --absent ok, 13/13 and 4/4 green).",
"gates": "dispatch-gates --commands on e316504: 97 commands, each exit code captured to a file before any pipe; --ran reconciliation 97 derived / 97 run / 0 NOT-MEASURED / 0 UNRUN. 94 exit 0 in the run. 3 exit-3 prerequisite refusals (skill-examples, dual-build-cjs-loads, lean-entry-closure: the fresh worktree had no dist) were re-run exit 0 after pnpm build. check:docs-transcript-drift exit 0 (the CLI rule count stays 53 after the merge). CI check-runs: not awaited, per the role file.",
"dogfood_battery": "e3165041a · os-verify-lock VERDICT command-exit 0 · Test Files 295 passed / 3 skipped (298) · Tests 2520 passed / 18 skipped (2538) · no failures",
"mcp_calls": "2: mcp__github__issue_read (get, get_comments). No write tool.",
"api_writes": "3 relay strokes: fleet-write pr_create (POST /repos/objectstack-ai/objectstack/pulls draft, plus POST /issues/25737/assignees); the first os-dev-report and this superseding one (POST /issues/25650/comments, via post-stamped.mjs).",
"open_questions": [
{
"question": "Deviation from the dispatch, role file wins: the role file says not to wait on or subscribe to the PR's CI (the dispatch contract overrides the platform's PR-subscription posture), so this session did not subscribe to #25737 and reports with CI in_progress.",
"options": [
"A. As done",
"B. The seat subscribes or watches CI"
],
"recommendation": "A, because the role file states it and the seat's §10 recheck owns CI convergence."
},
{
"question": "Worktree: the cloud session's primary checkout already held the branch. I detached that checkout's HEAD and added a linked worktree at ../objectstack-issue-25650 on the same branch, the worktree the claim names, so every edit happened in a linked worktree as the hook requires. Recorded as a procedure note.",
"options": [
"A. As done"
],
"recommendation": "A"
},
{
"question": "Object resolution: the rule spells anonymousFormObjectName's rung order (form binding, view list block, view default form, view) over lint's viewObjectName, because lint cannot import @objectstack/metadata-core. Should the helper move to @objectstack/spec/ui beside the candidates half, so there is one copy?",
"options": [
"A. Leave as is (two readings of one order; disagree only when a view's list and its own object name different objects)",
"B. A follow-up card moves anonymousFormObjectName into spec/ui; metadata-core re-exports it (outside this card's declared file surface)"
],
"recommendation": "B, on the four axes. Business need: no producer hits the divergence today (measured 0), so it is not urgent. Long-term: one rule, one copy (contract-first). AI-mistake axis: one reading cannot drift. Startup focus: a small follow-up, not this PR."
},
{
"question": "Object-write gap: an object write that retypes a field to a file type under a published public form is not refused at the object door (the snapshot carries no views). It is caught by validate, compile and the view's next publish.",
"options": [
"A. Accept (noted in Acceptance notes)",
"B. Widen the object-write snapshot to carry views (new gate surface)"
],
"recommendation": "A. No producer does this today, and a new gate surface defaults to no without a maintainer naming it."
}
],
"out_of_scope_findings": [
"class: none (observation) · the object-write gap above · carrier: none, recorded only in PR #25737's Acceptance notes · dedupe words: public form file field object write retype, runtimeTypes object views snapshot"
]
}
objectstack-fleet commentedon Oct 25, 2026
Landed ·
domain:specseat 2 (seat post #18549) ·sunhaolin· sessionsession_01PC4NPXPgPmpDRG9DdDnyXT· 2026-10-25T06:01Z6ad226aa2a. ACCEPT6393424180; contract review PASS6393521439, with a concurring seat-chain record after it, both on heade3165041aa(the landing head)gh-readonly-queue/main/pr-25737-*matching-refs: 0 refs)origin/main6ad226aa2a;packages/lint/src/authoring-rules.tsnamesvalidatePublicFormFileFields(3 hits)State: the card closed
completedon the merge (Fixes #25650).pm:dispatchedis removed in this stroke, and the dev's cloud sessionsession_01UDTkSkW5Hks1YsBoNomhDRis archived.@objectstack/spec/ui, as ACCEPT item 5 committed (open question 3, B). Measured divergence today is 0.objectwrite that retypes a field to a file type under a published public form is caught byvalidate/compileand by the view's next publish, not at the object door.Generated by Claude Code