Repository navigation
fix(scripts): floor the derived population in the engine-double and type-check gates - #17099
Merged
Merged
Conversation
…ype-check gates Both gates could report a clean tree over a scan that read almost none of it. Their only protection was emergent — the engine gate's zero-checks catch a total collapse but not a partial one, and the type-check gate's only guard is its ledgers reconciling, which disappears when a ratchet reaches zero. Two of that gate's six ledgers are already empty. Adds the `floorProblem()` shape check-dual-build-cjs-loads.mjs carries, over the DERIVED population in each gate — never over a ledger, whose shrinking is the goal — plus a provenance line on every green run so the record cannot silently stop describing the tree. Both refuse with a distinct exit code 2. Measured on 854639b: engine testFiles 3503 · productionFiles 2596 · discoveredFiles 911 · pinnedRows 779 type-check packages 80 · walkedTestFiles 3503 · walkedSourceFiles 2521 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
baozhoutao
marked this pull request as ready for review
September 9, 2026 09:03
baozhoutao
enabled auto-merge
September 9, 2026 09:04
This was referenced Sep 9, 2026
baozhoutao
pushed a commit
that referenced
this pull request
Sep 14, 2026
…ing run #17099 gave check-engine-double-contract a population floor so a run that read almost nothing REFUSES instead of reporting on the fraction it reached. That floor was spelled inline in report(), and `--write` is a sibling branch of the same dispatch chain: it re-scanned and rewrote both ledgers with nothing in front of it. A scan that silently under-collected, run through the regeneration command this gate's own RETAINED messages prescribe, baked the smaller population into the pinned and seam ledgers as the new truth — the floor protected the reader and left the writer free to destroy the thing being protected. One shared refusePopulationFloor() now turns a fallen floor into an exit, and it is the only thing in the file that does. report() and writeLedger() both call it; writeLedger() calls it before the first writeFileSync, so a refused regeneration leaves the ledgers that were already on disk. Not a second copy of the floor: populationFloorProblem stays the only decider. `--census` deliberately stays out — it reaches no verdict and writes nothing. The floor is over the DERIVED population and never over a ledger's size, so the documented bootstrap still holds: a fresh checkout with the artifacts absent walks the whole tree, clears all four counts, and writes them. A new self-test battery reads this file's own source and pins the wiring: both branches route through the helper, the write call precedes the first writeFileSync, and exactly one place turns a fallen floor into an exit. The 23 existing floor cases all passed while `--write` never called it — driving a pure decider cannot say which branches reach it. The POPULATION FLOORS header's one-package reproduction is re-judged in place rather than deleted: the reading it establishes is unchanged and is why the floors exist, but its `--write` step is now a refusal. Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…opulation floor as the reporting run (objectstack-ai#18097) Clause-②: no Closes objectstack-ai#17117 objectstack-ai#17099 gave `check-engine-double-contract` a **population floor** so a run that read almost nothing REFUSES instead of reporting on the fraction it reached. That floor was spelled inline in `report()`, and `--write` is a **sibling branch of the same dispatch chain** — it re-scanned and rewrote both ledgers with nothing in front of it. **The floor protected the reader and left the writer free to destroy the thing being protected.** ## What changed One shared `refusePopulationFloor()` is now the only thing in the file that turns a fallen floor into an exit. `report()` and `writeLedger()` both call it; `writeLedger()` calls it **before the first `writeFileSync`**, so a refused regeneration leaves the ledgers that were already on disk and says so. **Not a fourth copy** — see the ordering note below. `populationFloorProblem` stays the only decider; the file goes from one inline refusal to one helper with two call sites. `--census` deliberately stays out: it reaches no verdict and writes nothing (objectstack-ai#9747's ruling), so it has nothing to destroy; the day it writes or decides anything it joins the list rather than growing a third refusal. The floor is over the **derived population** and never over a ledger's size, so the bootstrap both ledger readers document still holds: a fresh checkout with the artifacts absent walks the whole tree, clears all four counts, and writes them. ## Verdict on today's ledger — CORRECT, nothing restored Triage pinned this as the prerequisite: if a previous `--write` already baked an under-collected population, the pin is wrong today and restoring it is part of the fix. It is not wrong. Measured on the unmodified tree at the branch point (`66e34d14d9`), before any edit: ``` check-engine-double-contract: OK — 875 pinned, 133 in the DEBT ledger, 3 exempt. check-engine-double-contract: 800 (file, verb) row(s) held by the RETAINED ledger provenance — testFiles/productionFiles/discoveredFiles/pinnedRows: this run 3685/2732/932/800 · floors 2800/2000/700/600 · derived from 3503/2596/911/779 measured on 854639b (+182/+136/+21/+21 vs the record) ``` Three legs, and the first is decisive rather than circumstantial: 1. **The reporting run reconciles the ledger against the LIVE census in both directions** and exits 0. The growth direction errors once per row the census finds and the ledger does not; the loss direction errors once per row the ledger records and the scan no longer sees. A green run therefore means the ledger's set **equals** today's census row for row — a baked-down ledger could not produce it. This is a measurement of the ledger, not an inference about its history. 2. **Every population count is above its floor and above the record** it was derived from (+182/+136/+21/+21). A collapsed scan is not what produced this ledger. 3. **The ledger's row count across the 25 most recent commits that touched it** climbs 765 → 800 with one legitimate 6-row dip (782 → 776 on 2026-09-10). No collapse anywhere in that window.⚠️ This checkout is shallow, so leg 3 is a positive reading over the window it holds and is **not** an absence claim over all time; legs 1 and 2 do not depend on history at all. ⇒ **Nothing was restored, because nothing needed restoring.** The ledgers are byte-identical to `origin/main` in this PR — the diff is one file. ## Positive control — `--write` refuses instead of writing a shrunken ledger Acceptance ⭐: without this, floor-added and floor-not-added look identical. Run from the committed state; the under-collection is the same one-package collapse the POPULATION FLOORS header was measured on (`SCAN_ROOTS` restricted to `packages/mcp`). **Leg A — floor wired, scan restricted.** `--write` exits **2** (`EXIT_POPULATION_REFUSED`): ``` check-engine-double-contract REFUSES — measured only 31 test file(s) offered by the discovery walk, below the floor of 2800 (3503 on 854639b). ⛔ NOTHING WAS WRITTEN. The pinned and seam ledgers on disk are the ones that were already there — a regeneration from this scan would have recorded the collapse as the new population. pinned ledger after: 5f8d55b… (HEAD 5f8d55b…) ✓ BYTE-IDENTICAL seam ledger after: 984cd43… (HEAD 984cd43…) ✓ BYTE-IDENTICAL ``` **Leg B — ablation, the writer's floor call removed, same restricted scan.** `--write` exits **0** and rewrites both ledgers: ``` gate hash moved f413d458… -> 18fbbf78… ✓ the ablation is on disk --write exit=0 pinned ledger after: 993281c0… (HEAD 5f8d55b…) ✓ REWRITTEN shrunken ledger rows: 15 (was 800) shrunken seam rows: 2 (was 6) ``` 15 pinned rows where the tree has 800 — the exact collapse objectstack-ai#17099's dev measured and the card was filed on. Leg B is what makes Leg A evidence rather than a tautology. **Restore settled by bytes, ⛔ never by an exit code** (the container's `trap` can silently not fire — objectstack-ai#17875; `trap` is present as a crash-path convenience only and is not the proof): ``` gate eee738f… vs HEAD eee738f… pinned 5f8d55b… vs HEAD 5f8d55b… seam 984cd43… vs HEAD 984cd43… ✓ git diff HEAD is EMPTY ``` An empty `git hash-object` output is treated as FAILURE, not as "nothing to compare", and the mutation legs prove they landed by counting the anchor text before and after — an editor's exit code is not on-disk evidence. ## Order taken, and the shape — re-stated because triage asked **This card first, objectstack-ai#17118 not touched**, per the PM's ruling. objectstack-ai#17118 records that the floor mechanism exists three times under two spellings across the gate family. **This implementation is not a fourth copy of the floor.** It adds no new floor anywhere: it de-duplicates *within* this file, going from one inline refusal to one helper invoked from two branches. Net copies of the floor in the tree are unchanged. A self-test case asserts the shape directly — **exactly one place in this file turns a fallen floor into an exit** — so a future branch that grew its own refusal reds rather than passing. ⇒ **This leaves objectstack-ai#17118 strictly easier, not harder**: its consolidation now re-points one helper in this file instead of two inline blocks. I do **not** judge that the shared helper had to land first, and I did not widen. ## Self-test — the wiring, not the decider The 23 existing floor cases all passed while `--write` never called the floor. That is the shape of the defect: driving a pure decider cannot say which branches reach it. A new battery (`Every branch that SCANS routes through the floor (objectstack-ai#17117)`, 6 cases) reads this file's own source — same instrument as the existing `PROVENANCE — the pass path in report() still prints it` case — and pins that both branches route through the helper, that the write call precedes the first `writeFileSync`, and that exactly one place exits on a fallen floor. A control case asserts the body reader finds a body at all, so the four scoped assertions cannot pass vacuously. ## Reverse-read — including the zeros *What in the tree does this change make false?* - **Re-judged in place, ⛔ not deleted:** the POPULATION FLOORS header's one-package reproduction recipe. Its `--write` step now meets a refusal. The reading it was taken to establish is unchanged and is still why the floors exist; the note says which half is history and which half still holds. - **The gate's own self-test verdict sentence** now names the routing clause it covers. *What states that the floor applies only to the reporting run?* **ZERO outside the gate.** `git grep` for the scope phrasing across the tree returns one unrelated hit in `check-undeclared-dep-imports.mjs` (its own floors' provenance wording). The only prose that carried the scope was inside this gate, and both places are handled above. *What describes this ledger's population?* **ZERO prose statements of its size.** The only external mentions are three per-file comments (`plugin-security/src/permission-set-drift.test.ts`, `permission-set-overlay-discard.test.ts`, `runtime/src/standalone-stack-seeder-declaration-copy.test.ts`) that each describe **their own row**, not the population — unaffected. The `800` and `875` figures are computed at runtime, never written into prose. *Reverse direction — anything that was false and is now true?* The two ledger `$comment` strings are the only places outside the gate naming `--write` ("Regenerate with … ; never hand-edit"). They were true before and remain true: the command is unchanged, it now refuses on a collapsed scan. Both files are byte-identical in this PR. ## Verification All against `e7427b155c`, the final commit. - `pnpm check:engine-double-contract` — **exit 0**, `OK — 875 pinned, 133 in the DEBT ledger, 3 exempt`, provenance `3685/2732/932/800`. - **32 of 32** derived gate families run, all exit 0. `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran …` → `✓ 32 derived famil(ies) accounted for — 32 run, 0 NOT-MEASURED (a DERIVED zero — all 32 recorded an exit code and none of them is 3)`. - `node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD` — exit 0. - Control-character self-scan over the edited file — `grep` exit 1 (clean), captured before any pipe. - Exit codes read from each gate's own printed verdict line with `$?` captured before any pipe, ⛔ never through one. Repo-wide scans (`pnpm lint` and the 51 artifact-roster, 11 wide-population, 14 pending-changeset families and the path-scheduled CI job the derivation names) are **CI's runs, declared not run locally** — they are outside the derived set by that tool's own accounting. ## Changeset — SKIP, measured `.changeset` skipped and the **`skip-changeset` label** applied (a label, not a body sentence). Measured rather than assumed: the changed path lies inside **0** of the repo's **70** published package directories, and the repo root package is `private: true`. Positive control — `packages/objectql/src/engine-delete-dispatch.ts` resolves into exactly one published package whose `files[]` is `["dist","README.md","CHANGELOG.md"]`, so the instrument does find a published file when one exists. Nothing any package ships moves. ## 验收备注 - `--census` (the third sibling branch) is left alone deliberately, and the reason is in the code: it reaches no verdict and changes no exit code, so it is a reporter with nothing to destroy. Noted in the helper's doc comment as the criterion for when it would join. - Triage recorded that `check-type-check-coverage`'s `--re-measure` / `--lower` branches may hold the same shape and that the ordering there was **not** established. I did not widen into it; it is objectstack-ai#17118's neighbourhood and the PM ruled against widening. --- _Generated by [Claude Code](https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU)_ Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
… one module (objectstack-ai#19174) Fixes objectstack-ai#17118 Clause-②: no `scripts/**` only — four files, no `packages/**` source, no published surface, no workflow file. Re-derived rather than recalled: `node -e "import('./scripts/pm/check-governed-merges.mjs')…"` prints today's register as `docs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md · docs/NORTH-STAR.md`, and `check-governed-merges.mjs --test` answers **exit 0 (NOT governed)** for each of the four paths. ## ⭐ Two corrections to the card's framing, both measured ### ① The extraction surface is THREE implementations and FOUR production call sites — not "four copies" Triage asked which order this landed in and warned that landing second means "extracting over four copies rather than three". objectstack-ai#17117 closed `completed` on 2026-09-14, so the order was settled by events: this is the second landing. But objectstack-ai#17117 did **not** add a fourth copy of the mechanism. It added `refusePopulationFloor()` — one exit funnel in the engine gate, whose own doc comment says so: > ⛔ NOT a second copy of the floor: `populationFloorProblem` is still the only thing that decides, and > this is the only thing that exits. Measured on `c229223e9`, the three declared gates carry **3 definitions** of the row-walk and **4 places a real run turns it into a verdict**: | gate | definition | production call sites | | :--- | :--- | ---: | | `check-engine-double-contract.mjs` | `populationFloorProblem` (4 rows) | 2 — `report()` and `--write`, both via `refusePopulationFloor` | | `check-type-check-coverage.mjs` | `populationFloorProblem` (3 rows) | 1 | | `check-dual-build-cjs-loads.mjs` | `floorProblem` (5 rows) | 1 | ⇒ All three definitions and all four call sites are covered. Nothing is omitted, so triage's "a helper extracted over two of four copies is worse than none" is satisfied by construction rather than by judgement. ### ② The population is SIX gates, not three — and the drift triage predicted has already happened Triage recorded what it had not swept: *"I did not sweep for other gates carrying an unnamed floor of the same shape."* Swept here. Re-counted on `c229223e9` by **which spellings live in each file**, as the deferral note asked: | file | `populationFloorProblem` | `floorProblem` | same mechanism? | | :--- | ---: | ---: | :--- | | `scripts/check-engine-double-contract.mjs` | 25 | 0 | yes — in this PR | | `scripts/check-type-check-coverage.mjs` | 19 | **4** | yes — in this PR | | `scripts/check-dual-build-cjs-loads.mjs` | 0 | 15 | yes — in this PR | | `scripts/check-manifest-repository-directory.mjs` | 0 | 17 | yes — ⛔ NOT in this PR | | `scripts/check-keyed-text-bounds.mjs` | 0 | 8 | yes — ⛔ NOT in this PR | | `scripts/check-undeclared-dep-imports.mjs` | 0 | 7 | yes — ⛔ NOT in this PR | | `packages/spec/scripts/*.ts` (4 files) · `check-comment-mask-corpus` · `check-osv-exemptions` · `typecheck-configs` | 0 | 3–7 each | **no** — `floorProblems` there is the local of `batteryFloorFailures()`, the self-test battery floor: a different family, same word | ⭐ The deferral note's reading is confirmed and refined: `check-type-check-coverage.mjs` carries both spellings (19 + 4), but its 4 `floorProblem` hits are the **battery** family at `:7539`, not a second population floor. So the honest statement of the defect is not "three implementations, two spellings" and not "one file holds both spellings of the population floor" — it is **six implementations across two spellings, and the refusal wording has already drifted into five distinct forms**: | wording | gates | | :--- | :--- | | `measured only …` + the two-sentence ⛔ tail naming row independence | engine, type-check | | `measured only …` + a one-sentence ⛔ tail | dual-build | | `measured only …` + a different two-sentence ⛔ tail ("Find what stopped being read") | manifest-repository-directory | | `discovered only … (measured N on ref)` — **no ⛔ line at all** | keyed-text-bounds | | `discovered only …` / `read only …` / `extracted only …`, one hand-written per row | undeclared-dep-imports | That is exactly the failure the card predicted, already materialised: five ways of explaining one refusal to one reader. This PR reduces six copies to four and five wordings to three. The remaining three gates are reported for the seat rather than swept into this diff — see **Acceptance notes**. ## The change `scripts/population-floor.mjs` — a pure module owning the row-walk, the refusal format and the provenance line. `definePopulationFloor({ ref, rows, provenance?, reproduce? })` returns the two functions each gate already calls, under one spelling, so **every existing call site and every self-test case reads unchanged**. ⛔ **The row tables stay in their gates**, per objectstack-ai#17099's dev's note, which triage adopted as the ruling and which I read before writing anything (PR objectstack-ai#17099 body, *"Do the two gates want the same floor shape?"*): > the honest extraction is the row-walk plus the provenance formatting, parameterised by the row table; > the row tables themselves must stay in their own gates, because each `why` is a claim about that gate's > internals. Row shape is `{ key, min, measured, what, why, at? }`. The three tables keep their own `why` prose verbatim — no row was reworded, merged or moved. Two parameters exist only because a real gate needs them, not on principle: `provenance` (dual-build floors `typedJudged` but cannot give it a column, because its number comes from a second census on a different commit and the line carries one ref) and `at` (that row names its own ref, which is where the refusal quotes it from). **`check-dual-build-cjs-loads`'s older spelling is retired**: `floorProblem` → `populationFloorProblem`, `provenanceLine` → `populationProvenanceLine`, including the byte-reading self-test pin that quotes the identifier as a string. No importer existed to break — `git grep` finds only `package.json` running the script. ⛔ No fifth spelling was introduced: the module's exports ARE the one spelling. ### The one deliberate text change dual-build's refusal tail was `⛔ NOT a pass: nothing, or nearly nothing, was read.` It now carries the engine/type-check tail, which adds *"This says WHICH population fell and nothing about why the others stand — they are reported by their own rows."* That sentence is true of dual-build too (5 rows, first-row-wins), so converging loses nothing and is the point of the card: the wording an operator acts on becomes one wording. Nothing asserts the old tail; its self-test pins the first sentence by regex, which is unchanged. ### ⛔ No `--self-test` of its own, and why The four shared `scripts/` modules lint.yml runs a self-test for (`invoked-as`, `ts-parse`, `js-comment-mask`, `import-prerequisite`) are pinned at the module because the gates that route to them are **spelling** gates — they assert routing and never behaviour, so nothing downstream checks that `ts-parse` still refuses. That is not the shape here: all three importers drive these two functions as pure functions over their own row tables and assert the **output** — the refusal text, the ref it cites, which row wins, that a missing count is zero, and the provenance deltas in both directions. After this extraction every one of those assertions exercises the one implementation. A fifth `run_self_test` line would put a workflow file in this diff and falsify lint.yml's own "all four ran and passed" line, to buy coverage that already exists three times over. Recorded in the module header as a considered omission. The precedent for the shape is `scripts/glob-match.mjs`: *"This module is deliberately not a gate, so it has no CI invocation of its own: its coverage is that its importers run `--self-test`."* ## Verification Gate families re-derived in this worktree against the real changed set: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` → **32 commands** (4 paths, 531 changed lines vs merge base `c229223e9`).⚠️ **"all derived families green" is not "CI green."** The same derivation names, outside those 32: 52 artifact-roster families whose silence is a fact about a list, 11 declared-wide-population families, 14 that apply once a changeset exists, 2 that take a value from the workflow, and one path-scheduled CI job. CI is the authority on those. All three edited files were run **both ways**, bare and `--self-test`, because each is itself a gate: | gate | `--self-test` | bare | | :--- | :--- | :--- | | `check:engine-double-contract` | exit 0 | exit 0 — `885 pinned, 133 DEBT, 3 exempt`; provenance `3771/2786/940/808 · floors 2800/2000/700/600 · +268/+190/+29/+29` | | `check:type-check-coverage` | exit 0 | exit 0; provenance `81/3771/2710 · floors 64/2800/2000 · +1/+268/+189` | | `check:dual-build-cjs-loads` | exit 0 — **93 cases**, real emitted bytes, real spawns, every vacuity floor driven to zero with its green control, the record's ref printed on the pass path | see below | Both provenance lines are byte-identical in form to the pre-change runs, on the real tree.⚠️ **`pnpm check:dual-build-cjs-loads` bare answers exit 3 — `PREREQUISITE NOT MET`, not a red.** It needs a real `dist/` and says so in its own header (*"it is a step in **Build Core** (ci.yml)"*); on an unbuilt worktree it lists 96 missing `dist/` paths and prints *"⛔ This is NOT a pass: nothing was measured."* `pnpm check:type-check-debt` answers **3** for the same class of reason (it is the `--re-measure` path and needs a built workspace closure). Both are declared to CI. The 93-case self-test is the half that reaches this diff's subject, and it spawns the real gate over a real fixture tree.⚠️ **No new TypeScript program sees anything new.** The new import relationship is ESM-only: the repo root declares no `typecheck` script, and the root `tsconfig.json` sets no `allowJs`, so no `scripts/**.mjs` file is in any tsc program. The four type-check lanes are therefore not owed per-package runs for this diff — stated rather than assumed, because `dispatch-gates` has no bucket for them. ## Changeset None, and `skip-changeset` is measured rather than asserted: across **70** published manifests, **0** `files[]` entries name `scripts/`; positive control, **23** name `dist/`. The repo root package is `private: true`. ⇒ nothing published moves. ⛔ No label applied from here — reporting it for the PM. ## Acceptance notes ⛔ Not filed, not fixed here — noted for the seat: - **The same mechanism lives in three more gates** — `check-manifest-repository-directory.mjs:773`, `check-keyed-text-bounds.mjs:964`, `check-undeclared-dep-imports.mjs:592` — each with its own `provenanceLine`, and the refusal wording has already drifted into three further forms (table above). This is the population triage named as un-swept. ⛔ Deliberately out of this diff: each is a gate this card does not otherwise touch, each brings its own self-test battery and pinned case floor into the verification surface, and the card warns in its own words that overselling it *"will produce a refactor with more churn than value"*. `check-undeclared-dep-imports`'s copy is not even table-driven (three hand-unrolled `if`s with three different verbs), so absorbing it is a behaviour change to its text, not a move. Successor: whoever takes the next population-floor card touches one of these files. - **`floorProblems` means two different things in `scripts/`** — the population floor, and the local of `batteryFloorFailures()` (self-test battery floors, ~31 files). Each is internally consistent; the collision is in the word, and it is what made the original count read as three. Naming only. --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk --- _Generated by [Claude Code](https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk)_ Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #13014
Implements the audit's residual R1 and nothing else. The card as filed proposed "an extractor that returns nothing must be an ERROR"; that is already the law in all four gates it named, and its flagship instance was misdiagnosed — the rebuttal is already in the tree at
scripts/check-stack-collection-maps.mjs:30-45. Not implemented as written, per the re-scoping in the four-gate audit.Clause-②: no
scripts/**only — nopackages/**source and no published surface. Re-derived rather than copied:node -e "import('./scripts/pm/check-governed-merges.mjs')…"prints today's register asdocs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md, and neither changed file is in it.What R1 actually was, per gate — the brief's premise held for one gate and not the other
The brief stated both gates "exit 0 when the scan collects nothing AND the ledger is empty". Measured, not assumed:
check-type-check-coverageOK — 0/0 workspace packages type-checkedcheck-engine-double-contractDISCOVEREDx3 +SEAMS_DISCOVERED), ledger-independentlyDISCOVEREDandSEAMS_DISCOVEREDare unconditional zero-floors on the derived population. So the engine gate's residual is narrower and different: a partial collapse, which no zero-check can see — and it is reachable by the remedy the gate's own error text prescribes.The engine gate's real blindness, measured end to end. A tree carrying one package of eighty (
packages/mcp) with all three ledgers emptied:node scripts/check-engine-double-contract.mjs --writeand commit."check-engine-double-contract: OK — 15 pinned, 0 in the DEBT ledger, 0 exempt.15 pinned rows where the whole tree has 779, from a walk that read 26 test files where the whole tree has 3,503. Every verdict this gate reaches is a statement about the set discovery handed it.
The type-check gate's blindness is the brief's R1 verbatim. Its only guard is ledger reconciliation, and that guard is on a timer:
PHANTOM_PIN_DEBTandUNCHECKED_SOURCE_DEBTare already{}on854639b311— the ratchet reached zero and took its share of the floor with it, as designed, with nothing anywhere saying so.The change
The
floorProblem()shape fromcheck-dual-build-cjs-loads.mjs, over the DERIVED population in each gate, plus aprovenanceLineon every green run and a distinct exit code2for a refusal (a finding stays1; the type-check gate's prerequisite stays3).⛔ No floor is over a ledger. A ledger floor would red the ratchet's own success, which is the defect and not the repair. The type-check gate asserts this mechanically rather than only in prose (
the census names only derived populations, never a ledger).Measured on
854639b311— every number, and how it was taken. All eight come from running each gate with its floors installed and reading its ownprovenanceline. Controls in the report; the two walk counts were each independently reproduced by afindpipeline to the exact integer, with a nonsense control returning 0.testFiles— the discovery walkproductionFiles— the seam walkdiscoveredFiles— (file, verb) pairs parsedpinnedRows— the RETAINED censuspackages— the workspace enumerationwalkedTestFiles— the per-package walk, testswalkedSourceFiles— the same walk, sourcesEach row measures a different stage, not one number several times — walk, then parse, then guard-recognition on the engine side; enumeration, then each half of the per-package walk on the type-check side. Two considered omissions are recorded in the source rather than left silent: no floor on the engine gate's
seamCount(population 6, any width would be invented), and none on the type-check gate'scovered(already protected — if manifest reading broke,COVEREDfires for every package in the same run).Acceptance evidence
diffs identical to the pre-change run apart from the addedprovenanceline.packages/mcp— exit 1, threePINNEDfindings, 0REFUSESlines. type-check: a new uncovered workspace package — exit 1, two findings, 0REFUSESlines. Neither floor fires on a healthy tree.testFilesrow must matchdiscovery walkand must not matchseam walkorRETAINED census.Do the two gates want the same floor shape? (acceptance 6)
The mechanism is identical; the populations are not, and neither is what they are protecting against. Both use one frozen
MEASURED_POPULATIONrecord, siblingMIN_constants, a pure first-row-winspopulationFloorProblem, apopulationProvenanceLineon green, and exit2. But the engine gate floors walk to parse to pin overpackages/+examples/recursively, and its gap is a partial collapse laundered through--write; the type-check gate floors enumeration to walk over thepnpm-workspace.yamlmembers, and its gap is a total collapse once the ledgers empty. Row counts differ (4 vs 3) and no row is shared.⇒ A shared helper is warranted, and ⛔ it is deliberately not built here — that is a third card, per the brief. Worth noting for whoever takes it: the honest extraction is the row-walk plus the provenance formatting, parameterised by the row table; the row tables themselves must stay in their own gates, because each
whyis a claim about that gate's internals.Hazards — ⛔ this list is not complete
Swept: that neither floor reds on
origin/main(both=on every provenance column); that no floor exceeds its own record (asserted in both self-tests); that the green text is otherwise byte-identical; that a real finding still reds with the floor silent, in both gates; control bytes in both files (gate plus an independentgrep -naPsweep, both clean); the full derived gate family (33 of 33 accounted for bydispatch-gates --ran, 0 UNRUN); repo-wideeslint . --no-inline-config, exit 0.One real finding my own change produced, and fixed:
check:entry-guardrefuses ascripts/**file that exports a binding and runs on import. The precedent exports these two functions because it already guards its dispatch withisEntrypoint; retrofitting that to two large gates' argv handling is out of scope, and nothing imports them (the self-tests are in the same modules), so they are module-local and the rule no longer reaches either file. The reasoning is written beside each function so it is not re-added.NOT swept, and named:
pnpm check:type-check-debtexits 3 —PREREQUISITE NOT MET, not a finding. It is the--re-measurepath and needs a built workspace closure this container does not have. It reads no code path this diff touches. Declared to CI. I also did not audit either gate on any axis but this one — not their CI wiring, not their other invariants, not their remaining self-test coverage.Drift already visible:
origin/mainadvanced toc43bac7048(#17047) while this was in flight, adding one test file and one fixture. That moves the population by +1 and the provenance line will print+1instead of=— which is the design working, not a defect. The records stay pinned to854639b311, the commit they were actually taken on.Changeset
None.
scripts/reaches 0 published manifests, so nothing published moves. IfCheck Changesetreds, ⛔ no label applied from here — reporting it for the PM to applyskip-changeset.🤖 Generated with Claude Code
https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
Generated by Claude Code