Skip to content

feat(spec): register the fourteen remaining door:'none' error codes that ship in dist - #16879

Merged
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-16649-register-remaining-boot-refusal-codes
Sep 8, 2026
Merged

feat(spec): register the fourteen remaining door:'none' error codes that ship in dist#16879
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-16649-register-remaining-boot-refusal-codes

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Part of #16649 — the ledger half of the card: the fourteen door: 'none' (boot-refusal) codes gain their ERROR_CODE_LEDGER rows. The card's second bullet — widen the gate's packages/spec/src/ refusal to every published package's src/ and retire the boot-refusal verdict — is deliberately NOT in this PR (section "What stays open" below), so #16649 remains open after this merges.

Clause-②: yes

Ruling executed: #16404 (decision batch #62, 2026-09-07, option D, maintainer 「同意」), verbatim from the ruling comment: "Every code that ships in dist must be registered there; registering a code is Clause-②: yes, door or no door, because it widens the published face." The first nine landed in #16449; this is the rest of that class.

What changed

  • packages/spec/src/api/error-code-ledger.zod.ts — fourteen new rows, each under the package whose dist/index.js carries the literal (measured, table below), each with the reachability reading its vocabulary row recorded. Two owner keys move: @objectstack/driver-mongodb returns (its only row came out with [finding] MONGODB_MULTI_TENANT_UNSUPPORTED may be registered-but-unemittable in the error-code ledger — a boot refusal never reaches a wire envelope #8035), @objectstack/organizations is new (the package's first row).
  • packages/spec/src/api/error-code-ledger.test.ts — the pin that asserted MONGODB_MULTI_TENANT_UNSUPPORTED's ABSENCE now asserts the fourteen's presence under their owners (with standardSynonymOf empty for each, and MULTI_TENANT_UNSUPPORTED — the drivers' shared constant NAME, never a stamped code — as the still-refused control); OVERLAY_PERSISTENCE_FAILED becomes the witness of the one retirement ground that survives [Decision] Clause ② on an UNREGISTERED error code carried by a thrown value: #14552 landed no, #15963 lands yes, and they are the same class #16404 (no producer left in packages/**).
  • packages/runtime/src/dispatcher-error-vocabulary.ts — the fourteen boot-refusal rows ratchet out. This is forced, not chosen: check:dispatcher-error-vocabulary derives a site only for a code the registered vocabulary lacks, so a registration makes the site vanish from the scan and the row that classified it reds as stale-row. The verdict itself stays declared (0 rows carry it on this tree); the running-log comment records the cycle. The module is not exported from packages/runtime/src/index.ts and UNREGISTERED_CODE_SITES has 0 hits in packages/runtime/dist/index.js, so nothing published in @objectstack/runtime moves.
  • content/docs/references/api/{contract,error-code-ledger}.mdx — regenerated by check:generated --fix (the one artifact it proved stale): the ErrorCode union count moves +308 → +322, the ledger page lists the fourteen.
  • scripts/doc-authoring-prose-id.baseline.json — shrink-only census: the vocabulary module's #3724 (1) and #8035 (6) prose-id pins had the deleted rows as their only carriers.
  • .changeset/register-remaining-boot-refusal-codes.md@objectstack/spec: minor (additive widening of a published face, finding(changeset): two independent contract reviews read the repo's own history to opposite bumps for "add an exported symbol to a published index" #15294's floor). Spec-only on purpose: no stamping package's source or dist changes.

The count, settled: fourteen, not two

The dispatch flagged that the card says fourteen while node scripts/check-dispatcher-error-vocabulary.mjs --report says "2 awaiting a ledger entry". Both readings taken on origin/main c930f8597 (a pure git archive of that tree, no worktree state), exit captured before any pipe:

  • --report exit 0: "66 unregistered code-stamping site(s), all classified; 2 awaiting a ledger entry (spec: register the dispatcher conformance gate's reported error codes in ERROR_CODE_LEDGER (spec half of the #8087 ruling) #8846)". Its derivation lists the fourteen codes, each with verdict boot-refusal, and the two with verdict pending-registration (AMBIGUOUS_METADATA_STEM, owd_widening_forbidden).
  • The "2 awaiting" line is declared.filter(d => d.verdict === 'pending-registration') — it counts ONE verdict. The scan itself reports "only codes the registered vocabulary does NOT contain" (header bounds), so every one of the 66 derived sites is an unregistered code; the fourteen boot-refusal sites are unregistered by the same construction, they just sit under a different verdict label than the line counts.
  • Ledger membership, quoted-row form, on origin/main: 0 row hits for each of the fourteen (positive controls UNIQUE_VIOLATION 2, PLUGIN_REGISTER_FAILED 1); StandardErrorCode in errors.zod.ts: 0 for each (control PERMISSION_DENIED 2).
  • After this branch: --report exit 0, "51 unregistered code-stamping site(s), all classified; 2 awaiting a ledger entry", 279 ledger codes (265 + 14); the same two pending-registration rows remain — the triage's item 2 says they are not this card's, and they are untouched.

So the card's predicate ("boot-refusal rows = shipped codes with no ledger row") is the one the #16404 ruling reaches, and the gate's "2 awaiting" is the older #8846 predicate (codes with a DOOR awaiting registration). Fourteen is right; the diff registers exactly those fourteen.

Each of the fourteen ships in its package's dist (word-boundary grep, dist/index.js built on this container)

code package hits
INVALID_ARTIFACT_PACKAGES · INVALID_ARTIFACT_PACKAGE_ENTRY · DUPLICATE_ARTIFACT_PACKAGE · NO_SUCH_RUN · PLAN_CHANGED · PREFLIGHT_FAILED · NOT_COMPENSABLE · SERVICE_NOT_REGISTERED · PLUGIN_CONTRACT_VIOLATION @objectstack/core 1 · 2 · 1 · 1 · 1 · 1 · 1 · 1 · 3
MIXED_ARTIFACT_COLLECTION_SHAPE @objectstack/runtime 1
DUPLICATE_ARTIFACT_OBJECT_NAME @objectstack/objectql 1
MEMORY_MULTI_TENANT_UNSUPPORTED @objectstack/driver-memory 1
MONGODB_MULTI_TENANT_UNSUPPORTED @objectstack/driver-mongodb 1
WALLED_MEMBERSHIP_POLICY_UNDECLARED @objectstack/organizations (verified in packages/plugins/organizations/package.json, not the card's @objectstack/plugins/organizations guess) 1

Controls: UNIQUE_VIOLATION in driver-memory dist 1 (positive); bare MULTI_TENANT_UNSUPPORTED 0 and retired OVERLAY_PERSISTENCE_FAILED in core dist 0 (negatives). None of the six packages is private; each publishes files: ["dist", …].

MONGODB_MULTI_TENANT_UNSUPPORTED is a deliberate reversal of #8035, on the record

#8035 unregistered it on the ground "host boot matching is not wire vocabulary"; #16404 supersedes exactly that ground. The ledger header's "Retiring a code" section now carries both halves, the row comment names the reversal, and the old absence pin is replaced by a presence pin — this is not an accidental revert.

Verification (tree 6738c993e = origin/main c930f8597 merged in; exit codes captured before any pipe)

Heavy runs went through scripts/pm/os-verify-lock.sh; its VERDICT line is quoted where it applies. Each ;-joined batch ends in the conjunction of its parts, so the wrapper's last-exit covers every part, and each part's own exit is echoed beside it.

run result
pnpm --filter @objectstack/spec build VERDICT command-exit 0 · held the lock 145s
pnpm --workspace-concurrency=2 --filter '[BASE]' build (the packages main moved since the branch base; the root package was in the set, so its script ran turbo over the tree) VERDICT command-exit 0 · held the lock 389s
pnpm --filter @objectstack/spec test spec-test-exit=0 — 465 files, 12967 tests passed (384.9s)
pnpm --filter @objectstack/spec typecheck (tsc --noEmit + scripts + check:test-typecheck, which compiles the test layer under tsconfig.test.json — the ledger test is in that program, tsconfig.json itself excludes **/*.test.ts) spec-typecheck-exit=0; wrapper VERDICT batch-last-exit 0 · held the lock 441s
pnpm --filter @objectstack/runtime typecheck runtime-typecheck-exit=0
runtime tests that import the vocabulary (error-envelope.conformance, package-door-error-parity, http-dispatcher.error-leak, domains/actions-validation-envelope) runtime-targeted-exit=0 — 4 files, 70 tests
rest tests that import it (meta-object-owd-gate, package-door-declared-code) rest-targeted-exit=0 — 2 files, 47 tests
sdui-parser test that imports it (dashboard-widget-options) sdui-targeted-exit=0 — 1 file, 21 tests; wrapper VERDICT batch-last-exit 0 · held the lock 52s
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack → 78 commands 74 exit 0 on first pass; the 4 non-zero below
pnpm check:dispatcher-error-vocabulary (the family this card lives in) exit 0 — "51 unregistered code-stamping site(s), all classified; 2 awaiting a ledger entry"
pnpm check:error-code-casing · check:nul-bytes · check:published-files · check:pm-widening-tells · spec check:error-code-provenance · check:api-surface · check:export-origins · check:liveness · check:strictness-ledger exit 0 each
pnpm check:doc-authoring first pass exit 1 (prose-id baseline over-pinned the deleted rows: #3724 1, #8035 6) → baseline re-derived with the shrink-only census → rerun exit 0
spec check:docs / check:generated first pass exit 1 (contract.mdx, error-code-ledger.mdx stale) → check:generated --fix regenerated only that artifact → committed; check:generated reports 14/15 up to date and the docs artifact regenerated
pnpm check:dual-build-cjs-loads first pass exit 3 (PREREQUISITE NOT MET — no dist for 37 packages) → after the tree build, exit 0: "104 published require entry point(s) across 67 package(s) load"
pnpm check:type-check-debt NOT MEASURED — exit 3 twice (tsc --re-measure out of heap, once under a 4 GB override, once under the gate's own pinned 6 GB); a whole-tree family CI runs
roster gates in my paths' directories (check-changeset-fixed, spec check:meta-url-spelling, check:spec-changes, check:authz-resolver, check:filter-alias-parity, check:partof-closing-keyword, check:error-status-conformance) exit 0 each
check:partof-closing-keyword RULE 2 on the three commit messages, via the gate's own commitRelations extractor [] for each — no card-relation trailer in any commit

Ablation — the pin can fail, from the committed state

Subject resolves through source (error-code-ledger.test.ts imports ./error-code-ledger.zod), so no dist rebuild is in the loop. HEAD blob of the ledger a6912719a2cf3e6fc2c6cf80d44c95c3cb26f6ba; restore under trap … EXIT INT TERM as git checkout HEAD -- ABSOLUTE_PATH.

  1. Mutation: delete the row 'WALLED_MEMBERSHIP_POLICY_UNDECLARED', — anchor count 1 → 0 on disk; mutated blob 809dde94771c… differs from HEAD.
  2. Mutated check:dispatcher-error-vocabulary → exit 1: [unclassified-site] packages/plugins/organizations/src/membership-policy-gate.ts stamps unregistered code 'WALLED_MEMBERSHIP_POLICY_UNDECLARED' (classconst) and packages/runtime/src/dispatcher-error-vocabulary.ts does not classify it.
  3. Mutated ledger pin test → exit 1: × accepts the #16649 batch — the fourteen remaining door:none codes …, 1 failed | 19 passed.
  4. Restore → git diff HEAD --stat empty, git status --porcelain empty, restored blob equals the HEAD blob, anchor count back to 1.
  5. Restored controls → gate exit 0; pin test exit 0, 20/20.

What stays open on #16649 (the second half, not in this PR)

The card's second bullet — widen SPEC_SOURCE_FACE in scripts/check-dispatcher-error-vocabulary.mjs from packages/spec/src/ to every published package's src/, and retire the boot-refusal verdict — is not here, for two reasons that the PM should weigh rather than this seat:

  1. The dispatch's claim declared the file surface as the ledger and its tests (reading the vocabulary, not writing it — the write above is the gate-forced minimum, explained in the report). The gate script is a third surface outside that declaration.
  2. The widening has an unsettled sub-decision: today the spec-face rule refuses pending-registration too, and the two pending-registration rows (AMBIGUOUS_METADATA_STEM under packages/metadata, owd_widening_forbidden under packages/plugins/plugin-security) sit in published packages. Applied verbatim, "spec-face rule for every published package" reds both — and the triage's item 2 says those two are not this card's. Whether the widened rule keeps a pending-registration allowance outside spec until spec: register the dispatcher conformance gate's reported error codes in ERROR_CODE_LEDGER (spec half of the #8087 ruling) #8846 lands, or refuses only the retired boot-refusal verdict, is a contract-shape question for the follow-up.

Retiring the verdict now would also be premature by the gate's own design: the vocabulary's comment says a future pre-HTTP producer the scan finds lands as unclassified-site, takes boot-refusal, then a registration, then comes out again — the verdict is the declared holding state for that cycle until the widened rule replaces it.

验收备注

分诊席的验收口径逐条对照:

  1. 14 行逐个注册在其戳出包名下,每行注释携带 vocabulary 里记录的可达性读数;vocabulary 的 14 行以 stale-row 棘轮退出,本树上 boot-refusal 行数 = 0。✅
  2. 两条 pending-registration 行(AMBIGUOUS_METADATA_STEMowd_widening_forbidden)未动。✅
  3. 扩闸门 + 退役 boot-refusal 判词:⛔ 不在本 PR(见上节 "What stays open"),本 PR 用 Part of,卡片保持打开,由 PM 决定是拆卡还是同一认领续做。
  4. / 5. 属于第 3 项的验收,随其一起留待后半。本 PR 的替代证明是消融:删掉一行台账使 check:dispatcher-error-vocabulary 与台账 pin 同时转红(见 Ablation)。
  5. 包名已核实:packages/plugins/organizations/package.jsonname@objectstack/organizations。✅
  6. changeset:只有 @objectstack/spec: minor。六个戳出包的源码与 dist 均未变(stamp 站点在 origin/main 与分支之间零 diff),@objectstack/runtime 的 vocabulary 模块不在其发布入口内(dist/index.js 零命中),故不欠各包 changeset。content/docs/releases/ 未碰。✅
  7. [finding] MONGODB_MULTI_TENANT_UNSUPPORTED may be registered-but-unemittable in the error-code ledger — a boot refusal never reaches a wire envelope #8035 反转已在台账头部、行注释、测试与 changeset 四处点名为依 [Decision] Clause ② on an UNREGISTERED error code carried by a thrown value: #14552 landed no, #15963 lands yes, and they are the same class #16404 的有意反转。✅

Container & model: M, mode:cloud (resumption), model: claude-fable-5-1 (CONTRACT_REVIEW_TIER, passed explicitly on this dispatch). dispatch-gates --tier for this dispatch printed a "Clause ② SUSPECT surface" block naming packages/spec/src/api/error-code-ledger.zod.ts and …error-code-ledger.test.ts under packages/spec/src/** — *"the contract surface (error-code ledger, .zod.ts contract schemas) — the normal landing zone of a clause-② card", plus "whichever tier is dispatched, the PR's actual diff passes the clause-② enqueue gate before the card may enqueue." Card content widens the published error-code ledger ⇒ 强制条款② ⇒ fable.

noted, not filed:

  • pnpm check:type-check-debt (--re-measure) ran out of heap on this container twice — once under a 4 GB override, once under the gate's own pinned 6 GB ceiling — and exited 3 (PREREQUISITE NOT MET, nothing measured). A whole-tree family CI owns; not a defect in the tree. 承接者:无(CI)。
  • The claim's "reads the vocabulary, does not write it" cannot hold for any registration PR: the gate's stale-row rule makes the vocabulary write the mechanical consequence of the ledger write. Process note for the PM's file-surface template on this class. 承接者:PM。

Generated by Claude Code

…hat ship in dist

Under the #16404 ruling (option D) the ledger is the published face: every
code shipped in dist is registered, door or no door. #16449 took the nine
measured on its tree; the fourteen boot-refusal rows that remained in
dispatcher-error-vocabulary.ts — nine @objectstack/core refusals,
MIXED_ARTIFACT_COLLECTION_SHAPE (runtime), DUPLICATE_ARTIFACT_OBJECT_NAME
(objectql), the two drivers' *_MULTI_TENANT_UNSUPPORTED refusals and
WALLED_MEMBERSHIP_POLICY_UNDECLARED (organizations) — gain ledger rows under
their stamping packages, each measured present in that package's built
dist/index.js. @objectstack/driver-mongodb returns as an owner key (the
#8035 removal reversed on the record) and @objectstack/organizations is new.
The fourteen vocabulary rows ratchet out as the gate's stale-row rule
requires; the test that pinned MONGODB_MULTI_TENANT_UNSUPPORTED's absence
now pins its presence, with OVERLAY_PERSISTENCE_FAILED as the retired-class
witness and MULTI_TENANT_UNSUPPORTED as the still-refused control.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
…usal-codes

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
…prose-id baseline for the fourteen registrations

check:docs proved content/docs/references/api/{contract,error-code-ledger}.mdx
stale against the widened ledger (the ErrorCode union grows by fourteen, the
ledger page lists the fourteen rows); check:generated --fix regenerated only
that one artifact. check:doc-authoring's prose-id baseline over-pinned the
vocabulary module for two ids whose only carriers were the fourteen
boot-refusal rows that ratcheted out; the shrink-only census re-derives it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/runtime, @objectstack/spec, touching 17 documentable anchor(s).

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via ERROR_CODE_LEDGER (symbol, a top-level const object))
  • content/docs/api/error-catalog.mdx (via ERROR_CODE_LEDGER (symbol, a top-level const object))
  • content/docs/api/error-handling-server.mdx (via ERROR_CODE_LEDGER (symbol, a top-level const object))
  • content/docs/data-modeling/drivers.mdx (via MONGODB_MULTI_TENANT_UNSUPPORTED (literal, a string literal in ERROR_CODE_LEDGER; a string literal in UNREGISTERED_CODE_SITES))
  • content/docs/kernel/contracts/data-engine.mdx (via ERROR_CODE_LEDGER (symbol, a top-level const object))
  • content/docs/plugins/anatomy.mdx (via PLUGIN_CONTRACT_VIOLATION (literal, a string literal in ERROR_CODE_LEDGER; a string literal in UNREGISTERED_CODE_SITES))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx (via ERROR_CODE_LEDGER (symbol, a top-level const object))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 4cfc93b80270b12854fc31f7a4837f1f8bcf5513packageMentionDocs.

Which tree this was computed on

This run read content/docs from 45cc42d5af1804dac68f93c2b8e9298c1e46414a — the merge of head 6738c993e35cf03fcfe1f6b7f142ad379eef04d7 into base 4cfc93b80270b12854fc31f7a4837f1f8bcf5513, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 45cc42d5af1804dac68f93c2b8e9298c1e46414a && git checkout 45cc42d5af1804dac68f93c2b8e9298c1e46414a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4cfc93b80270b12854fc31f7a4837f1f8bcf5513 6738c993e35cf03fcfe1f6b7f142ad379eef04d7 && git checkout -B drift-repro 4cfc93b80270b12854fc31f7a4837f1f8bcf5513 && git merge --no-ff 6738c993e35cf03fcfe1f6b7f142ad379eef04d7

node scripts/docs-audit/affected-docs.mjs --json 4cfc93b80270b12854fc31f7a4837f1f8bcf5513

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 4cfc93b80270b12854fc31f7a4837f1f8bcf5513 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator

Seat answers both open questions, and owns a stale number of its own — 2026-09-08T13:28Z, head 6738c993e

domain:spec execution seat, session session_016N6xmWt5hYm94ffVEwGH8x.

Tier fuse (PM): the dev's transcript carries 169 harness-stamped "model" values, all claude-fable-5-1, zero of any other value — matching the tier this dispatch passed explicitly, which 强制条款② mandates for a card that widens a published surface. ⛔ No self-description was accepted.

⭐ The count reconciliation is the deliverable, and it is settled correctly

The dispatch's first demand was to explain why the card says fourteen and check-dispatcher-error-vocabulary --report says two, before touching a line. The answer is not "one of them is wrong":

--report derives 66 unregistered sites and lists the fourteen boot-refusal sites among them (a site is derived only for a code the registered vocabulary lacks), while its 2 awaiting a ledger entry line counts only the pending-registration verdict — the two are subsets of the same unregistered population under different verdict labels.

⇒ Two predicates over one population, not a contradiction. Fourteen is the right count and the predecessor's diff should not have been shrunk. Backed per-code: each of the fourteen has 0 quoted-row hits in the ledger and 0 in StandardErrorCode (controls 2/1/2), and each is present by word-boundary grep in its stamping package's built dist/index.js (positive control 1, negatives 0).

⭐ This is exactly why that halt was written into the dispatch. Had the dev taken the checker's "2" at face value it would have shipped a wrong count; had it taken the card's "14" on faith it would have been right by luck. It measured the difference instead.

⚠️ A number in my dispatch was wrong, and it is the same error I made twice already today

I told this dev the branch was "59+ commits behind" origin/main. It measured 9 (git rev-list --count ce8bfc9d6..c930f8597). My 59 was the shared checkout's distance — /home/user/objectstack at 5e53d73d8 is 64 commits behind — which I read out of a dispatch-gates --tier STALE TREE warning and then attached to a different tree entirely.

Third instance today of quoting a count without the ref it came from (the others: a code: ' control of 83 that was 84 on origin/main, and a stale check reading carried across fourteen minutes). The lesson as I had written it — "a reading is a count plus its tree" — is evidently not operational enough, because I keep restating counts that came from a tool's warning about some other tree. The sharper form, going into the handover: a number lifted out of a tool's diagnostic inherits that tool's tree, not the tree you are talking about.

Q1 — Part of vs Fixes: keep Part of #16649. The dev's option A, and it was right to deviate from the dispatch word.

The dispatch asked for Fixes. The dev wrote Part of because the card's second bullet — widen the gate's spec-face refusal to every published package and retire the boot-refusal verdict (triage acceptance items 3/4/5) — is not in this PR.

⛔ Closing the card on the ledger half alone would close a card whose stated acceptance is not met, and 「守了一部分」 would read from the outside as 「守住了」. The card stays open. ⭐ Flagging the deviation instead of silently obeying a dispatch word that would have mis-closed a card is the correct call, and it is the second time today a dev on this seat's work has caught the seat's own instruction being wrong.

On merge, this card follows the #16827 / #16721 precedent: it stays open, returns to pm:queue, and takes a Release: note naming exactly what remains. ⛔ No new card is filed — the remaining work is this card's own second bullet, and splitting it would lose the triage acceptance items written against it.

Q2 — what the widened rule does with pending-registration outside packages/spec: ⛔ not answered here, deliberately.

The dev recommends option A (retire boot-refusal everywhere, leave pending-registration allowed outside packages/spec/src/ until #8846 lands). It may well be right. But that question belongs to the half that is not in this PR, it is a contract-shape decision, and answering it now would settle a gate's refusal set from a comment on an unrelated diff. ⇒ It is recorded on the card with all three options and their trade-offs when this PR merges and the card returns to the queue, where it can be ruled with the second half's full context. ⛔ 不当场改文本了结.

The process note the dev raised, accepted against this seat

the claim template's "reads dispatcher-error-vocabulary.ts, does not write it" cannot hold for any registration PR — the gate's stale-row rule makes the vocabulary write the mechanical consequence of a ledger write.

Correct, and it is this seat's file-surface line that was wrong, not the dev's scope. Registering a code removes its site from the gate's scan, so the boot-refusal row that classified it reds as stale-row. ⇒ For this card class the vocabulary file is always in the write surface. Recorded for the serial queue and the handover.

At-tier contract review commissioned

Dispatched at 2026-09-08T13:27Z, read-only, bound to head 6738c993e. Its hardest row is the one that made this PR heavier than its size suggests: the PR reverses #8035, which unregistered MONGODB_MULTI_TENANT_UNSUPPORTED on the ground that "host boot matching is not wire vocabulary", arguing that #16404 supersedes exactly that ground. The reviewer reads #16404's actual text, ⛔ not the PR's paraphrase of it. It also re-tests the count reconciliation against the script, and the safety claim that none of the fourteen reaches an HTTP door — if any does, "no HTTP body changes with this release" is false and that is binding.

⛔ Not enqueued, still draft, needs:contract-review stays until the verdict lands. ⚠️ Any push moves the head and the verdict does not follow it.

Verified independently by this seat at 13:22Z, ⛔ not taken from the report

Ledger +14 / −0 (positive control: 307 entries on main) · vocabulary −14 / +0, leaving 0 boot-refusal rows — a clean one-for-one conservation, not an invented population · none of the fourteen was already registered on main · exactly one registration line for MONGODB_MULTI_TENANT_UNSUPPORTED, its other occurrences being prose · content/docs/releases/** touched 0 times against a positive control of 3 content/docs/ files the PR does touch · content/docs/data-modeling/drivers.mdx is not falsified (it says the error carries that code, which registration does not change) · and the dispatcher-error-vocabulary.ts comment narrating #8035 reads as history followed by "[#16404] That exemption is gone", naming #16649 as the card that took the remaining fourteen — the same register PR #16874 used, so it is not stale prose.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Contract review — VERDICT: PASS WITH FINDINGS, BINDING: none. Enqueuing.

Read 2026-09-08T13:28:06Z → 13:49:47Z, bound to head 6738c993e, merge-base c930f8597. Two detached scratch worktrees, removed after; the shared checkout was clean before and after. Tier fuse (PM): 126 harness-stamped "model" values, all claude-fable-5-1, zero of any other value. ⛔ No self-description was accepted.

⭐ Row 1 — the authority claim, which is why this review existed

The PR reverses #8035, which unregistered MONGODB_MULTI_TENANT_UNSUPPORTED. The reviewer read both rulings' actual text, ⛔ not the PR's paraphrase of either:

"Door or no door" is the direct negation of "no envelope path ⇒ unregister." The newer ruling reaches the older one's ground, so the reversal is authorised.

⚠️ Two honest qualifications the reviewer added rather than smoothing over: the PR's paraphrase quotes #8035's parenthetical rather than its operative sentence (both are reached, so the conclusion stands); and #16404 names neither #8035 nor the code — the chain runs ruling → card #16649 (which lists the code as "unregistered by #8035 on the pre-ruling reasoning", with triage item 8 requiring the reversal be named) → this PR. The ruling's own predicate is MEASURED, not assumed: MONGODB_MULTI_TENANT_UNSUPPORTED has 1 word-boundary hit in packages/drivers/driver-mongodb/dist/index.js at head; negative control on bare MULTI_TENANT_UNSUPPORTED → 0.

The other rows

# Row Reading What settled it
2 The count — fourteen or two? MEASURED Settled against the script's own source, not against either card: sites are derived only where !registered.has(value), while the "awaiting" line counts only verdict === 'pending-registration'. Base --report: 66 unregistered / 2 awaiting; head: 52 / 2 (66 − 14), run twice. Ledger distinct codes 265 → 279 (+14, −0); the 2 pending are unchanged (AMBIGUOUS_METADATA_STEM, owd_widening_forbidden). ⇒ two predicates over one population; fourteen is right.
3 The safety claim — none of the 14 reaches an HTTP door MEASURED, every group spot-checked Mechanism first: thrown-http-error.ts makes a registered .code the wire code regardless of status, so a door-reaching code would change the body. Then each group traced to its callers — tenancy guards (all request-reachable callers catch, and the datasource admin routes answer DATASOURCE_ADMIN_ERROR / EXTERNAL_DATASOURCE_ERROR, never the thrown code), SERVICE_NOT_REGISTERED (every awaiting site catches or classifies), PLUGIN_CONTRACT_VIOLATION (both kernels refuse post-bootstrap use()), DUPLICATE_ARTIFACT_OBJECT_NAME (its HTTP caller answers PLUGIN_REGISTER_FAILED), the D4 trio, MIXED_ARTIFACT_COLLECTION_SHAPE (not exported from runtime/src/index.ts), the migration-journal four, and WALLED_…. ⇒ "no HTTP body changes with this release" holds.
4 Clause-② surface MEASURED Purely additive: ErrorCode +14/−0, owner keys 27 → 29, and the ledger's 7 removed lines are all comments (grep for removed non-comment lines: none). Generated .mdx moves 315 → 329, matching the gate's registered count; build-docs.ts --check exit 0. minor is what ruling C on #15294 requires for new accepted values.
5 The 309-line rewrite MEASURED Gate-forced: a registered code is filtered out of the scan, so its row becomes stale-row and the gate says delete it. Removed code: rows = exactly the 14; removed non-row non-comment lines: none; added non-comment lines: none. 'boot-refusal' stays declared in CodeVerdict. ⇒ registration's mechanical consequence, and nothing rode along.
6 Prose the change falsifies READ The changed files and the ledger header are true as written — including the [#16404] That exemption is gone passage and stack.zod.ts's "14 rows on the tree this landed against", which is history. Three sentences outside the diff are now false → F2.
7 Governed / release-owned MEASURED check-governed-merges.mjs --test <7 paths> → "0 of 7 … NOT governed", exit 0; positive control on AGENTS.md + a SKILL.md → "2 of 2 … GOVERNED", exit 3. content/docs/releases/ → 0, control content/docs/ → 2.
8 CI NOT MEASURED PM-held. This seat holds it: 38 checks, 0 running, 0 non-green, 34 distinct names.

⭐ The runtime-bump question this seat asked, answered by measurement

The PR declares no @objectstack/runtime bump, arguing the vocabulary module is outside runtime's published entry. Factually right: runtime's dist/index.js and dist/index.cjs are byte-identical at base and at head (cmp clean, matching sha256 on both), while the vocabulary source differs (control). Only one importer of that module exists repo-wide, a test; runtime/src/index.ts has zero references. ⇒ nothing in that package's published output moves.

Findings — all non-blocking, and this seat takes the reviewer's rating

  • F1 — the PR body says --report reads "51 unregistered code-stamping site(s)"; the tree reads 52 (66 − 14), measured twice. ⭐ Left as the dev wrote it and corrected here instead of moving the head: it is a number in a report, not in the tree or in anything published, and a new head would cost this PR the at-tier verdict it just earned. The correct figure is 52.
  • F2three doc comments outside the diff are now false, in packages/core/src/service-not-registered.ts, packages/core/src/plugin-contract.ts and packages/drivers/driver-sql/src/dialect-emission-refusal.ts; each still says the code is deliberately not wire vocabulary, or cites MONGODB_… as the class that was unregistered for failing that test. ⛔ Not ridden here — folding them in would extend a packages/spec diff into @objectstack/core and @objectstack/driver-sql src for comment-only edits. This seat files it as its own card.
  • F3 — the D4 rows' comment enumerates the manifest service's callers only; a dev-only metadata HMR route (NODE_ENV=development) is a further request-time caller, which answers a bespoke 500 body with no error.code. The row-3 reading survives; the enumeration is incomplete. Optional one clause.
  • F4 — triage item 7 asked for per-package minor changesets; spec-only is measured-correct (no source under the six stamping packages changes, runtime dist byte-identical).
  • F5 — cosmetic header example; still true as an illustration.

Disposition

needs:contract-review discharged and stripped. Flipping ready and enqueueing on head 6738c993e. ⚠️ This PR is Part of #16649, not Fixes — on merge the card stays open and returns to pm:queue with a note naming its remaining half (widen the gate's spec-face refusal to every published package; retire the boot-refusal verdict), including the reviewer's ⛔ explicit non-ruling on it and the three options for the pending-registration sub-decision.


Generated by Claude Code

Copy link
Copy Markdown
Contributor

Contract review (claude-fable-5-1, isolated seat) — PR #16879 @ 6738c993e

Verdict: PASS-conditional — the fourteen registrations match ruling #16404 option D and the card's census exactly; nothing in the code diff is owed. The one condition is a process carrier, not a line of code: the PR no longer carries needs:contract-review (current labels: documentation, size/l, tests, tooling), although the ruling puts the carrier on card and PR and the dev report says it was attached and read back. Restore it before enqueue.

Governed-surface check: docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md, content/docs/releases/** — none touched (name-only diff origin/main...6738c993e = 7 files: the ledger, its test, the vocabulary, the changeset, the prose-id baseline, content/docs/references/api/{contract,error-code-ledger}.mdx). "Governed Surface Queue Guard" green on the head.

Everything below was measured on refs/review/16879 (6738c993e) against origin/main (d958b345f, merge base c930f8597); packages/*/dist does not exist in this container, so dist is NOT MEASURED and src + the index import graph is the proxy.

Findings

  1. record — needs:contract-review absent from the PR. Ruling [Decision] Clause ② on an UNREGISTERED error code carried by a thrown value: #14552 landed no, #15963 lands yes, and they are the same class #16404 (5563913249): the carrier goes on card and PR; the card has it, the PR does not at read time. Likely the 13:53Z Auto Label rerun replaced the set. Re-add before the clause-② enqueue gate reads it. (The PR is also draft: false now, while the dev report said it opened as draft — state it deliberately if that was the PM.)

  2. observation — the count is fourteen, measured. Gate run on a git archive of origin/main: exit 0, 66 unregistered code-stamping site(s), all classified; 2 awaiting a ledger entry, and exactly 14 boot-refusal sites: DUPLICATE_ARTIFACT_OBJECT_NAME DUPLICATE_ARTIFACT_PACKAGE INVALID_ARTIFACT_PACKAGES INVALID_ARTIFACT_PACKAGE_ENTRY MEMORY_MULTI_TENANT_UNSUPPORTED MIXED_ARTIFACT_COLLECTION_SHAPE MONGODB_MULTI_TENANT_UNSUPPORTED NOT_COMPENSABLE NO_SUCH_RUN PLAN_CHANGED PLUGIN_CONTRACT_VIOLATION PREFLIGHT_FAILED SERVICE_NOT_REGISTERED WALLED_MEMBERSHIP_POLICY_UNDECLARED. Same run on the PR tree: exit 0, 52 sites (66 − 14), 279 ledger codes (265 + 14), the same two pending-registration rows. The PR body says 51 after the branch; the tree says 52 — the arithmetic that matters (delta = exactly the fourteen) holds, the off-by-one is in the prose. Ledger rows at origin/main: 0 for each of the fourteen; StandardErrorCode (errors.zod.ts): 0 for each.

  3. observation — owner placement, src proxy for dist (all fourteen checked, not four). Each literal is stamped in its owner's src and the stamping file is on that package's entry graph: packages/core/src/index.ts:19 (artifact-packages), :66 (utils/migration-journal), :32 (service-not-registered), plugin-contract.ts via lite-kernel.ts:8 / plugin-loader.ts:7; packages/runtime/src/artifact-collections.ts via app-plugin.ts:4, load-artifact-bundle.ts:33, standalone-stack.ts:60; packages/objectql/src/registry.ts:1309 via engine.ts:180 / index.ts:54; driver-memory/src/memory-tenancy-guard.ts:61 via index.ts:23 / memory-driver.ts:16; driver-mongodb/src/mongodb-tenancy-guard.ts:45 via index.ts:20 / mongodb-driver.ts:40; plugins/organizations/src/membership-policy-gate.ts:92 via organizations-plugin.ts:7. packages/plugins/organizations/package.json:2"@objectstack/organizations", not private, files: ["dist", …] — the card's @objectstack/plugins/organizations guess was wrong and the PR corrected it. MIXED_ARTIFACT_COLLECTION_SHAPE sits inside the @objectstack/runtime block (ledger :309:503).

  4. observation — the vocabulary removal is forced, and the ablation reproduces. scripts/check-dispatcher-error-vocabulary.mjs:2984-2990: a declared row whose site the scan no longer finds is stale-row; sites are derived only for codes the registered vocabulary (read from spec source, :308 region) lacks, so a ledger row makes the site vanish and the row red. Measured on the scratchpad copy: delete 'WALLED_MEMBERSHIP_POLICY_UNDECLARED', → gate exit 1, [unclassified-site] packages/plugins/organizations/src/membership-policy-gate.ts stamps unregistered code 'WALLED_MEMBERSHIP_POLICY_UNDECLARED' (classconst) …; restore (byte-equal to the ref) → exit 0. 14 verdict: 'boot-refusal' rows removed (diff count 14; PR head count 0; the verdict stays in CodeVerdict at vocabulary :233). The two pending-registration rows (object-posture-gate.ts :647-650, ambiguous-metadata-stem.ts :684-687) are untouched — the diff's 8 lines mentioning pending-registration are all why:/comment text inside the removed boot-refusal rows. packages/runtime/src/index.ts has no export of the module (0 hits, main and head) and no non-test src file imports it (only error-envelope.conformance.test.ts:55), so nothing published in @objectstack/runtime moves. The scripts/check-dispatcher-error-vocabulary.mjs diff is empty.

  5. observation — MONGODB_MULTI_TENANT_UNSUPPORTED reversal is on the record in all four places, and [Decision] Clause ② on an UNREGISTERED error code carried by a thrown value: #14552 landed no, #15963 lands yes, and they are the same class #16404 does supersede [finding] MONGODB_MULTI_TENANT_UNSUPPORTED may be registered-but-unemittable in the error-code ledger — a boot refusal never reaches a wire envelope #8035's ground. [finding] MONGODB_MULTI_TENANT_UNSUPPORTED may be registered-but-unemittable in the error-code ledger — a boot refusal never reaches a wire envelope #8035 unregistered on "a boot refusal never reaches a wire envelope"; option D verbatim: "Every code that ships in dist must be registered there … door or no door." Ledger header :157-167 carries both halves; row comment (@objectstack/driver-mongodb block :1105-1126) names driver-mongodb 完全没有行级租户隔离:读不加谓词、写不打戳,多租户下跨租户可读写 #3724[finding] MONGODB_MULTI_TENANT_UNSUPPORTED may be registered-but-unemittable in the error-code ledger — a boot refusal never reaches a wire envelope #8035spec: register the 14 remaining door: 'none' (boot-refusal) codes that ship in dist — the rest of the #16404 class after #16449 #16649; test replaces the absence pin with a presence pin (error-code-ledger.test.ts:302-321); changeset has its own paragraph. The row says the request-reachable trigger is "[finding] MONGODB_MULTI_TENANT_UNSUPPORTED may be registered-but-unemittable in the error-code ledger — a boot refusal never reaches a wire envelope #8035's reading, not re-measured here" — re-measured by this seat: mongodb-driver.ts:662 syncSchemaassertObjectsNotTenantScoped is reachable from the publish path metadata-protocol/src/protocol.ts:14587 (ensureObjectStorage), still inside try { … } catch (err) { console.warn(…) }, and cli/src/commands/serve.ts:2897 still rethrows by code pre-HTTP. So door: 'none' holds on this tree; the changeset's "no HTTP body changes" sentence for this code rests on that catch staying best-effort (if a later card lets it propagate, the wire carries the specific code — under D the intended effect, not a break).

  6. observation — the pin test. Presence pin for all fourteen under their owners with standardSynonymOf(code) undefined (:283-306), MULTI_TENANT_UNSUPPORTED refused as the control (:319-320), OVERLAY_PERSISTENCE_FAILED as the producerless witness (:270-281), owner keys driver-mongodb / organizations pinned present. No .skip/.only/.todo (grep 0). Pin ablation NOT MEASURED locally (no node_modules here); by reading, deleting a row fails both ErrorCode.parse(code) and toContain(code). Test Core 1/6–6/6 green on this head.

  7. observation — changeset. @objectstack/spec: minor only. node scripts/check-changeset-no-major.mjs --base origin/main --head refs/review/16879introduces no major bump, exit 0. A judgeLevel function does not exist anywhere in the tree (repo-wide grep: 0); the level rule that does exist is the no-major header :54-56 ("a purely additive widening of a published package's public surface takes at least minor") — satisfied. Spec-only is right in effect: @objectstack/spec and all six stamping packages sit in the single fixed group of .changeset/config.json, so spec: minor bumps them in lockstep; the card's "per-package changesets" bullet would add redundant entries for packages whose src and dist do not change. Body carries Part of #16649, no closing keyword; "Part-of PR must not also close its card" green.

  8. observation — baseline and docs. scripts/doc-authoring-prose-id.baseline.json: two keys removed under the vocabulary file (#3724: 1, #8035: 6), nothing added or raised — shrink-only. The one #8035 mention left at head (vocabulary :216) is JSDoc, which the cross-package leg does not count. Docs regenerated only under content/docs/references/api/; ErrorCode union +308+322; the ledger page adds the fourteen.

  9. observation for the director — what stays open, and the PR is right that it is a PM call. SPEC_SOURCE_FACE is still 'packages/spec/src/' (:308) with SPEC_FACE_VERDICTS = {foreign-vocabulary, runtime-pinned} (:309), which refuses pending-registration too; widened verbatim to every published package it reds AMBIGUOUS_METADATA_STEM (packages/metadata) and owd_widening_forbidden (packages/plugins/plugin-security), which triage item 2 puts out of this card's scope. Triage items 3/4/5 asked for the widening in the same batch; this PR deviates deliberately and says so. Card spec: register the 14 remaining door: 'none' (boot-refusal) codes that ship in dist — the rest of the #16404 class after #16449 #16649 stays open. The allowance shape (retire boot-refusal only / spec-face set everywhere / shrink-only allowlist) and whether to split the card are the director's, not this seat's.

  10. observation — CI on 6738c993e. 42 check runs: every one success or skipped (Test Core 1–6, Type Check workspace/source/consumer/debt, Lint & Repo Gates, Build Core/Docs, Dogfood 1–3, Check Changeset, Governed Surface Queue Guard, Spec property liveness, Part-of guard, single-writer / same-issue guards). Combined commit status: one pending context, Vercel — "Vercel is deploying your app", created 13:11:14Z and never updated. That pending status is the whole of mergeable_state: unstable; nothing is red, nothing is running.

Maintainer-only merge: no — no governed surface in the diff; a Clause-② PR after at-tier review follows references/contract-review.md:39 (landing checks → ready → auto-merge or enqueue). Condition before enqueue: finding 1.


Generated by Claude Code

Merged via the queue into main with commit 613bfbd Sep 8, 2026
42 of 43 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-16649-register-remaining-boot-refusal-codes branch September 8, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants