lint: widen the vocabulary gate's face refusal to every published package and retire boot-refusal - #17056
Conversation
…ed package and retire boot-refusal `check-dispatcher-error-vocabulary` refused a site classified away from registration only under `packages/spec/src/` — the one package #16449 had measured. The #16404 ruling it enforces is about every code that ships in `dist`, so the refusal now covers every published workspace member's `src/`, enumerated from the manifests through the shared workspace parse and held against a floor so a broken enumeration refuses instead of going quiet. `packages/spec/src/` keeps the stricter verdict set. Outside it, `pending-registration` keeps a named, dated allowance owed to #8846, so the two rows whose disposition belongs to #8846 / #9460 do not red on a card that is not theirs; every run prints how many rows still stand on it. With the whole scan population inside the face, `boot-refusal` is a verdict no row can legally carry, so it retires from `CodeVerdict`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…den-vocabulary-gate-retire-boot-refusal
📓 Docs Drift Check2 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 087e1d4b37b243c8702136805554ffa529a8a6bf && git checkout 087e1d4b37b243c8702136805554ffa529a8a6bf
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e4fd55d9b0897bfc5031e0cfa2654b08514d69de 26d81f7fd071c3b4f87d4955db076d3849fb2cde && git checkout -B drift-repro e4fd55d9b0897bfc5031e0cfa2654b08514d69de && git merge --no-ff 26d81f7fd071c3b4f87d4955db076d3849fb2cde
node scripts/docs-audit/affected-docs.mjs --json e4fd55d9b0897bfc5031e0cfa2654b08514d69de |
…e verdict `packages/spec` ships `src/**/*.zod.ts` in its `files[]`, so the ledger's TSDoc is published prose. One sentence said the dispatcher vocabulary's `boot-refusal` verdict records reachability for codes not yet registered and pointed at the file this branch deletes it from — a live-mechanism claim, not a case that can no longer arise, so a reader would follow the pointer and find nothing. It now records the retirement and names what replaced it. The paragraph below it claimed only `packages/spec/src/**` is held mechanically. That understated the rule after the widening, so it now states the published face, the stricter spec sub-face and the dated #8846 allowance, with both finding kinds named. The mirrored reference page is regenerated with `check:generated --fix` (`gen:docs`), never by hand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Fixes #16649
Second half of the card. The first half (registering the fourteen
door: 'none'codes) landed as #16879 on 2026-09-08; this is the mechanical closure the card's Scope and triage acceptance items 3/4/5 ask for.Implemented per the director seat's ruling of 2026-09-08 (comment 5586454647, decision batch #95), quoted verbatim in the card. The later release comment 5586642296 re-lists options A/B/C as open; it was written thirteen minutes after the ruling and does not cite it. This PR follows the ruling; A/B/C were not re-litigated.
What changed
scripts/check-dispatcher-error-vocabulary.mjs— the face refusal, which askedfile.startsWith('packages/spec/src/')and nothing else, now asks which published package'ssrc/a site sits in. Members are enumerated from their manifests throughscripts/workspace-enumerator.mjs, the repo's one parse ofpnpm-workspace.yaml; "published" isname && private !== true, the same testcheck-published-files.mjsapplies, because the ruling's premise is about what a consumer installs. Measured on this tree: 80 workspace members, 70 published, 69 of those with asrc/on disk. The derivation emits one prefix per published member and does not probe the disk forsrc/, so the printed count is members, not members-with-sources; that is stated where it is derived.The two faces stay separate because they differ:
packages/spec/src/keepsforeign-vocabulary/runtime-pinnedonly, and the widened region additionally admitspending-registrationunder a named, dated allowance owed to #8846. The spec region keeps reportingspec-face-unregisteredand the widened region reports a newpublished-face-unregistered, so the ledger's existing prose about that kind stays true and the two regions stay distinguishable in output.The enumeration is floor-checked before use (
PUBLISHED_SOURCE_FACE_FLOOR = 40, against 70 measured). A face list that silently shrank would turn the refusal off for whatever fell out and print the same green line as a complete one, so the gate refuses instead of reporting.The
unclassified-siteremedy text told every author to reach forpending-registration. Insidepackages/spec/src/that is the one verdict the face refuses, so the text is now face-aware and names the ledger row first.packages/runtime/src/dispatcher-error-vocabulary.ts—boot-refusalretired fromCodeVerdict. With the whole scan population inside the face (52 sites: 47 published-face, 5 spec-face), no row can legally carry it. Its own doc comment had already named this as the condition for its retirement. A paragraph in its place records what it meant and why it must not come back; the running log records the fourth cycle closing mechanically rather than by having been emptied once.Acceptance item 4: the red-before-green demonstration
Item 4 was written when the fourteen codes were unregistered, so "run the widened gate and watch it red on all fourteen" was the available proof.
git grep -c "verdict: 'boot-refusal'"now returns 0 — the first half consumed that proof. The equivalent was constructed instead, and it measures the delta rather than merely showing the gate can fail: one mutation, two gates, the same tree.packages/types/src/node.ts'sMODULE_NOT_FOUNDrow (a published package outsidepackages/spec/src/) was flipped fromforeign-vocabularytosandbox-authored— still a legalCodeVerdict, so nothing else catches it.origin/main's script, byte-for-byte (308374 bytes), run from this worktree'sscripts/so itsROOTresolves here[published-face-unregistered] packages/types/src/node.ts stamps unregistered code 'MODULE_NOT_FOUND' (objlit) ... classifies it 'sandbox-authored' — a verdict refused under a published package's src/Mutation proven on disk before either leg: HEAD blob
0a1e68f2..., mutated blob26b59e1e..., anchor count 1 to 0 and injected count 0 to 1. Restored undertrap ... EXIT INT TERMwithgit checkout HEAD -- ABSOLUTE_PATH; restored blob equals the HEAD blob,git diff HEAD --statandgit status --porcelainboth empty. The temporary base-gate copy was removed by the same trap.Two further ablations, same discipline:
'pending-registration'from the widened verdict set (the rule applied verbatim, with no carve-out) reds exactly two rows, and they are exactly the two the ruling named:AMBIGUOUS_METADATA_STEMinpackages/metadataandowd_widening_forbiddeninpackages/plugins/plugin-security. Gate exit 1, restored blob equals HEAD.Acceptance item 5: the negative controls
The two surviving verdicts must not be refused by the widening, or the implementation would be "everything unregistered reds", which passes item 4 while deleting two legitimate exemptions. On the live tree the widened gate is green over all 52 rows — 49
foreign-vocabulary, 1runtime-pinned, 2pending-registration— and the--self-testbattery pins each one outsidepackages/spec/src/as an explicit case, alongside the two boundary controls (a private member'ssrc/and a published package file outsidesrc/, both outside the face).The battery was rewritten and its floor raised from 7 to 21. Every case passes its own
publishedFaces, becausereconciledefaults to none; the last three cases hold the fixture population against the live enumeration, so the battery cannot be green over a workspace this repo does not have.Clause 2 and the changeset, both re-measured
Clause-②: no, re-derived rather than inherited.git grep -n "dispatcher-error-vocabulary" HEAD -- packages/runtime/src/index.tsreturns zero: the module is not exported from the published entry point. Confirmed from the dist side after building@objectstack/runtime:boot-refusal,CodeVerdict,UNREGISTERED_CODE_SITES,SANDBOX_AUTHORED_LIMBanddispatcher-error-vocabularyeach hit 0 of the 6 files inpackages/runtime/dist/, against positive controlsMissingDriverPackageErrorandresolveProjectDatabaseUrlat 4 files each. Deleting a member of a type that does not ship, plus a strictly stricter gate, moves no published face.skip-changeset, measured on both halves as the lane requires. Path test: 70 published manifests examined, none carries afiles[]entry that could ship a rootscripts/path, and the root manifest isprivate: true;@objectstack/runtime'sfiles[]is['dist','README.md','CHANGELOG.md'], so itssrc/is not shipped. Dist test: the symbol readings above. Nothing published moves.Verification
Merged
origin/mainonce (merge commit22efeead, clean, lockfile unmoved). All readings below are from that head.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, letting it take the change set from the merge base itself: 64 families, change setpackages/runtime/src/dispatcher-error-vocabulary.tsandscripts/check-dispatcher-error-vocabulary.mjs. 63 exit 0. Reconciled with--ran: "64 derived famil(ies) accounted for — 64 run, 0 NOT-MEASURED".pnpm check:dispatcher-error-vocabularyexit 0 — self-test "10 shapes + 344 assertions OK", production run "52 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)".pnpm check:dual-build-cjs-loadsexit 3,PREREQUISITE NOT MET— 37 packages have nodist/in this worktree; its own text says "This is NOT a pass: nothing was measured". Recorded as NOT MEASURED, not as a failure. Its self-test passed (93 cases). Whole-tree family, CI owns it.scripts/pm/os-verify-lock.sh:pnpm --workspace-concurrency=2 --filter '@objectstack/runtime...' buildexit 0,pnpm --filter @objectstack/runtime typecheckexit 0,pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 src/domains/actions-validation-envelope.test.tsexit 0. WrapperVERDICT command-exit 0.*.test.ts; its tests are its--self-test, wired intocheck:dispatcher-error-vocabularyand run above.git grepof the script's filename across test files finds one carrier,packages/spec/src/api/error-code-ledger.test.ts— run, 20/20 passed.eslint . --no-inline-configexit 0 over 6398 files, 0 errors, 0 warnings, with both changed files confirmed present in the linted set.Acceptance notes
Not filed, recorded here. Retiring the verdict leaves prose elsewhere in the tree pointing at a
CodeVerdictmember that no longer exists:packages/spec/src/api/error-code-ledger.zod.ts(the spec-face paragraph and several historical row comments) andpackages/spec/src/stack.zod.ts(which qualifies itself with "on the tree this landed against" and stays true as a historical reading). None of it is false in a way that misdirects an author — the sentences describe a case that can no longer arise — and the ledger file is deliberately outside this PR's file surface: the ledger half is merged and that file is contended by #17015 and #16783. #16884 is already open on the same class and explicitly names the staleboot-refusalpointer, so nothing was re-filed. Successor: #16884 for thepackages/coreandpackages/drivers/driver-sqlcarriers; for the spec-tree prose, whoever next edits the ledger.The
sandbox-authoredverdict is now refused everywhere in the published face, which follows from "refusing exactly the verdicts the spec face refuses today exceptpending-registration". No row on this tree carries it — the sandbox limb is outside the scan by construction — so this is a widening with no live subject. It was not retired: that is not ruled here.🤖 Generated with Claude Code
https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
Generated by Claude Code