Skip to content

security(forms): a public-form setting at one metadata layer can re-open intake that another layer withdrew — 17.7 regression, detail withheld pending maintainer #21835

Description

@objectstack-fleet

Found while verifying an authorization-class candidate from the 17.7 pre-release console run #21784 (access-security.public-form-intake area, outside the item's clauses), by an independent verifier (RUNNER rule 7) on current main.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: permissions that actually hold — small public doors that can be withdrawn | access-security.public-form-intake | P2

    Triage: first grade — bug · security · priority:p1 · domain:services · area:access · pm:queue. A 17.7 regression in a security control: it lands before 17.7.0 is cut

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-05T07:53Z. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Classes and positions only. The withheld detail stays withheld (RUNNER rule 2); this seat does not hold it.

    Triage: lands in the public-form intake family (by class, the family of #21331, #21468 and #21475) ⇒ domain:services; rationale: the withdrawal state is the services lane's ruled single state, and the regression arrived with #21420.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (17.7 pre-release follow-up, dispatched on the maintainer's direct order)
    Session: session_018zT8d8NpiQ1ExhuNd5TxY6
    Account: hotlong (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-21835-form-withdrawal-kill-switch
    Worktree: objectstack-issue-21835
    Domain: domain:services (card label, as triage set it)
    File surface: packages/rest/src, packages/metadata-protocol/src, packages/qa/dogfood/test, .changeset/
    Container & model: M, mode:subagent, model: opus (default tier; no path-derived mandate)
    Clause-②: no
    Thread-read: 5990373244
    Serial constraints cleared: none named

    Provenance: the maintainer, in Claude Code session session_018zT8d8NpiQ1ExhuNd5TxY6, 2026-10-05, verbatim: 「都开单派发」, with the ruling 「全局关闭是总开关」 recorded on the card; landing per the standing order 「开发完整就进队列合并」. Dispatched one at a time within the session's load cap. Where the card withholds detail, this session holds it and the dispatch carries it privately.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 21835,
      "status": "done",
      "branch": "claude/issue-21835-form-withdrawal-kill-switch",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21864",
      "session": "session_018zT8d8NpiQ1ExhuNd5TxY6",
      "premise_still_valid": true,
      "summary": "Resumed after a container restart. The branch already implemented the ruling: a withdrawal is a kill switch, and layering can only narrow intake. Read time: GET /forms/:slug and POST /forms/:slug/submit share one resolver (resolveFormBySlug). When an organization is resolved, it reads the env-wide view layer beside the organization's, and a candidate is served only when no layer withdraws it (anonymousFormIntakeWithdrawnIn, new in metadata-core; the layers are ANDed). Write time: an org-scoped view save that would re-open a slug the env-wide layer withdrew is refused with 403 NOT_OVERRIDABLE and a userMessage naming the remedy. Saves that keep the form withdrawn, or leave intake unchanged, are still accepted. I found no gap, so I added no code. I merged origin/main (5 commits, clean, protocol.ts auto-merged), rebuilt the closure, ran the suites, the ablation and the dogfoods, and opened draft PR #21864 assigned to hotlong. The PR says the bug is a regression introduced after 17.6.0 (with #21420) and should land before 17.7.0. The #21420 org-only intake, the #21473 anchors and the #21566 field allowlist keep their behaviour: the full rest and metadata-protocol suites are green. Note for the seat: the claim's file surface omits packages/metadata-core/src, which the branch edits (it holds the shared predicate). Please add it to the claim.",
      "tests": "All at head 3730a51e5d. Build: pnpm --filter '@objectstack/rest...' --filter '@objectstack/metadata-protocol...' build, lock VERDICT command-exit 0. metadata-core test: 18 files / 394 tests passed, VERDICT command-exit 0. metadata-protocol test: 214 passed, 3 skipped files; 27749 tests passed, 19 skipped; VERDICT command-exit 0. rest test: 260 files passed; 4905 tests passed, 326 skipped; REST-EXIT=0. An earlier rest run showed 21 red files with 'Cannot read properties of undefined (reading fields)'. That was not this diff: platform-objects dist had been zeroed by a concurrent unlocked check:type-check-debt re-measure that I started and cut short with a 240s timeout. I rebuilt platform-objects, and the rerun is the green one above. Typecheck for rest, metadata-protocol and metadata-core: Done for all three (rest includes check:test-typecheck OK). Dogfood: rebuilt '@objectstack/dogfood^...', then showcase-public-form-withdrawal-layers.dogfood.test.ts passed 5/5 (VERDICT command-exit 0). The five sibling public-form dogfoods (read-back-masking, withdrawal-walled, walled-intake, withdrawal, showcase-public-form) passed 20/20 across 5 files. The first dogfood attempt failed with a DTS build error in plugin-sharing, caused by the same concurrent dist rewrite; the clean rerun is the one quoted. Ablation (committed state, trap restore): scripts/ablation-replace.mjs replaced the read-time layer check in rest-server.ts (anchor x1 -> x0, blob 3f30cb5229 -> 4533e4508f). Then src/public-form-withdrawal.test.ts went red: 5 failed / 18 passed. The red tests were every env-wide-withdrawn re-open case plus the cross-view slug withdrawal. The organization-withdrawn case stayed green, as expected, because the org read alone sees it. Restore: blob == HEAD 3f30cb5229, git diff HEAD empty, then 23/23 passed. No build was needed because the test imports ./rest-server from src. Gates: node scripts/pm/dispatch-gates.mjs --commands derived 70 commands, and I ran all 70. 67 exited 0 on the first pass. query-options-erasure and type-check-debt hit my 240s cap (exit 124) and exited 0 when rerun without the cap. check:dual-build-cjs-loads gave PREREQUISITE NOT MET (exit 3; packages outside the closure have no dist), so it is NOT MEASURED: dual-build-cjs-loads, reason: whole-tree build absent. Ran with nul-bytes, engine-double-contract, published-files, dts-closure, adr-0087-registration and changeset gates all green. --ran reconciliation: 70 derived, 70 run, 0 UNRUN. CI-owned (NOT MEASURED locally): repo-wide lint, the workspace type-check lanes, and the Test Core, Dogfood, Build Core and Temporal shards.",
      "mcp_calls": "0",
      "api_writes": "3 relay dispatches via scripts/pm/fleet-write (fleet-write.yml workflow_dispatch): pr_create POST /repos/objectstack-ai/objectstack/pulls (draft, #21864, read-back 3338/3338 bytes identical); label-write --assign hotlong on #21864 (POST /issues/21864/assignees, read-back MATCHES); os-dev-report comment POST /issues/21835/comments. Plus one git push (merge commit afb150b5bc..3730a51e5d). The single-card REST reads (issue 21835 comments) were reads only.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none (承接者:无) · noted, not filed: check:type-check-debt (its --re-measure built-closure step) appears to rewrite package dist/ while it runs and is not under os-verify-lock. When it ran beside a locked build or test in the same worktree, the platform-objects dist was left as zero-byte files, or rewritten in the middle of a dependent DTS build. This is inferred from mtimes matching the gate's run window, not proven by a probe. Dedupe words: type-check-debt re-measure dist rewrite, unlocked gate build race, zero-byte dist"
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim correction (PM seat), superseding the claim's Clause-②: no and file surface:

    Clause-②: yes (widening)

    File surface: packages/rest/src, packages/metadata-protocol/src, packages/metadata-core/src, packages/qa/dogfood/test, .changeset/

    PR #21864 adds two public exports to @objectstack/metadata-core (the shared withdrawal judgement both doors and the save path read); its changeset declares minor for that package and patch for the others. A contract-tier review record is owed on the PR head before landing.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Rulings recorded by the PM seat from the maintainer's answers in Claude Code session session_018zT8d8NpiQ1ExhuNd5TxY6, 2026-10-05, refining the kill-switch ruling above:

    • Ruled: a withdrawal closes the same form (the same view across its metadata layers), not every view that happens to share its public slug; two different views publishing the same slug do not close each other (a slug collision is a separate concern). Chosen option 「只关同一个表单」.
    • Ruled: only an explicit withdrawal closes — the link kept with enabled: false or allowAnonymous: false. Removing the sharing block, clearing the public link or deleting the view at a layer is not a withdrawal; the docs say so. Chosen option 「不算,写进文档」.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 21835,
      "status": "done",
      "branch": "claude/issue-21835-form-withdrawal-kill-switch",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21864",
      "session": "session_018zT8d8NpiQ1ExhuNd5TxY6",
      "premise_still_valid": true,
      "summary": "Applied the two new rulings and the security-review fixes at head e8778acb96. Final design: (1) Identity. A withdrawal closes only the same form: the same view (by name), at the same slot (nested form, the same formViews key, or the flattened config), across its metadata layers. Other views that share the public slug, whether in the same read or in another layer, never close each other. The anonymous doors now read only the env-wide layer beneath an organization read; the found-in read is no longer a layer, because under identity matching it holds only the open view itself. (2) Explicit only. A layer withdraws a form when its body of the same view, at the same slot, names the same slug in publicLink and does not open it (enabled or allowAnonymous not true, so a raw body and its schema-parsed defaults agree). These are not withdrawals: a sharing with no link (raw, or parsed with false defaults), a cleared or changed link, a removed sharing block, or no body of the view at that layer. (3) Write door. An org-scoped view save in the organization the doors read is refused with 403 NOT_OVERRIDABLE when it would leave open any form the env-wide layer explicitly withdraws. This is judged by the doors' own predicate, with no diff against the org's current slugs, so re-saving an overlay that was open before the env-wide withdrawal is refused. A container-shaped body is first expanded the way the list read serves it (the protocol's runtime view-container expansion), then judged. The message names both remedies: save the overlay withdrawn, or publish env-wide. (4) Public surface. metadata-core now exports one new function, anonymousFormIntakeWithdrawnIn. The other new export from the earlier round, anonymousFormWithdrawnSlugs, was never released and is removed, because nothing reads it under identity matching. The changeset says what counts and what does not count as a withdrawal, and says that the earlier org-overlay-re-publishes-a-package-withdrawn-form behaviour is intentionally reversed. The public data collection docs page gains a Withdraw a public form section with the same rules. Rollback and commit-revert are NOT gated at write time (the changeset says so). The doors still keep such a form closed at read time. See open_questions. Do not edit the PR body yourself: it still describes the earlier round (any-view slug kill, the opening-only write diff, two exports) and needs a PM rewrite from this report.",
      "tests": "All at head e8778acb96, unless noted (source last changed at 1d6bdd5fad; later commits touch only docs, changeset and the dogfood). BUILD: pnpm --workspace-concurrency=2 --filter '@objectstack/rest...' --filter '@objectstack/metadata-protocol...' build, VERDICT command-exit 0. Then the '@objectstack/dogfood^...' closure build, VERDICT command-exit 0. SUITES: metadata-core test: 18 files, 394 tests passed. rest full test: 260 files passed; 4906 passed, 326 skipped; VERDICT 0. metadata-protocol full test: 214 files passed, 3 skipped; 27752 passed, 19 skipped; VERDICT 0. Targeted files: rest public-form-withdrawal + public-form-intake-availability, 2 files, 40 tests passed; metadata-protocol protocol.org-scoped-write-refused, 30 tests passed. TYPECHECK for metadata-core, metadata-protocol, rest (including check:test-typecheck) and dogfood: all Done, VERDICT 0. DOGFOOD, run with maxWorkers=1 so one boot at a time: showcase-public-form-withdrawal-layers 5/5 passed. It now also pins that re-saving the org overlay as it is gets a 403. The five sibling public-form dogfoods passed 20/20 across 5 files. CHANGESET: check-changeset-fixed exit 0; check-changeset-no-major --base origin/main exit 0; check-empty-changeset --base origin/main exit 0; check-adr-0087-registration exit 0. GATES: dispatch-gates --commands derived 95 commands; 94 were run, each with its exit code captured before any pipe. 92 exited 0, including check:nul-bytes, check:doc-anchors, check:doc-authoring, check:docs-single-h1, check:issue-citations, check:published-files, check:test-source-alias and docs-audit check-affected-docs. 2 exited 3 (PREREQUISITE NOT MET, so NOT MEASURED): spec check:skill-examples and check:dual-build-cjs-loads. Both need workspace-wide dist, which this box does not have; declared to CI. NOT MEASURED: check:type-check-debt. Reason: its --re-measure is workspace-wide and rewrote shared dist on this box in an earlier round; declared to CI. dispatch-gates --ran reconciliation: 95 derived, 92 run, 2 NOT-MEASURED, 1 UNRUN (that same family). NOT MEASURED: eslint repo-wide scan, which CI owns. ABLATION, from the committed state with a trap restore: the three source files were set to the BASE blobs (hash-object equal to the BASE blob for each), then the three packages were rebuilt. dist preflight passed: core marker absent, protocol marker absent, the base rest marker present in dist. Results: metadata-core 3 failed / 30 passed (two views sharing a slug; not a withdrawal; parsed linkless sharing). rest 4 failed / 20 passed (two views in one read, under both tenancy org and no-org; another view env-wide; link cleared env-wide). metadata-protocol 4 failed / 26 passed (re-save of an already-open overlay [F3]; container-shaped save [F6]; linkless env-wide sharing [F5]; plus the re-open test, which is red only because its message regex changed with the new wording). Restore: git diff HEAD empty, rebuilt, every marker read back in its restored state.",
      "mcp_calls": "0",
      "api_writes": "1 — POST /repos/objectstack-ai/objectstack/issues/21835/comments (this report, via scripts/pm/fleet-write relay); git push is not a REST write; no PR body edit, no label write this round",
      "open_questions": [
        { "question": "Should rollbackMetaItem and revertCommit refuse an org-scoped restore that would leave a form open that the env-wide layer withdrew?", "options": ["A leave them ungated: the doors already keep the form closed at read time, so the cost is an accepted write that is never honoured", "B gate them: they need the restored body before repo.restoreVersion writes, which today happens inside the repository (deriveRestoredBody), so this needs a history read up front"], "recommendation": "A for this PR, with B as a follow-up if wanted: there is no exposure (read-time enforcement holds), and B touches the restore path's write ordering, which is beyond this card's surface" }
      ],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted, not filed — when two installed apps each ship a view publishing the same public slug, the doors serve whichever open candidate the list read returns first. The maintainer named slug collision a separate concern in the ruling; it is recorded here as an observation only. dedupe words: public form slug collision, duplicate publicLink, forms slug uniqueness"
      ]
    }

    Generated by Claude Code

  7. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 21835,
      "status": "needs_decision",
      "branch": "claude/issue-21835-form-withdrawal-kill-switch",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21864",
      "session": "session_018zT8d8NpiQ1ExhuNd5TxY6",
      "premise_still_valid": true,
      "summary": "Round 3 is pushed at head 95951e19bc. The judgement in metadata-core changed as follows. A withdrawal is now explicit only: the sharing keeps a non-empty publicLink, and enabled or allowAnonymous is strictly === false, judged on the body as stored. An absent switch is not a withdrawal. A withdrawn form matches a candidate in a body of the same row name by slot OR by slug, where a slug is compared exactly the way the doors resolve it (trimmed request param against the normalised link, case-sensitive). So a slug change at the same slot, including a case-only variant, stays closed. A sibling that differs in both slot and slug stays independent. Different rows that share a slug never close each other. WRITE DOOR, item 1 fixed here: an org-scoped save or draft promotion is judged twice. (a) The expanded body is checked against the env-wide list, name-anchored, the way the doors read it. (b) The raw body is checked against the env-wide raw body of the SAME stored row. That raw body is read through the canonical overlay-row read and conversion chain, falling back to the package artifact of that name when there is no env DB row. This closes the four escape routes at save and publish: a key rename, a form.name rename, a slot move, and a listViews collision rename. Item 5: promotion now passes packageId, the request's own, or else the draft row's package_id. ANONYMOUS DOORS, item 1 NOT fixed (needs_decision): the doors receive only list-read items, and expanded items carry no source-row provenance, so door identity is still the expanded item name. Under the new rule the doors do close a slug change or case change on the same item name, and do honour explicit-only. But an org overlay of a container row that renames its key or moves its form, and that existed before the env-wide withdrawal (or arrived through ungated rollback or revert), is still served. Carrying provenance means a new served key on every expanded view item, or a new protocol query the doors call. Either is a cross-package read-contract choice (see open_questions), so I did not guess. Door tests for those four routes are therefore not added. Item 3: DB rows are judged raw, which is measured: saveMetaItem persists the body verbatim, and conversion does not fill defaults. Code-authored artifacts are schema-parsed by defineView, so explicit cannot be told from absent there (see open_questions). Item 4: not implemented (see open_questions). The changeset and docs section were rewritten for the identity rule and the explicit-false rule. The reversed behaviour is now described as existing only between 17.6.0 and this fix, never shipped in a release. Export set unchanged: anonymousFormIntakeWithdrawnIn only. Clause-② stays yes (widening), metadata-core minor. Note: the docs and changeset state the row-identity rule as the intended contract. Until the door half lands, it holds at save and publish time but not on the read path for pre-existing overlays. Hold or reword them if you land this before the decision.",
      "tests": "At head 95951e19bc. BUILD: pnpm --workspace-concurrency=2 --filter '@objectstack/rest...' --filter '@objectstack/metadata-protocol...' build, VERDICT command-exit 0. Then the '@objectstack/dogfood^...' closure build at the final head, VERDICT command-exit 0. SUITES: metadata-core: 18 files, 399 tests passed. rest: 260 files passed; 4909 passed, 326 skipped. Both under one lock call, VERDICT command-exit 0. metadata-protocol: 214 files passed, 3 skipped; 27760 passed, 19 skipped; VERDICT command-exit 0. Targeted: core anonymous-form-intake 38/38; rest public-form-withdrawal + intake-availability 43/43; protocol org-scoped-write-refused 38/38. TYPECHECK for metadata-core, metadata-protocol, rest (including check:test-typecheck) and dogfood: all Done, VERDICT command-exit 0. DOGFOOD, maxWorkers=1: 6 public-form dogfood files, 25/25 passed, VERDICT command-exit 0. GATES: dispatch-gates --commands at 95951e19bc derived the same 95-command set as round 2. 94 were run with exit codes captured before any pipe: 92 exited 0, including check:nul-bytes, check-changeset-no-major, check-empty-changeset, check:changeset-gate-self-tests and the docs families. check-changeset-fixed also exited 0. 2 exited 3 and are NOT MEASURED (PREREQUISITE NOT MET: workspace-wide dist absent): spec check:skill-examples and check:dual-build-cjs-loads. NOT MEASURED: check:type-check-debt, whose workspace-wide re-measure rewrites shared dist; declared to CI. --ran reconciliation: 95 derived, 92 run, 2 NOT-MEASURED, 1 UNRUN (that family). NOT MEASURED: repo-wide eslint, which CI owns. ABLATION of the new identity checks, done from the committed state with ablation-replace (anchor hit 1 to 0, blob changed) and a trap restore, rebuilding before each read; dist preflight showed each marker present. Leg 1 disabled the write door's row-anchored judgement. Result: protocol 3 failed / 35 passed (key rename, form.name plus slot move, listViews collision rename). The same-key slug-change cases stay green because the name-anchored expanded judgement also catches them. Leg 2 reverted the core match from slot OR slug to slot AND slug. Result: core 3 failed / 35 passed (key rename, slot move, slug change including case-only); rest 2 failed / 25 passed (both slug-change door tests). Restore: git status --porcelain empty, rebuilt, both markers absent from dist, tree clean.",
      "mcp_calls": "0",
      "api_writes": "1 — POST /repos/objectstack-ai/objectstack/issues/21835/comments (os-dev-report via scripts/pm/fleet-write relay); git push is not a REST write; no PR body edit, no label write",
      "open_questions": [
        { "question": "Item 1, door half: how should the anonymous doors learn which stored row an expanded view item came from, so that door identity is row-anchored like the write door?", "options": ["A a served read decoration on every expanded view item (row name plus slot), stamped by every container expander (objectql engine registration, metadata plugin registration and runtime expansion, protocol runtime expansion, spec manifest assembly) and added to the spec read-decorations list so writes and strict re-parses strip it. This is a public wire-shape change, visible to the console and to every strict re-parse consumer; it touches about 6 packages plus spec", "B a protocol query the doors call (for example, ask the protocol whether a slug served to an organization is withdrawn), which the protocol answers from its raw overlay rows and artifacts. No wire change, but a new rest-to-protocol method contract; other protocol implementations would lack it, and the door then needs a fail-closed or fail-open rule", "C keep the doors name-anchored: the write door already blocks every new escape, and the residue is overlays that predate the withdrawal plus rollback and revert"], "recommendation": "B. It keeps provenance inside the layer that owns the rows and needs no served-key change. If the method is absent, fail closed only on a tenancy-scoped read, mirroring the doors' existing tenancy fail-closed rule. Cost: one protocol method plus rest wiring, roughly this PR's size again." },
        { "question": "Item 3: code-authored artifacts are schema-parsed (defineView parses with ViewSchema, which defaults enabled and allowAnonymous to false), so for a package-shipped form an explicit false cannot be told from an absent switch. Where the artifact is the env-wide layer (no env DB row), a shipped sharing with publicLink and enabled true but no allowAnonymous reads as a withdrawal. How should that case be judged?", "options": ["A treat a parsed artifact false as explicit (current behaviour): fail closed, so an org cannot open a form the package ships closed", "B exclude artifact bodies from the explicit-only judgement: a package can then never withdraw through the kill switch, only by not shipping the form open", "C carry the authored (pre-parse) sharing on the artifact so explicit can be measured: a spec and loader change"], "recommendation": "A for now. It fails closed and matches the package author's likely intent of shipping the form closed. C if explicit-only must hold for artifacts too." },
        { "question": "Item 4: should the package artifact be its own layer beneath the env-wide DB overlay, so that an env-wide save cannot re-open a form the package ships withdrawn?", "options": ["A no: the env-wide definition is the administrator's switch, and shipping a form closed for the admin to enable env-wide is a plausible intended workflow that a kill switch would forbid", "B yes: the doors would need an artifact-only read (no protocol request reaches it today), and the write door would also have to judge env-wide saves against lookupArtifactItem"], "recommendation": "A. B is a product rule change: it forbids enabling a package-shipped closed form, and it needs a new read channel at the doors. If wanted, it belongs in its own card." }
      ],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted, not filed — when two installed apps each ship a view publishing the same public slug, the doors serve whichever open candidate the list read returns first; the maintainer named slug collision a separate concern. dedupe words: public form slug collision, duplicate publicLink, forms slug uniqueness"
      ]
    }

    Generated by Claude Code

  8. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Rulings recorded by the PM seat from the maintainer's answers in Claude Code session session_018zT8d8NpiQ1ExhuNd5TxY6, 2026-10-05. They answer the three open_questions in the round-3 os-dev-report (comment 5996587869):

    • Ruled, door identity: keep the anonymous doors name-anchored and document the limit. The write door already refuses every new escape at save and publish time. What remains is not fixed in this card and is stated as a known limit: an organization overlay that existed before the env-wide withdrawal, or that came back through rollback or revert, can still be served after its key or slot is renamed. Chosen option 「保持现状,写进文档」.
    • Ruled, package-shipped forms: a schema-parsed false on a package artifact counts as an explicit withdrawal. This fails closed, so an organization cannot open a form the package ships closed. Chosen option 「包内的 false 算显式关闭」.
    • Ruled, package as a layer: the package artifact is not a separate layer beneath the env-wide definition. The env-wide definition is the administrator's switch, so an env-wide save may open a form the package ships closed. Chosen option 「不算,环境级是开关」.

    Generated by Claude Code

  9. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 21835,
      "status": "done",
      "branch": "claude/issue-21835-form-withdrawal-kill-switch",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21864",
      "session": "session_018zT8d8NpiQ1ExhuNd5TxY6",
      "premise_still_valid": true,
      "summary": "Round 4 applies the three rulings in comment 6005722623. Merged origin/main (merge commit 035a6e0dae, no conflicts, no regen debt). Rewrote the changeset and the docs section 'Withdraw a public form' to state the rules by door. The save/publish write door judges by the stored row and catches renamed keys, form.name, slot moves and listViews collision renames. The anonymous doors judge by the served item name, by slot or slug, and withdraw only on an explicit false. A package's parsed false is a withdrawal. The env-wide definition may open a package-closed form. The known limit covers overlays stored before the withdrawal and overlays that rollback or revert restore. The changeset keeps Clause-② yes (widening) and metadata-core minor, because the export set did not change this round: anonymousFormIntakeWithdrawnIn is still the only new export. It still says the reversed behaviour existed only between 17.6.0 and this fix. Added pins for rulings 2 and 3, which had none. The JSDoc of anonymousFormIntakeWithdrawnIn and of the explicit-withdrawal helper now says the same thing. That is a comment-only code change; runtime behaviour is unchanged. Head 79b847042d.",
      "tests": "All runs are on head 79b847042d. Targeted suites, run through os-verify-lock (VERDICT command-exit 0) after rebuilding metadata-core: metadata-core src/anonymous-form-intake.test.ts 39/39 passed; metadata-protocol src/protocol.org-scoped-write-refused.test.ts 41/41 passed; rest src/public-form-withdrawal.test.ts + src/public-form-intake-availability.test.ts 2 files, 43/43 passed. Typecheck: metadata-core (tsc --noEmit && tsc -p tsconfig.test.json) and metadata-protocol (tsc --noEmit) both exit 0. A --listFilesOnly count of 1 confirms each changed test file is in its package's tsc program. New pins: (1) metadata-core: a sharing parsed by SharingConfigSchema that keeps its link and has enabled defaulted to false IS a withdrawal, and the same raw body is not. (2) metadata-protocol 'single: a package-shipped form' (3 tests): the parsed artifact carries enabled false; an org-scoped save that opens it is refused with 403 NOT_OVERRIDABLE and nothing is saved; an env-wide save opens it, getMetaItems({type:'view'}) then serves exactly one item of that name with the open body (the layer the doors read), and an org-scoped open save is then accepted. No ablation this round: the pins record current behaviour as ruled. Gates, each exit 0: check:doc-authoring, check:doc-anchors, check:docs-audit-scope, docs-audit/check-affected-docs, docs-audit/check-drift-comment, check-doc-frontmatter, check-doc-route-spelling --advisory, check-docs-section-name, check:docs-single-h1, check:docs-redirects, check:docs-spec-enumerations, check:docs-transcript-drift, lint check:doc-security-posture, lint check:doc-formula-expressions, check-adr-0087-registration --base origin/main ('this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)'), check-changeset-no-major --base origin/main, check-empty-changeset --base origin/main, check:changeset-gate-self-tests, check:pm-changeset-deadline-census, check:nul-bytes, check:engine-double-contract, check:test-source-alias, check:cross-package-test-inputs. Declared narrowing: full-package suites for the three packages, the rest of the 95 derived families, and the dogfood suite were not re-run this round. The code delta is comments and tests only. These are left to CI. NOT MEASURED: full-package test runs, reason: narrowed as declared.",
      "mcp_calls": "0",
      "api_writes": "1 — POST issues/21835/comments (this os-dev-report, through the fleet-write relay). The two git pushes (035a6e0dae, 79b847042d) are not REST writes. No PR body edit and no label write.",
      "open_questions": [],
      "out_of_scope_findings": [],
      "deviations": "The merge commit 035a6e0dae was pushed before any test ran. It was a clean merge of origin/main with no regen debt. The next push came only after all suites and gates were green.",
      "pr_body_edits": [
        "In ## What, replace the bullet starting '**Anonymous doors (`GET /forms/:slug`, `POST /forms/:slug/submit`).**' with: '- **Anonymous doors (`GET /forms/:slug`, `POST /forms/:slug/submit`).** Both use one resolver and judge by the name of the view item they serve. When an organization is resolved, the env-wide view list beneath it is read as well. A form is served only when the env-wide item of the same name does not explicitly withdraw a form in the same slot (nested form, the same `formViews` key, or the flattened config) or with the same slug. Other views that share the public slug never close each other.'",
        "Replace the bullet starting '**What counts as a withdrawal.**' with: '- **What counts as a withdrawal.** A sharing that keeps its `publicLink` and sets `enabled: false` or `allowAnonymous: false`. Only an explicit false counts. Not a withdrawal: an absent switch, a sharing with no link (raw, or schema-parsed), a cleared link, a removed sharing block, or no body of the view at that layer. The public data collection docs page has a \"Withdraw a public form\" section with these rules.'",
        "Replace the bullet starting '**Write door.**' with: '- **Write door (save and publish).** An org-scoped `view` save or draft promotion in the organization the doors read is refused with `403 NOT_OVERRIDABLE` when it would leave open a form the env-wide definition explicitly withdraws. It judges by the stored row: the body is compared with the env-wide body of the row it is keyed by (the active env-wide row, else the package artifact), matched by slot or by slug. So renamed `formViews` keys, `form.name`, slot moves and listViews collision renames are the same form. It is also judged against the env-wide view list the way the doors read it, with container bodies expanded. Re-saving an overlay that was open before the withdrawal is refused. The message names both remedies.'",
        "Insert after the Write door bullet: '- **Package-shipped forms.** A package artifact is part of the env-wide definition, not a separate layer. A schema-parsed `false` on it (the schema defaults `enabled` to false) is an explicit withdrawal and fails closed. The env-wide definition is the administrator's switch, so an env-wide save may open a form the package ships closed.'",
        "Replace the bullet starting '**Not gated at write time:**' with: '- **Known limit (ruled to stay as is).** The doors match by served item name, and the write door runs only on an org-scoped save or publish. An organization overlay stored before the env-wide withdrawal, or restored by rollback or commit revert, can still be served if it keeps the form open under a different key or slot than the env-wide definition. Withdrawing the form in that overlay closes it. Stated in the changeset and the docs.'",
        "In ## Tests, add as the first bullet: '- Round 4 at `79b847042d` (targeted): metadata-core `anonymous-form-intake.test.ts` 39/39, metadata-protocol `protocol.org-scoped-write-refused.test.ts` 41/41, rest `public-form-withdrawal` + `public-form-intake-availability` 43/43. Typecheck green for metadata-core and metadata-protocol. Docs and changeset gates green. New pins: a package parsed `false` is a withdrawal; an env-wide save opens a package-closed form.'",
        "In ## Acceptance notes, replace the first bullet ('Gating rollback / revert at write time is a possible follow-up ... read-time enforcement already holds.') with: '- The known limit above (an overlay stored before the withdrawal, or restored by rollback or revert, with its form under a different key or slot) is accepted per the ruling on #21835. No provenance or new protocol query was added.'",
        "Keep line 3 `Clause-②: yes (widening)` and the `Fixes #21835` first line unchanged."
      ]
    }

    Generated by Claude Code

  10. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 21835,
      "status": "done",
      "branch": "claude/issue-21835-form-withdrawal-kill-switch",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21864",
      "session": "session_018zT8d8NpiQ1ExhuNd5TxY6",
      "premise_still_valid": true,
      "summary": "Round 5, head d8657b5c19 (merged origin/main 3dbd084209 first, merge commit be9d98a32b). (1) MEDIUM: promoteDraftForPublish now reads the gated draft under one package key and promotes under the same key: the stated binding (spelled exactly as repo.promoteDraft receives it), or, when none is stated, the package_id of the draft row resolved once and then stated to both the read and the promotion. The separate view-only draftPackageId lookup is gone (it reuses that key). (2) LOW: anonymousFormIntakeWithdrawnIn skips a layer body when it and the candidate view are both bound to a package and the packages differ. Deviation from the literal instruction (strict equality with absent = undefined on both sides): a body bound to no package is still compared, because the list read serves a package-less row as each package's row of the name, so strict equality would let a package-bound overlay escape a package-less env-wide withdrawal. The write door now carries the saved row's stated package as _packageId on the item it judges and on the row-anchor body; the doors already pass list items that carry _packageId. (3) Doc and changeset fallback, not the code fix: they now say the parsed default applies to schema-parsed artifacts only (strict defineStack) and that a strict:false or hand-built artifact is judged as written. Reason in open_questions. CI was red on f37e09c956 (objectql Test Core 4/6). Cause: my fix 1 adds an engine.findOne read on a publish with no stated package, and one objectql test double built the protocol with an empty engine. I fixed the double (d8657b5c19) and objectql is green locally. The PR body was not edited; the exact edits are in open_questions[1].",
      "tests": "All at d8657b5c19 unless noted. metadata-core: 18 files, 411 passed. metadata-protocol: 216 passed + 3 skipped files, 27938 passed, 19 skipped. rest: 260 files, 4911 passed, 326 skipped. objectql: 374 files, 7464 passed. Before the double fix, objectql had 3 failures, all 'this.engine.findOne is not a function' in protocol-publish-package-drafts.test.ts seed self-apply. metadata plugin.test.ts 17/17. runtime: 12 publish-related unit files, 488 passed. Typecheck green: metadata-core (tsc plus tsconfig.test.json), metadata-protocol (tsconfig include src/**/* covers tests), rest (tsc plus check:test-typecheck OK), objectql (check:test-typecheck OK, debt held). Gates: dispatch-gates --commands derived 97 at d8657b5c19 (95 at 6d6f894d8c, plus check-engine-split-ratio x2 added by the objectql test path). I ran all 97, every one exit 0, including check:docs, check-affected-docs, the changeset gates, check:nul-bytes, check:type-check-debt and check:dual-build-cjs-loads. check:skill-examples first exited 3 (PREREQUISITE NOT MET, client-react dist missing); after building client-react it was green. dispatch-gates --ran: 97 derived, 97 run, 0 NOT-MEASURED, 0 UNRUN. New pins: metadata-protocol 'walled: two packages hold a draft of the same view in one organization' (promoting B judges B: refused, nothing active; control: promoting A promotes A, B pending). metadata-core 'the package is part of the row' x3 (cross-package closes nothing; same package closes, beside another package's open body; a package-less body stands in on either side). rest doors x2 (another package's withdrawal leaves this package's form served 200/201; the same package's withdrawal closes it 404 FORM_NOT_FOUND). Ablation 1 (ablation-replace.mjs, anchor 1 to 0, blob 00561e891b80 to 061c6a13868f): removing the package key from the draftForGate read failed 'promoting package B judges B's draft' (1 failed, 1 passed); restored blob == HEAD 00561e891b80, git diff HEAD empty. Ablation 2 (anchor 1 to 0, blob 228bd61aa216 to ae21ca6f14a7): deleting the package comparison failed 'another package's withdrawal of the same name closes nothing' (1 failed, 3 passed); restored to HEAD, diff empty. Both read src (protocol test imports ./protocol.js; core test imports the module relatively), so no dist rebuild leg applies. NOT MEASURED: dogfood showcase suite (boots apps), reason: CI-owned; rest-door ablation of fix 2, reason: rest resolves metadata-core through dist and the core-level ablation was taken instead.",
      "mcp_calls": "0",
      "api_writes": "2: git push x2 to the PR branch (f37e09c956, then d8657b5c19); 1 REST write via fleet-write relay: POST /repos/objectstack-ai/objectstack/issues/21835/comments (this report). No label writes (the label set is unchanged), no PR body PATCH.",
      "open_questions": [
        {
          "question": "Fix 3: should non-strict and hand-built package artifacts get the schema's sharing defaults, so a shipped false is a withdrawal on every load path?",
          "options": [
            "A: as landed. The changeset and docs say the parsed default applies to schema-parsed artifacts (strict defineStack); strict:false and hand-built artifacts are judged as written. The doors and the write door agree on that today.",
            "B: normalise at load in objectql, registerMetadataCollections for views: entries, defaulting only an absent sharing.enabled to the schema default, with no alias rewrite. Cost: objectql joins the PR. It covers only that seam; plugin.ts loader and metadata-facade.register are separate paths.",
            "C: normalise at judge time. Not workable: the served env-wide list cannot tell an artifact from an overlay (overlays inherit _packageId/_provenance through mergeArtifactProtection, and the registry also holds hydrated overlay bodies), and normalising stored overlays would contradict the explicit-false ruling."
          ],
          "recommendation": "A for this PR, with B as a follow-up card if the maintainer wants it. Reasons: a full SharingConfigSchema parse would canonicalise alias spellings (public, isPublic and the like map to enabled), which could OPEN forms that are closed today. A defaults-only fix needs a seam in objectql that holds only artifacts, and that seam has three entry points. Today the doors and the write door judge the same bodies the same way, so A is consistent, and now it is also accurately documented."
        },
        {
          "question": "Exact PR-body edits for the seat to apply (I did not PATCH the body).",
          "options": [
            "EDIT 1, replace the bullet that starts '- **Package-shipped forms.**' with: - **Package-shipped forms.** A package artifact is part of the env-wide definition, not a separate layer. A package artifact parsed by the stack schema (strict `defineStack`, the default) carries the schema's default `enabled: false`, so a shipped form that keeps its link without switching `enabled` on is an explicit withdrawal and fails closed. An artifact loaded without that parse (`defineStack(..., { strict: false })` or a hand-built manifest) is judged as written: a switch it omits is absent, which is not a withdrawal. The env-wide definition is the administrator's switch, so an env-wide save may open a form the package ships closed.",
            "EDIT 2, insert directly after that bullet: - **Packages and names.** A package is part of a row's identity (ADR-0048). One package's withdrawal of a view name closes only that package's form of the name. A definition bound to no package stands in for every package's row of its name, so its withdrawal applies to all of them. A publish judges the draft it promotes under the same package key (the stated one, else the resolved draft row's own), so with two packages holding a draft of the same view in one organization, each draft is judged on its own publish.",
            "EDIT 3, in ## Tests replace the line 'The first bullet is round 4; the bullets after it were measured at `e8778acb96` (round 2):' with 'The first bullet is round 5, the second round 4; the bullets after them were measured at `e8778acb96` (round 2):' and insert above the Round 4 bullet: - Round 5 at `d8657b5c19`: metadata-core 18 files, 411 passed; metadata-protocol 216 files (3 skipped), 27938 passed, 19 skipped; rest 260 files, 4911 passed, 326 skipped; objectql 374 files, 7464 passed. Typecheck green for metadata-core, metadata-protocol, rest and objectql, test layers included. 97 of 97 derived gates green, reconciled with `dispatch-gates --ran`. New pins: two packages' drafts of one view in one organization are each judged on their own publish; one package's withdrawal of a name leaves another package's form of it open and closes its own (metadata-core and both doors). Ablation: removing the package key from the publish gate's draft read turned the two-package pin red, and removing the package comparison turned the cross-package pin red; both restored to HEAD (`git diff HEAD` empty).",
            "EDIT 4, in ## Acceptance notes append: - A package artifact loaded without the stack schema's parse (`strict: false`, a hand-built manifest) is judged as written; giving every load path the schema's sharing defaults is left as a possible follow-up (see the round 5 report on #21835). - An objectql test double now answers the publish gate's draft-row read (`protocol-publish-package-drafts.test.ts`); that is test-only."
          ],
          "recommendation": "Apply all four edits as written. The current Package-shipped bullet states the parsed-default rule for every package artifact, which overstates it."
        }
      ],
      "out_of_scope_findings": [
        "carrier: none (承接者:无) · noted, not filed: one publish with no stated package now makes one extra sys_metadata findOne to resolve the draft row's binding. That is the price of judging and promoting the same row. publishPackageDrafts always states the package and does not pay it."
      ]
    }

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    Correction to the claim's file surface (it extends the earlier correction, 5993499550). Round 5 also edits packages/objectql/src/protocol-publish-package-drafts.test.ts: a test double now answers the publish gate's draft-row read. The change is test-only, and nothing published in objectql changes. No other part of the claim changes.


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 21835,
      "status": "done",
      "branch": "claude/issue-21835-form-withdrawal-kill-switch",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21864",
      "session": "session_018zT8d8NpiQ1ExhuNd5TxY6",
      "premise_still_valid": true,
      "summary": "Round 6, head 7882eef683, per the maintainer ruling of 2026-10-06 (verbatim: 「撤掉跨包那一改,合并」). Merged origin/main first (merge commit 46d08189a7, main at 9dce635337). (1) 4d5f6c4e61 removes the cross-package skip from anonymousFormIntakeWithdrawnIn together with the anonymousFormPackageOf helper, and removes the write-door code that only fed it (the package stamp on the judged item and on the row-anchor body in anonymousFormIntakeReopenRefusal); that function is now byte-equal to its pre-round-5 form. A withdrawal of a view name again applies to every same-name body whatever its package (fails closed: may over-close, never under-close). (2) Kept: round 5 fix 1 (the publish gate reads and promotes the draft under one package key) and its two pins, the objectql test double, and the schema-parsed-artifact wording in docs, changeset and source comment. (3) Pins inverted: metadata-core and rest now assert another package's withdrawal of the same name closes this package's form too (rest gained a control where every package's body is open and the form is served). New metadata-protocol pin: with two packages shipping the same view name, a row-anchored rename by a package-bound org save is refused and nothing is saved, plus a control where the same save keeping the form withdrawn is accepted. (4) af60aff7ca: the changeset's and docs page's package rule is replaced by the known limit (a withdrawal of a view name closes that name in every package, may over-close; per-package precision tracked in #21934); the changeset keeps the publish-under-one-package-key sentence and Clause-②: yes (widening) with metadata-core minor, export set unchanged. (5) 7882eef683, separate commit per the coordinator: after the merge brought #21919 in, dispatch-gates --self-test failed on exactly the mkdtempSync-base case (1 of 1976); the three files packages/qa/dogfood/test/per-file-cwd.setup.ts, packages/qa/dogfood/test/per-file-cwd.global-setup.ts and packages/qa/dogfood/vitest.config.ts are ported unchanged from refs/pull/21935/head at 2edc5d59d4; rerun on a non-symlinked node_modules: 1976 cases pass. PR body not edited; exact edits below in open_questions[0].",
      "tests": "Build closure (metadata-core, metadata-protocol and dependents, rest and objectql dependencies) VERDICT command-exit 0. Suites at 4d5f6c4e61 (later commits touch docs, changeset and the 3 ported dogfood files only): metadata-core 18 files, 411 passed; metadata-protocol 216 files passed, 3 skipped, 27940 passed, 19 skipped; rest 260 files, 4912 passed, 326 skipped; objectql 375 files, 7469 passed; each VERDICT command-exit 0. Typecheck (metadata-core tsc + tsconfig.test.json; metadata-protocol tsc, whose --listFiles includes protocol.org-scoped-write-refused.test.ts; rest and objectql tsc + check:test-typecheck OK) VERDICT command-exit 0. Ablation (one-shot, trap-guarded script, run under the lock): the two edited sources replaced with their round-5 blobs from d8657b5c19 (git hash-object equal to those blobs, markers anonymousFormPackageOf 0 to 1 and the package stamp 0 to 1 on disk), metadata-core and metadata-protocol rebuilt, ablation-dist-preflight proved both markers present in dist/; results: metadata-protocol org-scoped-write-refused 1 failed of 45 (the new two-package row-anchored rename pin: promise resolved success instead of rejecting), metadata-core anonymous-form-intake 1 failed of 42 (the inverted cross-package pin), rest public-form-withdrawal 1 failed of 30 (the inverted cross-package pin). Restore: git checkout HEAD on both paths, git diff HEAD empty, hashes equal the HEAD blobs, rebuilt, preflight --absent exit 0 for both markers with the tree clean, metadata-protocol file 45 of 45 green. Gates at 7882eef683: node scripts/pm/dispatch-gates.mjs --commands derived 97 families (unchanged set after the port), 97 of 97 exit 0 including the changeset gates (check-changeset-no-major, check-empty-changeset, check:changeset-gate-self-tests) and docs gates (check:doc-authoring, check:doc-anchors, check-affected-docs, spec check:docs and the rest); check:skill-examples and check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET, unbuilt workspace), a workspace turbo build was run (first attempt failed once on plugin-webhooks TS7016 while service-messaging declarations were being emitted; the retry 72 of 72 successful) and both then exited 0. dispatch-gates --repo objectstack-ai/objectstack --ran: 97 derived, 97 run, 0 NOT MEASURED, 0 UNRUN (the record carries no exit codes, so the zero is the runner's claim; the per-command exits are in the local results file, all 0). dispatch-gates --self-test: 1976 cases pass after the port (1 failed before it, the mkdtempSync-base case). Dogfood suite not run locally: the three ported files come unchanged from the open fix PR and are declared to CI. Lint: not run (CI-owned).",
      "mcp_calls": "0",
      "api_writes": "1 — the os-dev-report comment on #21835 via scripts/pm/with-fleet.sh --via dispatch (fleet-write relay, repository_dispatch, comment op). Not REST: two git push of the branch (46d08189a7, then 7882eef683). Reads only: GET /repos/objectstack-ai/objectstack/pulls/21864.",
      "open_questions": [
        {
          "question": "Exact PR-body edits for #21864 (seat to apply; dev did not edit the body). EDIT 1, under ## What: replace the whole bullet that begins '- **Packages and names.** A package is part of a row's identity (ADR-0048).' with: '- **Known limit: packages and names.** A withdrawal of a view name closes that name in every package: when two packages ship a view of the same name, one package's withdrawal also closes the other package's form of that name. It may over-close, never under-close. Per-package precision is tracked in #21934. A publish judges the draft it promotes under the same package key (the stated one, else the resolved draft row's own), so with two packages holding a draft of the same view in one organization, each draft is judged on its own publish.' EDIT 2, under ## Tests: replace the lead sentence 'The first bullet is round 5, the second round 4; the bullets after them were measured at `e8778acb96` (round 2):' with 'The first bullet is round 6, the second round 5, the third round 4; the bullets after them were measured at `e8778acb96` (round 2):'. EDIT 3, insert this bullet directly above the bullet that begins '- Round 5 at `d8657b5c19`': '- Round 6 at `7882eef683` (merged origin/main `9dce635337`, merge commit `46d08189a7`): metadata-core 18 files, 411 passed; metadata-protocol 216 files (3 skipped), 27940 passed, 19 skipped; rest 260 files, 4912 passed, 326 skipped; objectql 375 files, 7469 passed (suites at `4d5f6c4e61`; the later commits touch docs, the changeset and three ported dogfood files only). Typecheck green for metadata-core, metadata-protocol, rest and objectql, test layers included. 97 of 97 derived gates green at `7882eef683`, reconciled with `dispatch-gates --ran`; `dispatch-gates --self-test` 1976 cases pass. The cross-package skip of round 5 is removed per the ruling, so a withdrawal of a view name closes it in every package again. Pins: another package's withdrawal of the same name closes this package's form too (metadata-core and the doors); with two packages shipping the same view name, a row-anchored rename by a package-bound org save is refused (metadata-protocol), with a withdrawn-save control. Ablation: the two edited sources set back to their round-5 blobs and rebuilt, markers proved in `dist/`: 1 red in each of metadata-protocol, metadata-core and rest; restored to HEAD (`git diff HEAD` empty), rebuilt, markers proved absent.' EDIT 4, in the Round 5 bullet replace 'one package's withdrawal of a name leaves another package's form of it open and closes its own (metadata-core and both doors)' with 'one package's withdrawal of a name closes its own form (metadata-core and both doors; the cross-package half was inverted in round 6)'. EDIT 5, under ## Acceptance notes append two bullets: '- Maintainer ruling, 2026-10-06: 「撤掉跨包那一改,合并」. The cross-package skip is removed; per-package precision is tracked in #21934.' and '- This branch carries three dogfood files ported unchanged from #21935 (`packages/qa/dogfood/test/per-file-cwd.setup.ts`, `packages/qa/dogfood/test/per-file-cwd.global-setup.ts`, `packages/qa/dogfood/vitest.config.ts`) so the dispatch-gates self-test is green here; they merge away once #21935 lands.'",
          "options": [
            "A: the seat applies the five edits as written",
            "B: leave the body and rely on this report"
          ],
          "recommendation": "A, because the body still states the per-package rule the ruling removed, and the review reads the body."
        }
      ],
      "out_of_scope_findings": [
        "carrier: whoever lands #21935 · the three dogfood files on this branch equal that PR's head 2edc5d59d4; if #21935 changes before landing, this branch's merge of main resolves them toward main · noted, not filed",
        "carrier: 承接者:无 · a cold workspace turbo build failed once on plugin-webhooks TS7016 (service-messaging declarations not yet present), the immediate retry succeeded; one occurrence, not reproduced · noted, not filed"
      ]
    }

    Generated by Claude Code

  13. added 2 commits that reference this issue on Oct 7, 2026
    3c7785d
    2a22177
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:servicespriority:p1High: required for production / M2security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions