Skip to content

feat(pm): derive check:dispatcher-error-vocabulary from a diff's CONTENT, the gate no path can name - #12875

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-12850-vocabulary-convention-trigger
Aug 28, 2026
Merged

feat(pm): derive check:dispatcher-error-vocabulary from a diff's CONTENT, the gate no path can name#12875
os-elon merged 1 commit into
mainfrom
claude/issue-12850-vocabulary-convention-trigger

Conversation

@os-elon

@os-elon os-elon commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Fixes #12850

check:dispatcher-error-vocabulary computes its own population by walking a bare
top-level root, so no path-derived trigger ever names it in a dispatch prompt's gate
list. A dev who introduces a new error/notice code runs the derived union green
locally and reds on CI's Lint & Repo Gates. That silence cost one full CI round trip
on PR #12843, which is where this card came from.

This adds a CONTENT-shaped CHANGE_KIND_GATES entry — the first one in the table
judged from a file's text rather than from its path.

Why not a path-shaped trigger

Not a judgment call: the tree has already adjudicated this exact spelling.
scripts/pm/bare-root-worklist.mjs carries the row

check:dispatcher-error-vocabulary SCAN_ROOT packages -> REFUSE-WIDE
  'non-test sources plus manifests, 1898 of 4903 (39%) - same trade'

and the sibling row it is grouped with spells the trade out: such a declaration
"would name this gate for every card in the repo that touches a package". Inventing a
path prefix here would mirror, inside this file, a population the ledger next door has
already refused to spell — and a lead that fires on every card is one a reader learns
to skip. Recognising that the gate is content-shaped is the whole of this change.

The predicate

Two limbs over the comment-masked source, deliberately broader than the gate's own
SHAPES table:

  • stamp position — a code token bound to a quoted literal, a SCREAMING_SNAKE
    identifier, or a typeof reference to one. The typeof half is load-bearing: the
    specimen that cost the round trip is code: typeof CONVERSION_NOTICE_CODE.
  • constant binding — a SCREAMING_SNAKE binding whose value is a quoted
    SCREAMING_SNAKE string, which is how a code is declared before any code token is
    near it.

It is not a copy of SHAPES and must not become one: a copy would go stale in the
silent direction the day that table grows an indirection (it has grown twice for
exactly that reason), whereas a superset cannot.

Measured on this tree (7169 tracked files)

reading value
files the predicate matches 196 of 2281 non-test TS (8.6%, 2.7% of tree)
what a path spelling would have named 1898 of 4903 (39%)
matches inside the gate's own scanned population 194 of 196
matches outside it 2 (one app file, one example file)
limb overlap no file is reached by the constant limb alone

Those 2 outside files are the entire cost of carrying no population literal — and
carrying none is what keeps this file from spelling a pathy string it would then match
cards through. Verified: watch hints on this file are 9 before and 9 after, none
added, none removed.

A third limb (a SCREAMING_SNAKE string-literal type) was drafted from the card's
wording and dropped on measurement: it reached zero files the other two did not,
so it would have been a limb no fixture in the tree could pin.

The false-positive trade, written down

Per the card's ruling, the entry over-matches on purpose: it fires for a file that
merely carries a code, not only one that adds a new one. A false positive costs one
extra gate run (this gate needs no build and answers for the whole tree in one pass);
a false negative costs a CI round trip. The trade is stated in the predicate's
docblock and in the rendered why line, and a self-test case fails if that sentence
is silently removed — narrowing this later is a trade against a measured price, not a
tidy-up.

What it deliberately does not close

A file that does not exist has no content, so the trigger fires for the dev's
re-derivation off the merge base (where the file is real, and where the missed gate
actually costs the round trip) and stays quiet for the PM's hypothetical file surface.
Closing that by falling back to the path half would reintroduce the refused trigger
under another name. Stated in the docblock and pinned by its own case.

Verification

All at final head 7e53b389.

  • node scripts/pm/dispatch-gates.mjs --self-test834 cases pass (27 new).

  • Ablation. Removing the typeof limb reddens exactly one case, the one pinning
    the feat(metadata): versioned ADR-0087 forward conversion at the artifact-ingestion door #12843 shape: 1 of 834 case(s) failed. Mutation confirmed on disk before the
    run (limb occurrences 1 to 0; blob 400f9278 to 8f3093af), restored with
    git checkout HEAD --, restore confirmed by an empty git diff HEAD and the working
    blob hashing back to 400f9278. No build or dist/ is involved — this script is run
    directly by node — so there is no rebuild leg to report.

  • Gate family re-derived from the actual diff with
    node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths);
    it returned exactly the 12 families the dispatch brief named, no more. All green,
    each exit code captured before any pipe:

    check:agent-test-spelling · check:bash32-floor · check:cli-command-ids ·
    check:cross-package-test-inputs · check:entry-guard · check:parse-guard ·
    check:pm-dispatch-gates · check:pnpm-filter-targets · check:watch-hint-literal ·
    scripts/check-ci-filter-parity.mjs · scripts/check-cross-package-test-inputs.mjs ·
    scripts/check-self-test-wired.mjs · plus check:nul-bytes.

    check-ci-filter-parity.mjs first exited 1 with PREREQUISITE NOT MET - the dependency yaml is not installed. That is NOT MEASURED, not a finding; after
    pnpm install it re-ran green: "all 109 declared cross-package glob(s) (84 unique)
    are covered".

  • This script's own suite is its --self-test; no vitest file exercises it (the two
    test files that name it do so in prose comments only).

Changeset: none, deliberately

Derived, not assumed. scripts/pm/** is internal PM tooling and ships in no published
package, so this PR releases nothing. Receipt: of the 8 most recent commits touching
scripts/pm/dispatch-gates.mjs, 8 carried zero changesets
. The PR carries
skip-changeset instead, which is a live mechanism here — pr-automation.yml reads
that label as the author's explicit opt-out.

Scope

The CONVENTION trigger only. #12856 — the tsc heap ceiling that makes
check:type-check-debt --re-measure OOM only on CI — was split off by triage and is
not addressed here; it remains open.


Generated by Claude Code

…ENT, the gate no path can name

The vocabulary gate computes its own population by walking a bare top-level
root, so no path-derived trigger ever names it in a dispatch prompt's gate
list. A dev introducing a new error/notice code runs the derived union green
locally and reds on CI — measured once, at the price of a full round trip.

Adds a CONTENT-shaped CHANGE_KIND_GATES entry: two limbs over the masked
source (a `code` token bound to a quoted literal, a SCREAMING_SNAKE constant
or a `typeof` reference to one; and a SCREAMING_SNAKE constant bound to a
SCREAMING_SNAKE string), deliberately broader than the gate's own SHAPES
table so it cannot go stale in the silent direction.

Measured on this tree: 196 of 2281 non-test TS files (8.6%), against the 39%
a path spelling would have named — the spelling the bare-root ledger already
records as REFUSE-WIDE for this gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
@os-elon os-elon added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 28, 2026 — with Claude
@os-elon
os-elon marked this pull request as ready for review August 28, 2026 06:51
@os-elon
os-elon enabled auto-merge August 28, 2026 06:51
@os-elon
os-elon added this pull request to the merge queue Aug 28, 2026
Merged via the queue into main with commit 44564d2 Aug 28, 2026
34 checks passed
@os-elon
os-elon deleted the claude/issue-12850-vocabulary-convention-trigger branch August 28, 2026 07:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants