Skip to content

lint: widen the vocabulary gate's face refusal to every published package and retire boot-refusal - #17056

Draft
os-bill wants to merge 3 commits into
mainfrom
claude/issue-16649-widen-vocabulary-gate-retire-boot-refusal
Draft

lint: widen the vocabulary gate's face refusal to every published package and retire boot-refusal#17056
os-bill wants to merge 3 commits into
mainfrom
claude/issue-16649-widen-vocabulary-gate-retire-boot-refusal

Conversation

@os-bill

@os-bill os-bill commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

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 asked file.startsWith('packages/spec/src/') and nothing else, now asks which published package's src/ a site sits in. Members are enumerated from their manifests through scripts/workspace-enumerator.mjs, the repo's one parse of pnpm-workspace.yaml; "published" is name && private !== true, the same test check-published-files.mjs applies, because the ruling's premise is about what a consumer installs. Measured on this tree: 80 workspace members, 70 published, 69 of those with a src/ on disk. The derivation emits one prefix per published member and does not probe the disk for src/, 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/ keeps foreign-vocabulary / runtime-pinned only, and the widened region additionally admits pending-registration under a named, dated allowance owed to #8846. The spec region keeps reporting spec-face-unregistered and the widened region reports a new published-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-site remedy text told every author to reach for pending-registration. Inside packages/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.tsboot-refusal retired from CodeVerdict. 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's MODULE_NOT_FOUND row (a published package outside packages/spec/src/) was flipped from foreign-vocabulary to sandbox-authored — still a legal CodeVerdict, so nothing else catches it.

gate source exit
base origin/main's script, byte-for-byte (308374 bytes), run from this worktree's scripts/ so its ROOT resolves here 0 — "52 unregistered code-stamping site(s), all classified"
widened this branch 1[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 blob 26b59e1e..., anchor count 1 to 0 and injected count 0 to 1. Restored under trap ... EXIT INT TERM with git checkout HEAD -- ABSOLUTE_PATH; restored blob equals the HEAD blob, git diff HEAD --stat and git status --porcelain both empty. The temporary base-gate copy was removed by the same trap.

Two further ablations, same discipline:

  • The allowance is load-bearing, and the ruling's stated reason for it is true on this tree. Deleting '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_STEM in packages/metadata and owd_widening_forbidden in packages/plugins/plugin-security. Gate exit 1, restored blob equals HEAD.
  • The floor refuses rather than going quiet. Raising it to 999 makes the gate throw and name the enumerated count. 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, 1 runtime-pinned, 2 pending-registration — and the --self-test battery pins each one outside packages/spec/src/ as an explicit case, alongside the two boundary controls (a private member's src/ and a published package file outside src/, both outside the face).

The battery was rewritten and its floor raised from 7 to 21. Every case passes its own publishedFaces, because reconcile defaults 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.ts returns 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_LIMB and dispatcher-error-vocabulary each hit 0 of the 6 files in packages/runtime/dist/, against positive controls MissingDriverPackageError and resolveProjectDatabaseUrl at 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 a files[] entry that could ship a root scripts/ path, and the root manifest is private: true; @objectstack/runtime's files[] is ['dist','README.md','CHANGELOG.md'], so its src/ is not shipped. Dist test: the symbol readings above. Nothing published moves.

Verification

Merged origin/main once (merge commit 22efeead, clean, lockfile unmoved). All readings below are from that head.

  • Gate set derived with 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 set packages/runtime/src/dispatcher-error-vocabulary.ts and scripts/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-vocabulary exit 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-loads exit 3, PREREQUISITE NOT MET — 37 packages have no dist/ 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.
  • Dependency closure and the affected package, under scripts/pm/os-verify-lock.sh: pnpm --workspace-concurrency=2 --filter '@objectstack/runtime...' build exit 0, pnpm --filter @objectstack/runtime typecheck exit 0, pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 src/domains/actions-validation-envelope.test.ts exit 0. Wrapper VERDICT command-exit 0.
  • The edited gate script ships no *.test.ts; its tests are its --self-test, wired into check:dispatcher-error-vocabulary and run above. git grep of the script's filename across test files finds one carrier, packages/spec/src/api/error-code-ledger.test.ts — run, 20/20 passed.
  • Full repo lint, not a narrowing: eslint . --no-inline-config exit 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 CodeVerdict member that no longer exists: packages/spec/src/api/error-code-ledger.zod.ts (the spec-face paragraph and several historical row comments) and packages/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 stale boot-refusal pointer, so nothing was re-filed. Successor: #16884 for the packages/core and packages/drivers/driver-sql carriers; for the spec-tree prose, whoever next edits the ledger.

The sandbox-authored verdict is now refused everywhere in the published face, which follows from "refusing exactly the verdicts the spec face refuses today except pending-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

…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
@os-bill os-bill added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 9, 2026 — with Claude
@github-actions github-actions Bot added the size/m label Sep 9, 2026
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/api/error-code-ledger.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/api/error-code-ledger.zod.ts) — pages documenting those are invisible to this run
  • 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 e4fd55d9b0897bfc5031e0cfa2654b08514d69depackageMentionDocs.

Which tree this was computed on

This run read content/docs from 087e1d4b37b243c8702136805554ffa529a8a6bf — the merge of head 26d81f7fd071c3b4f87d4955db076d3849fb2cde into base e4fd55d9b0897bfc5031e0cfa2654b08514d69de, 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 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

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

…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
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation and removed size/m labels Sep 9, 2026
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 skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: register the 14 remaining door: 'none' (boot-refusal) codes that ship in dist — the rest of the #16404 class after #16449

2 participants