fix(spec): give four packages/spec self-tests a roster, a floor and a verdict handshake - #19076
Conversation
… verdict handshake `check-exported-any.ts`, `check-dual-source-exports.ts`, `check-error-code-provenance.ts` and `check-browser-reachable-entries.ts` each dispatched on `--self-test` with no battery roster, no floor and no verdict handshake, so "every case held" and "the cases never ran" printed the same line. Measured on this package rather than inherited: deleting `'InferredFromAnySchema'` from `check-exported-any.ts`'s red-leg fixture name list de-registers one of its two type-half detection pins, and the run still prints its verdict byte-identically and still exits 0. Each of the four now carries all three pieces, COPIED from the landed precedents and never imported, so every self-test keeps running standalone: - a frozen `SELF_TEST_BATTERIES` roster (battery name -> minimum case count), every case registered against the battery most recently opened, registration first in the assertion sink so the floor asserts REACH rather than failure; - `SELF_TEST_BATTERY_FLOOR`, pinned to the roster's own size, so deleting an entry cannot take its own floor with it; - a module-level verdict flag set after the verdict line prints and a dispatch that refuses a self-test which returned without reaching it. In three of the four, a `return` above the verdict did not even stop: control fell through to the real audit, which on a built tree prints its own green line and exits 0. No detector, scan surface, ledger, baseline or audit verdict text moves. Each self-test's verdict line now also reports the registered case count and battery count, because a printed count is evidence and the floor is the proof. Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh Co-authored-by: Claude <noreply@anthropic.com>
…lf-test-floor-tranche-1
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
… published package Measured rather than assumed: `packages/spec`'s `files[]` ships `dist`, `json-schema`, `liveness`, `prompts`, `llms.txt`, `README.md`, `src/**/*.zod.ts`, `CHANGELOG.md`, `api-surface`, `api-surface-declarations` and `spec-changes.json`. `scripts/` is not among them, and the four retrofitted files live there, so no published byte moves and the changeset would have published a version whose tarball content is unchanged. The label route is the gate's own prescription for this shape and it is written by the seat, not from here; `Check Changeset` reds between this push and that write, which is the expected shape and not a regression. Claude-Session: https://claude.ai/code/session_019srGWGCBBCBHqcDoRZpQRh Co-authored-by: Claude <noreply@anthropic.com>
|
This red was predicted and sequenced in the disposition on the card (#18919, comment 5732760075) before the push that caused it. ⛔ It is not a regression, and ⛔ nobody should re-run it. Why the exemption, and not a changeset
The changeset this PR briefly carried was there because the dispatch told the dev to write one, without checking whether that package ships Route 0 checked before the write, because it is the one case where this label is REFUSEDRuling ② B on #18375 refuses The drop commit is exactly the drop
What to expect on the check displayPer the gate's own log, applying the label fires a Generated by Claude Code |
|
The event names head Why it is an artifact, measured rather than assumedOn the superseded head, the check-runs read: 19 success · 3 skipped · 12 The aggregator's own timing says the same thing: run ⇒ the push cancelled the in-flight run, and the aggregator turned the cancellations into a Current headZero non-green. The four That last point is the one that would have made this worth a fix rather than a note: the TS surface on both heads is byte-identical, so a genuine type error could not hide behind the supersession. It will be visible on the current head or it does not exist. ⛔ No re-run of the dead head's run, and no push: re-running a cancelled lane to tidy a display is what the standing rule forbids, and there is nothing to fix. Generated by Claude Code |
Refs #18919
Clause-②: no
Tranche 1 of #18919 — 4 of the 12 gates under
packages/spec/scripts/that dispatch on--self-testwhile carrying no battery roster, no floor and no verdict handshake. ⛔ This PR deliberately does not close the card; the card's own fence is 「⛔ 不主张一次全做完」 and the eight-file residue is named below so the next tranche needs no re-derivation.The hole reproduces inside this package — measured before any retrofit
The card claims absence of protection, ⛔ not existence of a defect, and says #18512's readings are not these files' readings. So the first act was an ablation on one of the four, at the branch point
d4cb05cbf, throughscripts/ablation-replace.mjs(anchor must hit, write verified against the disk, restore proven):packages/spec/scripts/check-exported-any.ts['BareAny', 'InferredFromAnySchema']→['BareAny']— one of the two RED-leg detection pins stops being reached1→0; replacement0→17150b80a00ead8fecb8b13f08f27d2627332fb283210b6b88928bf1baebc50cf520133a65133b2fe✅ self-test: detects \any` types and `any`-output schemas, and nothing else.` — byte-identical to the unmutated rungit checkout HEAD -- PATH: blob back to7150b80a00ea…,git diff HEADemptyA second reading, on the file whose dispatch hands the self-test's return value straight to
process.exit()—check-error-code-provenance.ts, same tool, same tree, in a throwaway worktree atd4cb05cbf:return 0;as the first statement ofselfTest()9e74797b6d2db68bb59bd9c9fd93ff81e9ef32e7→2245217ca8d41e3b46fb1634c7b99637dd9b351d9e74797b6d2d…,git diff HEADemptySo both holes the card names are real here, not inherited: a battery can shrink silently, and a
returnabove the verdict prints nothing and passes.What the four carry now, with the lit control
Predicate, re-established rather than quoted from the dispatch —
grep -c -E 'SELF_TEST_BATTERIES|SELF_TEST_BATTERY_FLOOR'(roster) andgrep -c 'returned without reaching its verdict'(handshake), matching lines:check-exported-any.tscheck-dual-source-exports.tscheck-error-code-provenance.tscheck-browser-reachable-entries.tsscripts/check-test-typecheck.mts(unchanged)--self-testprints 11 semantic casesThe same instrument answers 10 / 1 on the TS precedent and answered 0 / 0 on all four targets, so the four zeros were readings and not a dead grep. The registered case counts are not derived from the roster — each is the number the run itself registered, and the roster is a literal the run is compared against.
Every floor was measured, not guessed: the roster was first declared with a sentinel floor of 999 per battery and the run asked which batteries registered how many cases; the floors are those answers. That sentinel run is also a red-leg demonstration — the floor named all nine
check-browser-reachable-entries.tsbatteries and exited 1.Reverse verification: the same mutation now reds, and names the battery
From the committed retrofit, the ablation that was silent-green above, re-run through the same tool:
blob
99c2fbe72538…→f92324cceba0…, command exit 1, restore proven (blob == HEAD,git diff HEADempty).And the handshake, one leg per file —
return;(return 0;for the one returning a number) injected as the first statement ofselfTest(), each from the committed state, each restored with the blob equal to HEAD's and an emptygit diff HEAD:check-exported-any.tsd51735a53c12…✗ check-exported-any self-test: selfTest() returned without reaching its verdict,check-dual-source-exports.ts21bf4cbbaedf…✗ check-dual-source-exports self-test: …without reaching its verdict,check-error-code-provenance.tsd5bd8fe9c0e6…✗ check-error-code-provenance self-test: …without reaching its verdict,check-browser-reachable-entries.ts33dc39d561dc…✗ check-browser-reachable-entries self-test: …without reaching its verdict,In three of the four that
returndid not even stop the process before this PR:selfTest()was typedneverand the dispatch was a bareif (SELF_TEST) selfTest();, so control fell through to the real audit, which on a built tree prints its own green line and exits 0.The retrofit changes nothing about what these gates say about the tree
Each file's audit path is byte-identical to the branch point (
sha256over the region, taken from the two blobs):check-exported-any.ts// ── Audit ─→ EOF74d2dbb87e8e5ec674d2dbb87e8e5ec6check-dual-source-exports.ts// ── Audit ─→ EOFf95e248f6ce2a74df95e248f6ce2a74dcheck-error-code-provenance.ts8ad0283719c0f8558ad0283719c0f855check-browser-reachable-entries.ts82c3e7ee5ab505f982c3e7ee5ab505f9The only change outside the self-test region is each file's dispatch block. All four real (non-self-test) runs are green after a
packages/specbuild, and say what they said before:Checker health is not a PR verdict — and now their own health is falsifiable
scripts/pm/dispatch-gates.mjs --commandsalready separates this class by name, printing a--self-test-only family as 「⚠ checker-health only (--self-test) — NOT a PR verdict」. The repo therefore already knew a self-test is not a verdict about the tree. What these four lacked is the other half: a self-test whose own health can be falsified. A roster with a floor makes a shrunk battery say so, and the handshake makes a silent return say so.Two design points worth naming, both copied from the landed precedents rather than invented:
⛔ Nothing is imported: each of the four still runs standalone as
tsx scripts/NAME.ts --self-test, which is the whole point of the mechanism.Residue — the eight files this tranche does NOT touch
Measured at this branch's head, all eight still answer roster
0/ handshake0on the predicates above:12 today, not the card's 13: PR #18916 landed
check-duration-unit-keys.ts, which now carries all three pieces (roster 10 / handshake 1) and is out of the population.Changeset
No changeset — this diff releases nothing, and the exemption label is the gate's own prescription for that case.
@objectstack/specshipsdist,json-schema,liveness,prompts,llms.txt,README.md,src/**/*.zod.ts,CHANGELOG.md,api-surface,api-surface-declarationsandspec-changes.json; the four changed files live underscripts/, which is not in that list, so they reach no tarball and a named changeset would publish a version whose content is unchanged. This PR briefly carried apatchchangeset because the dispatch told the author to write one without checking whether the package shipsscripts/— that was the seat's error; the author measured the contradiction and flagged it inopen_questionsrather than resolving it silently, and the changeset was dropped in0e8b6567a49e(one file, −31 lines, nothing else moved).skip-changesetwas then applied by the seat, a label write the author is forbidden to make, after checking the one case where that label is refused: ruling ② B on #18375 refuses it where a PR's.changesetrows are CHANGED, and this PR's diff against base carries zero.changesetrows of any status.Verification
Head of this branch when these were taken:
fe05b5ae1.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack0ec81857a), every one run, exit code captured before any pipe… --ran RECORD✓ 63 derived famil(ies) accounted for — 61 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)pnpm check:dual-build-cjs-loads,pnpm check:lean-entry-closuredistin this container; onlypackages/spec's closure was built). Their own--self-testlegs passed inside the same run. ⛔ Recorded as not measured, not as a pass and not as a finding.pnpm --filter '@objectstack/spec...' build --concurrency=2, throughscripts/pm/os-verify-lock.shVERDICT command-exit 0· held the lock 145s · waited 0spnpm --filter @objectstack/spec typechecktsc --noEmit,check:scripts-typecheck(this is the leg that compilesscripts/**) andcheck:test-typecheckpnpm --filter @objectstack/spec test, under the same lockvitest run --maxWorkers=2 scripts/dist-freshness-adoption.test.ts scripts/check-error-code-provenance.test.ts--self-testend to end: the spawned--self-testmust exit 0 printingself-test OK, and both stale-dist cases require--self-testto still run and exit 0 on a dist it never reads.pnpm --filter @objectstack/spec run check:{exported-any,dual-source-exports,error-code-provenance,browser-reachable-entries}pnpm lint(eslint . --no-inline-config, the whole repo)grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'over the four files, pluspnpm check:nul-bytesorigin/mainwas merged into this branch before these readings (three commits,scripts/check-closing-target-claim.mjs,scripts/measure-self-test-floor.mjs,scripts/pm/os-verify-lock.sh— all disjoint from this diff, nonemerge=os-regen, no regeneration debt recorded).Acceptance notes
noted, not filed:scripts/measure-self-test-floor.mjs— the repo's own self-test floor/handshake instrument — has a population of repo-rootscripts/**only (population()walksjoin(ROOT, 'scripts')), so the twelve gates underpackages/spec/scripts/that #18919 is about cannot be probed by it at all, in either direction. Nothing inAGENTS.mdor that file's own contract promises that directory, so this is a scope gap rather than a violated contract; widening it is its own card and its own measurement. Next toucher: whoever dispatches tranche 2 of #18919, or whoever regeneratesdocs/audits/2026-09-self-test-shape-census.md.Generated by Claude Code