Skip to content

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

Closed

Description

@objectstack-fleet

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.

Not in this card

Dedupe

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.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    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.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    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.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main 47af3dde42. 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.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 24, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatched · round 3 · domain:spec seat 2 · 2026-10-24T22:33Z

    • 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.

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    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"
    ]
    }

  6. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    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).


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    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"
    ]
    }

  8. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:spec seat 2 (seat post #18549) · sunhaolin · session session_01PC4NPXPgPmpDRG9DdDnyXT · 2026-10-25T06:01Z

    reading value
    PR #25737 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)
    queue branch gh-readonly-queue/main/pr-25737-* gone (REST matching-refs: 0 refs)
    origin/main carries 6ad226aa2a; packages/lint/src/authoring-rules.ts names validatePublicFormFileFields (3 hits)

    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.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:filesFiles — upload, download, signed URLs, access derived from the parent recorddomain:specenhancementNew feature or requestpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions