Skip to content

fix(scripts): floor the derived population in the engine-double and type-check gates - #17099

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-13014-gate-population-floors
Sep 9, 2026
Merged

baozhoutao merged 1 commit into
mainfrom
claude/issue-13014-gate-population-floors

Conversation

@claude

@claude claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

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 — no packages/** source and no published surface. Re-derived rather than copied: node -e "import('./scripts/pm/check-governed-merges.mjs')…" prints today's register as docs/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:

gate scan collects nothing + ledgers empty, BEFORE this PR verdict
check-type-check-coverage exit 0, prints OK — 0/0 workspace packages type-checked premise HOLDS
check-engine-double-contract exit 1, four named refusals (DISCOVERED x3 + SEAMS_DISCOVERED), ledger-independently premise FALSE

DISCOVERED and SEAMS_DISCOVERED are 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:

  1. first run — exit 1, 17 findings, each ending "Run node scripts/check-engine-double-contract.mjs --write and commit."
  2. doing exactly that, then re-running — exit 0, 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_DEBT and UNCHECKED_SOURCE_DEBT are already {} on 854639b311 — 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 from check-dual-build-cjs-loads.mjs, over the DERIVED population in each gate, plus a provenanceLine on every green run and a distinct exit code 2 for a refusal (a finding stays 1; the type-check gate's prerequisite stays 3).

⛔ 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 own provenance line. Controls in the report; the two walk counts were each independently reproduced by a find pipeline to the exact integer, with a nonsense control returning 0.

gate population measured floor headroom
engine testFiles — the discovery walk 3503 2800 20%
engine productionFiles — the seam walk 2596 2000 23%
engine discoveredFiles — (file, verb) pairs parsed 911 700 23%
engine pinnedRows — the RETAINED census 779 600 23%
type-check packages — the workspace enumeration 80 64 20%
type-check walkedTestFiles — the per-package walk, tests 3503 2800 20%
type-check walkedSourceFiles — the same walk, sources 2521 2000 21%

Each 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's covered (already protected — if manifest reading broke, COVERED fires for every package in the same run).

Acceptance evidence

# requirement result
1 before leg measured above; simulated, not a blindness proof — the condition is not in the tree. How: fixture roots outside the worktree, with the gates' own scripts and empty scan roots. Reported honestly because the engine leg falsified the stated premise.
2 after: loud refusal; healthy tree unchanged collapse trees now exit 2 naming the fallen population. Healthy tree: exit 0, and the green text diffs identical to the pre-change run apart from the added provenance line.
3 negative control (mandatory) engine: an unpinned engine double planted in packages/mcp — exit 1, three PINNED findings, 0 REFUSES lines. type-check: a new uncovered workspace package — exit 1, two findings, 0 REFUSES lines. Neither floor fires on a healthy tree.
4 honest message each row's text names only its own stage. Asserted mechanically, both directions: a fallen testFiles row must match discovery walk and must not match seam walk or RETAINED census.
5 battery floors ratcheted to what runs engine roster 31 to 32, new battery pinned at its measured 23 cases; type-check roster 61 to 62, new battery at its measured 23. Both counts taken with a firing control (pin at 999, gate names the real number) and a nonsense control (each row asserted at its own floor, which must pass).
6 same shape or different? Different populations, same mechanism. Details below.

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_POPULATION record, sibling MIN_ constants, a pure first-row-wins populationFloorProblem, a populationProvenanceLine on green, and exit 2. But the engine gate floors walk to parse to pin over packages/ + examples/ recursively, and its gap is a partial collapse laundered through --write; the type-check gate floors enumeration to walk over the pnpm-workspace.yaml members, 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 why is 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 independent grep -naP sweep, both clean); the full derived gate family (33 of 33 accounted for by dispatch-gates --ran, 0 UNRUN); repo-wide eslint . --no-inline-config, exit 0.

One real finding my own change produced, and fixed: check:entry-guard refuses a scripts/** file that exports a binding and runs on import. The precedent exports these two functions because it already guards its dispatch with isEntrypoint; 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-debt exits 3 — PREREQUISITE NOT MET, not a finding. It is the --re-measure path 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/main advanced to c43bac7048 (#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 +1 instead of = — which is the design working, not a defect. The records stay pinned to 854639b311, the commit they were actually taken on.

Changeset

None. scripts/ reaches 0 published manifests, so nothing published moves. If Check Changeset reds, ⛔ no label applied from here — reporting it for the PM to apply skip-changeset.


🤖 Generated with Claude Code

https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU


Generated by Claude Code

…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
@github-actions github-actions Bot added the size/l label Sep 9, 2026
@baozhoutao baozhoutao added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 9, 2026 — with Claude
@baozhoutao
baozhoutao marked this pull request as ready for review September 9, 2026 09:03
@baozhoutao
baozhoutao enabled auto-merge September 9, 2026 09:04
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit ad308fc Sep 9, 2026
35 of 36 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-13014-gate-population-floors branch September 9, 2026 09:27
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

2 participants