Uh oh!
There was an error while loading. Please reload this page.
fix(devx): branch the graduation remedy on the ledger it graduates from - #11848
Conversation
…aduates from `check-type-check-coverage.mjs` printed one graduation note for both of its ledgers, offering both remedies joined by an "or": add a `typecheck` script, or drop the test exclusion. Each remedy belongs to exactly one ledger, and the gate's own header states the distinction the message dropped -- DEBT is "src does not check", TEST_DEBT is "src checks, tests are hidden". Measured on the ledgers at e47d5ef, both halves misfire on TEST_DEBT: * "add a `typecheck` script" is a no-op for 19 of 19 TEST_DEBT entries. Every one already declares one -- that is what makes them TEST_DEBT. * "drop the test exclusion" turns `check:type-source-resolution` from exit 0 to exit 1 on 14 of the 18 entries that have an exclusion to drop, measured by doing it per package and restoring. The re-admitted tests import workspace packages the src program never held, and that gate's registry is shrink-only, so the author arrives at a second gate with no remedy at all. The 19th entry, `@objectstack/cli`, has no exclusion to drop -- its tests are hidden by an `include` that never reaches them. On DEBT the sentence misfires once more, on the entry easiest to get wrong: the workspace root's `typecheck` slot is the aggregator (`turbo run typecheck`) and its own TypeScript is read through `typecheck:root`. A taker following the old note verbatim would overwrite every other package's typecheck. So `graduationRemedy()` keys the remedy on `m.ledger`, which was already on every measurement. Neither remedy is dropped -- each prints in the branch that owns it. TEST_DEBT keeps both of its real routes and names the precondition plus `pnpm check:type-source-resolution`, the command that decides which one is available: a message that needs a second gate's source read to act on has not fixed what this card names. No verdict and no number moves. Graduation candidates remain a note, the structural run's output is byte-identical, and the `--lower` clause is pinned across all branches as the half that must not move. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
os-steve
commented
Aug 24, 2026
ACCEPT. Verified by content on ⭐ You did better than the instructionI said: re-derive the counterexample, and if it no longer reproduces, stop and report so I could re-scope. The named subject had gone stale — The result inverts the card:
So the card called it "a worked counterexample." It is the majority case. A message whose two offered remedies are, for four out of five readers, either useless or actively harmful is not imprecise — it is wrong for most of the people who see it. ⭐ And remedy 1's no-op has a structural reason, not just a count: every TEST_DEBT entry already declares a The third misfire is new and I confirmed itNobody had named this one. The workspace root's So a taker following the old note verbatim at the root would overwrite every other package's typecheck — and this gate would go green while they did it. A remedy that is silently destructive is worse than one that is merely useless, and it was hiding behind the same merged sentence. (I could not separately confirm the The fix keys on data that was already there
An unrecognised ledger inherits neither — refusing rather than guessing. Rendered evidence, as requiredThree cases both directions, through ⭐ The ⭐ Two self-corrections, and the second states a general rule
That last sentence is the sharpest instrument note anyone has written in this lane today, and adding Repo-wide Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11491
The
TEST_DEBT/DEBTgraduation note offered both ledgers' remedies in one sentence, joined by an "or" that says they are alternatives a reader may pick between. They are not alternatives: each belongs to exactly one ledger, and the gate's own header three screens above states the distinction the message dropped — DEBT is "src does not check", TEST_DEBT is "src checks, tests are hidden".The card's evidence is one package. This PR re-derived it across both ledgers.
The counterexample, re-derived on current
origin/main@objectstack/trigger-record-change— the card's subject — graduated, so its own instance is spent. The shape it demonstrated is not: it reproduces on the entries still in the ledger, and it is the majority case rather than the exception.Measured at
e47d5ef61, by dropping each entry's"**/*.test.ts"exclusion from itstsconfig.jsonand readingcheck:type-source-resolution, one package at a time, restoring between each (script and per-package output in the report on #11491):typecheckscript"objectql,lint,formula,verify). The 19th (cli) has no exclusion to drop — its tests are hidden by anincludethat never reaches them, so the remedy names an edit that does not exist for itSample of the failure the old note walks the author into (
plugin-approvals, exclusion dropped):That registry is shrink-only, so the author who followed the note arrives at a second gate with no remedy at all — and nothing in this gate's output ever named it.
On DEBT the sentence misfires once more, on the entry easiest to get wrong: the workspace root's
typecheckslot is the aggregator (turbo run typecheck) and its own TypeScript is read throughtypecheck:root. A taker following the old note verbatim overwrites every other package's typecheck with a baretsc --noEmit, and this gate goes green.The evidence is the rendered text, both ways
Rendered through
evaluateMeasurements()— the same pure function--re-measurecalls and--self-testpins.Before — three different situations, one message. Only the name and the number differ:
After — case A, the counterexample class (TEST_DEBT):
After — case B, where the other remedy is the right one (DEBT):
After — case C, the workspace root, where the DEBT remedy is right but the script name is not:
Why this shape
Neither remedy is dropped.
m.ledgerwas already on every measurement, so each remedy simply prints in the branch that owns it. Within TEST_DEBT there is a real choice of route, so that branch keeps both and names the precondition pluspnpm check:type-source-resolution— the command that decides which one is available. A message that is correct but needs a second gate's source read to act on has not fixed what the card names.An unrecognised ledger inherits neither remedy, by design: a third ledger silently receiving DEBT's advice is how this message was wrong for TEST_DEBT for its whole life.
Controls
problems.length === 0alongside its note.origin/main's:node scripts/check-type-check-coverage.mjs,diffagainst the pre-change capture, exit 0.check:type-source-resolutionis byte-identical before and after, exit 0 both ends — the sweep above restored every file it touched, andgit statuswas clean afterwards.--lowerclause is ledger-independent and is pinned across all three branches by a dedicated assertion. In the ablation below it is the one thing that stayed green while the remedy assertions went red.Non-vacuity
The self-test additions are paired — a remedy that must be present and the other ledger's remedy that must be absent — because a presence-only regex stays green if the two branches are ever re-merged into one sentence, which is exactly the state this replaces.
Ablation:
graduationRemedy()collapsed back to the old merged sentence (--lowerclause deliberately retained), mutation proved on disk before reading anything (injected marker count 1, remedy text occurrences 2 → 1), syntax checked so the red could not be a load error:Restore leg proved on disk too: marker count back to 0, remedy text back to 2, self-test exit 0. The
--lowercontrol did not fire in either leg.An earlier attempt at this ablation produced exit 1 from a shell-quoting SyntaxError rather than from the assertions; that reading was discarded and the ablation redone. Recording it because it is the exact failure shape where a red reads as proof and is not.
Verification
At
5b36713b5(final commit; the union was re-run on this head):check:type-check-coverage(incl.--self-test)check:nul-bytescheck:entry-guardcheck:parse-guardcheck:agent-test-spellingcheck:cross-package-test-inputs(both lint.yml and ci.yml forms)check:pnpm-filter-targetspnpm lint(eslint . --no-inline-config, repo-wide)--format jsonGate set derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no paths passed; it read the change set itself), re-derived after the commit — same 8 families.One declared narrowing:
check:type-check-debt(--re-measure) was not run locally. It refuses without a built dependency closure — by design, and unchanged by this PR, since that refusal fires inmeasureLedgers()before any measurement. CI runs it afterturbo run build, which is where theisRootwiring is exercised end to end; the branch content is pinned by--self-test, which CI also runs. Verbatim refusal:No changeset:
scripts/**publishes nothing, matching the precedent of the last several scripts-only PRs (#11824, #11815) — labelledskip-changeset.Generated by Claude Code
Generated by Claude Code