Commit 83649b8
refactor(scripts): the population-floor mechanism is spelled once, in one module (#19174)
Fixes #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". #17117 closed `completed` on 2026-09-14, so the
order was settled by events: this is the
second landing. But #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 #17099's dev's note, which
triage adopted as the ruling and
which I read before writing anything (PR #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`).
1 parent 89377ce commit 83649b8
4 files changed
Lines changed: 308 additions & 223 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
190 | 190 | | |
191 | 191 | | |
192 | 192 | | |
193 | | - | |
| 193 | + | |
| 194 | + | |
194 | 195 | | |
195 | 196 | | |
196 | 197 | | |
| |||
218 | 219 | | |
219 | 220 | | |
220 | 221 | | |
| 222 | + | |
221 | 223 | | |
222 | 224 | | |
223 | 225 | | |
| |||
519 | 521 | | |
520 | 522 | | |
521 | 523 | | |
522 | | - | |
523 | | - | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
524 | 529 | | |
525 | | - | |
526 | | - | |
527 | | - | |
528 | | - | |
529 | | - | |
530 | | - | |
531 | | - | |
532 | | - | |
533 | | - | |
534 | | - | |
535 | | - | |
536 | | - | |
537 | | - | |
538 | | - | |
539 | | - | |
540 | | - | |
541 | | - | |
542 | | - | |
543 | | - | |
544 | | - | |
545 | | - | |
546 | | - | |
547 | | - | |
548 | | - | |
549 | | - | |
550 | | - | |
551 | | - | |
552 | | - | |
553 | | - | |
554 | | - | |
555 | | - | |
556 | | - | |
557 | | - | |
558 | | - | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
559 | 535 | | |
560 | | - | |
561 | | - | |
562 | | - | |
563 | | - | |
564 | | - | |
565 | | - | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
566 | 541 | | |
567 | | - | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
568 | 545 | | |
569 | | - | |
570 | | - | |
| 546 | + | |
| 547 | + | |
571 | 548 | | |
572 | | - | |
573 | | - | |
574 | | - | |
575 | | - | |
576 | | - | |
577 | | - | |
578 | | - | |
579 | | - | |
580 | | - | |
581 | | - | |
582 | | - | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
583 | 573 | | |
584 | 574 | | |
585 | 575 | | |
| |||
1133 | 1123 | | |
1134 | 1124 | | |
1135 | 1125 | | |
1136 | | - | |
| 1126 | + | |
1137 | 1127 | | |
1138 | 1128 | | |
1139 | 1129 | | |
| |||
1167 | 1157 | | |
1168 | 1158 | | |
1169 | 1159 | | |
1170 | | - | |
| 1160 | + | |
1171 | 1161 | | |
1172 | 1162 | | |
1173 | 1163 | | |
| |||
1486 | 1476 | | |
1487 | 1477 | | |
1488 | 1478 | | |
1489 | | - | |
1490 | | - | |
1491 | | - | |
1492 | | - | |
1493 | | - | |
1494 | | - | |
| 1479 | + | |
| 1480 | + | |
| 1481 | + | |
| 1482 | + | |
| 1483 | + | |
| 1484 | + | |
1495 | 1485 | | |
1496 | | - | |
| 1486 | + | |
1497 | 1487 | | |
1498 | 1488 | | |
1499 | 1489 | | |
1500 | 1490 | | |
1501 | 1491 | | |
1502 | | - | |
1503 | | - | |
| 1492 | + | |
| 1493 | + | |
1504 | 1494 | | |
1505 | 1495 | | |
1506 | | - | |
1507 | | - | |
| 1496 | + | |
| 1497 | + | |
1508 | 1498 | | |
1509 | 1499 | | |
1510 | 1500 | | |
| |||
1525 | 1515 | | |
1526 | 1516 | | |
1527 | 1517 | | |
1528 | | - | |
1529 | | - | |
1530 | | - | |
| 1518 | + | |
| 1519 | + | |
| 1520 | + | |
1531 | 1521 | | |
1532 | 1522 | | |
1533 | 1523 | | |
1534 | 1524 | | |
1535 | 1525 | | |
1536 | 1526 | | |
1537 | 1527 | | |
1538 | | - | |
1539 | | - | |
| 1528 | + | |
| 1529 | + | |
1540 | 1530 | | |
1541 | 1531 | | |
1542 | | - | |
1543 | | - | |
| 1532 | + | |
| 1533 | + | |
1544 | 1534 | | |
1545 | 1535 | | |
1546 | 1536 | | |
| |||
0 commit comments