Skip to content

Commit 83649b8

Browse files
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`). ⚠️ **"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>
1 parent 89377ce commit 83649b8

4 files changed

Lines changed: 308 additions & 223 deletions

File tree

‎scripts/check-dual-build-cjs-loads.mjs‎

Lines changed: 67 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@
190190
*
191191
* So the repair is not enforcement. What was missing is that a GREEN run never
192192
* showed the reader the two numbers side by side, so the record could stop
193-
* describing the tree with nothing, anywhere, saying so. `provenanceLine`
193+
* describing the tree with nothing, anywhere, saying so.
194+
* `populationProvenanceLine`
194195
* prints both on every pass: the drift is a fact in the log now, not a
195196
* discovery.
196197
*
@@ -218,6 +219,7 @@ import { dirname, join, relative, resolve } from 'node:path';
218219
import { fileURLToPath } from 'node:url';
219220

220221
import { isEntrypoint } from './invoked-as.mjs';
222+
import { definePopulationFloor } from './population-floor.mjs';
221223

222224
// ── The self-test's own battery roster and floor (#13489) ──────────────────
223225
//
@@ -519,67 +521,55 @@ const MIN_TYPED_JUDGED = 88;
519521
const MEASURED_TYPED = Object.freeze({ ref: '196612a313', typedJudged: 102 });
520522

521523
/**
522-
* The first floor a run falls below, as a refusal message -- or `null` when
523-
* every count clears. Pure, so `--self-test` drives every floor with no tree.
524+
* The vacuity floors and the provenance line, over the row table THIS gate
525+
* declares. The row-walk, the refusal wording and the provenance formatting are
526+
* shared with the two other gates that carry the same mechanism
527+
* (`scripts/population-floor.mjs`); the rows stay HERE, because each `why` is a
528+
* claim about this gate's internals and is true of nothing else.
524529
*
525-
* @param {{entries?: number, packages?: number, cjsFiles?: number, probes?: number}} counts
526-
* @returns {string | null}
527-
*/
528-
export function floorProblem(counts) {
529-
const rows = [
530-
[counts?.entries ?? 0, MIN_ENTRIES, MEASURED.entries, 'published `require` entry point(s)',
531-
'The manifest walk or the `exports` resolver broke. With no entries nothing is required, nothing is parsed, and the gate prints what a clean tree prints.',
532-
MEASURED.ref],
533-
[counts?.packages ?? 0, MIN_PACKAGES, MEASURED.packages, 'publishable package(s)',
534-
'Entries were found but collapsed onto a fraction of the tree — the walk is reading part of `packages/`, not the whole of it.',
535-
MEASURED.ref],
536-
[counts?.cjsFiles ?? 0, MIN_CJS_FILES, MEASURED.cjsFiles, 'emitted CommonJS file(s)',
537-
'This is the PARSES population. `commonJsFilesUnder` matched (almost) nothing, so `node --check` ran over an empty set and every byte we emit went unread.',
538-
MEASURED.ref],
539-
[counts?.probes ?? 0, MIN_PROBES, MEASURED.probes, 'cross-format behaviour probe(s) run',
540-
'AGREES is the invariant loading alone cannot give you, and an empty probe table satisfies it vacuously.',
541-
MEASURED.ref],
542-
[counts?.typedJudged ?? 0, MIN_TYPED_JUDGED, MEASURED_TYPED.typedJudged, 'require entry point(s) JUDGED by TYPED',
543-
'This is the TYPED population — entries reached and answered, clean or not. It does not move when packages are defective, only when the row loop stops asking, so a fall here means TYPED went silent rather than that the tree got worse.',
544-
MEASURED_TYPED.ref],
545-
];
546-
for (const [got, min, measured, what, why, at] of rows) {
547-
if (got >= min) continue;
548-
return `measured only ${got} ${what}, below the floor of ${min} (${measured} on ${at}).\n`
549-
+ ` ${why}\n`
550-
+ ' ⛔ NOT a pass: nothing, or nearly nothing, was read.';
551-
}
552-
return null;
553-
}
554-
555-
/**
556-
* The provenance footer for a PASSING run: the census this run read, the floors
557-
* it cleared, the census those floors were derived from, and the ref that
558-
* census belongs to -- side by side.
530+
* This file is the PRECEDENT the other two were copied from, and it carried the
531+
* mechanism under the older name `floorProblem` / `provenanceLine`. The names
532+
* here are now the shared spelling, so the mechanism is spelled ONE way in all
533+
* three gates -- which is the whole point: the refusal text is what an operator
534+
* acts on, and three hand-typed copies of it are three places for it to drift.
559535
*
560-
* This is the whole repair. The floors are inequalities on purpose, so no run
561-
* can ever contradict the record; without this line the record could stop
562-
* describing the tree and every green log would look identical either way. The
563-
* delta is reported as INFORMATION and never as a verdict: this population
564-
* moves in both directions for good reasons (see the header), and only the
565-
* floors decide anything.
536+
* ⛔ `typedJudged` has a floor but NO provenance column. Its number comes from a
537+
* SECOND census (`MEASURED_TYPED`), taken on a different commit, and a
538+
* one-ref provenance line cannot carry two refs without lying about one of
539+
* them. The row itself names its own ref through `at`, which is where the
540+
* refusal quotes it from.
566541
*
567-
* Pure, so `--self-test` drives it with no tree.
542+
* Exported, unlike the two other gates' copies: this file already guards its
543+
* dispatch with `isEntrypoint`, so `check:entry-guard`'s "exports a binding AND
544+
* runs on import" rule does not reach it.
568545
*
569-
* @param {{entries?: number, packages?: number, cjsFiles?: number, probes?: number}} counts
570-
* @returns {string}
546+
* @type {{populationFloorProblem: (counts?: object) => string | null,
547+
* populationProvenanceLine: (counts?: object) => string}}
571548
*/
572-
export function provenanceLine(counts) {
573-
const got = [counts?.entries ?? 0, counts?.packages ?? 0, counts?.cjsFiles ?? 0, counts?.probes ?? 0];
574-
const rec = [MEASURED.entries, MEASURED.packages, MEASURED.cjsFiles, MEASURED.probes];
575-
const floors = [MIN_ENTRIES, MIN_PACKAGES, MIN_CJS_FILES, MIN_PROBES];
576-
const delta = got.map((g, i) => (g === rec[i] ? '=' : `${g > rec[i] ? '+' : ''}${g - rec[i]}`));
577-
return ` provenance — entries/packages/cjsFiles/probes: this run ${got.join('/')}`
578-
+ ` · floors ${floors.join('/')} · derived from ${rec.join('/')} measured on ${MEASURED.ref}`
579-
+ ` (${delta.join('/')} vs the record).\n`
580-
+ ' ⚠ The delta is information, not a verdict — this population grows AND shrinks for good'
581-
+ ' reasons, and only the floors decide. Reproduce the record: see this file\'s header.';
582-
}
549+
const populationFloor = definePopulationFloor({
550+
ref: MEASURED.ref,
551+
provenance: ['entries', 'packages', 'cjsFiles', 'probes'],
552+
reproduce: 'Reproduce the record: see this file\'s header.',
553+
rows: [
554+
{ key: 'entries', min: MIN_ENTRIES, measured: MEASURED.entries,
555+
what: 'published `require` entry point(s)',
556+
why: 'The manifest walk or the `exports` resolver broke. With no entries nothing is required, nothing is parsed, and the gate prints what a clean tree prints.' },
557+
{ key: 'packages', min: MIN_PACKAGES, measured: MEASURED.packages,
558+
what: 'publishable package(s)',
559+
why: 'Entries were found but collapsed onto a fraction of the tree — the walk is reading part of `packages/`, not the whole of it.' },
560+
{ key: 'cjsFiles', min: MIN_CJS_FILES, measured: MEASURED.cjsFiles,
561+
what: 'emitted CommonJS file(s)',
562+
why: 'This is the PARSES population. `commonJsFilesUnder` matched (almost) nothing, so `node --check` ran over an empty set and every byte we emit went unread.' },
563+
{ key: 'probes', min: MIN_PROBES, measured: MEASURED.probes,
564+
what: 'cross-format behaviour probe(s) run',
565+
why: 'AGREES is the invariant loading alone cannot give you, and an empty probe table satisfies it vacuously.' },
566+
{ key: 'typedJudged', min: MIN_TYPED_JUDGED, measured: MEASURED_TYPED.typedJudged, at: MEASURED_TYPED.ref,
567+
what: 'require entry point(s) JUDGED by TYPED',
568+
why: 'This is the TYPED population — entries reached and answered, clean or not. It does not move when packages are defective, only when the row loop stops asking, so a fall here means TYPED went silent rather than that the tree got worse.' },
569+
],
570+
});
571+
export const populationFloorProblem = populationFloor.populationFloorProblem;
572+
export const populationProvenanceLine = populationFloor.populationProvenanceLine;
583573

584574
/**
585575
* Ledger rows naming an id the discovered population does not contain. Pure.
@@ -1133,7 +1123,7 @@ async function main(argv) {
11331123
// ⛔ Before any verdict: a run that read (almost) nothing must refuse, not
11341124
// report the clean tree. Ordered after the prerequisite check so an unbuilt
11351125
// tree still answers 3 — "nothing was measured" has its own code.
1136-
const floor = floorProblem({
1126+
const floor = populationFloorProblem({
11371127
entries: rows.length,
11381128
packages: new Set(rows.map((r) => r.pkg)).size,
11391129
cjsFiles: cjsFileCount,
@@ -1167,7 +1157,7 @@ async function main(argv) {
11671157
);
11681158
for (const h of ledgerHits) console.log(` · declared: ${h}`);
11691159
for (const h of typedExempt) console.log(` · declared UNREACHABLE declaration: ${h}`);
1170-
console.log(provenanceLine({
1160+
console.log(populationProvenanceLine({
11711161
entries: rows.length,
11721162
packages: new Set(rows.map((r) => r.pkg)).size,
11731163
cjsFiles: cjsFileCount,
@@ -1486,25 +1476,25 @@ export async function selfTest() {
14861476
// real run, which is the opposite failure and just as invisible in review.
14871477
battery('the vacuity floors, each driven to zero');
14881478
const full = { entries: MEASURED.entries, packages: MEASURED.packages, cjsFiles: MEASURED.cjsFiles, probes: MEASURED.probes, typedJudged: MEASURED_TYPED.typedJudged };
1489-
t('FLOOR — the values in the records clear every floor', floorProblem(full) === null, JSON.stringify(floorProblem(full)));
1490-
t('FLOOR — a dead manifest walk refuses', floorProblem({ ...full, entries: 0 }) !== null);
1491-
t('FLOOR — entries collapsed onto too few packages refuses', floorProblem({ ...full, packages: 0 }) !== null);
1492-
t('FLOOR — a dead CommonJS collector refuses (PARSES over an empty set)', floorProblem({ ...full, cjsFiles: 0 }) !== null);
1493-
t('FLOOR — an emptied probe table refuses (AGREES satisfied vacuously)', floorProblem({ ...full, probes: 0 }) !== null);
1494-
t('FLOOR — a dead types resolver refuses (TYPED judged nothing)', floorProblem({ ...full, typedJudged: 0 }) !== null);
1479+
t('FLOOR — the values in the records clear every floor', populationFloorProblem(full) === null, JSON.stringify(populationFloorProblem(full)));
1480+
t('FLOOR — a dead manifest walk refuses', populationFloorProblem({ ...full, entries: 0 }) !== null);
1481+
t('FLOOR — entries collapsed onto too few packages refuses', populationFloorProblem({ ...full, packages: 0 }) !== null);
1482+
t('FLOOR — a dead CommonJS collector refuses (PARSES over an empty set)', populationFloorProblem({ ...full, cjsFiles: 0 }) !== null);
1483+
t('FLOOR — an emptied probe table refuses (AGREES satisfied vacuously)', populationFloorProblem({ ...full, probes: 0 }) !== null);
1484+
t('FLOOR — a dead types resolver refuses (TYPED judged nothing)', populationFloorProblem({ ...full, typedJudged: 0 }) !== null);
14951485
t('FLOOR — the TYPED refusal cites the tree ITS number came from, not the older one',
1496-
/\(102 on 196612a313\)/.test(floorProblem({ ...full, typedJudged: 0 }) ?? ''), JSON.stringify(floorProblem({ ...full, typedJudged: 0 })));
1486+
/\(102 on 196612a313\)/.test(populationFloorProblem({ ...full, typedJudged: 0 }) ?? ''), JSON.stringify(populationFloorProblem({ ...full, typedJudged: 0 })));
14971487
// ⛔ The measured regression the floor must NOT produce: a tree where every
14981488
// one of the 35 defective entries is judged and reported still clears it,
14991489
// because judged does not fall when clean does. With the floor on the clean
15001490
// count this returned a refusal and the 35 findings were never printed.
15011491
t('FLOOR — a tree FULL of TYPED findings still reports them, never refuses',
1502-
floorProblem({ ...full, typedJudged: MEASURED_TYPED.typedJudged }) === null);
1503-
t('FLOOR — a missing count is zero, not "unmeasured but fine"', floorProblem({}) !== null);
1492+
populationFloorProblem({ ...full, typedJudged: MEASURED_TYPED.typedJudged }) === null);
1493+
t('FLOOR — a missing count is zero, not "unmeasured but fine"', populationFloorProblem({}) !== null);
15041494
t('FLOOR — the refusal names the count, the floor and the measurement',
15051495
new RegExp(`measured only 0 .* below the floor of \\d+ \\(${MEASURED.cjsFiles} on ${MEASURED.ref}\\)`, 's')
1506-
.test(floorProblem({ ...full, cjsFiles: 0 }) ?? ''),
1507-
JSON.stringify(floorProblem({ ...full, cjsFiles: 0 })));
1496+
.test(populationFloorProblem({ ...full, cjsFiles: 0 }) ?? ''),
1497+
JSON.stringify(populationFloorProblem({ ...full, cjsFiles: 0 })));
15081498
t('FLOOR — every floor sits at or below the value it was measured from',
15091499
MIN_ENTRIES <= MEASURED.entries && MIN_PACKAGES <= MEASURED.packages
15101500
&& MIN_CJS_FILES <= MEASURED.cjsFiles && MIN_PROBES <= MEASURED.probes
@@ -1525,22 +1515,22 @@ export async function selfTest() {
15251515
t('PROVENANCE — the record carries the ref it was measured on',
15261516
typeof MEASURED.ref === 'string' && /^[0-9a-f]{7,40}$/.test(MEASURED.ref), JSON.stringify(MEASURED.ref));
15271517
t('PROVENANCE — the refusal reads the ref from the record rather than restating it',
1528-
(floorProblem({ ...full, entries: 0 }) ?? '').includes(MEASURED.ref),
1529-
JSON.stringify(floorProblem({ ...full, entries: 0 })));
1530-
const provDrifted = provenanceLine({ entries: 102, packages: 66, cjsFiles: 610, probes: 1 });
1518+
(populationFloorProblem({ ...full, entries: 0 }) ?? '').includes(MEASURED.ref),
1519+
JSON.stringify(populationFloorProblem({ ...full, entries: 0 })));
1520+
const provDrifted = populationProvenanceLine({ entries: 102, packages: 66, cjsFiles: 610, probes: 1 });
15311521
t('PROVENANCE — a passing run shows the census it read AND the census the floors came from',
15321522
provDrifted.includes('102/66/610/1')
15331523
&& provDrifted.includes(`${MEASURED.entries}/${MEASURED.packages}/${MEASURED.cjsFiles}/${MEASURED.probes}`)
15341524
&& provDrifted.includes(`${MIN_ENTRIES}/${MIN_PACKAGES}/${MIN_CJS_FILES}/${MIN_PROBES}`)
15351525
&& provDrifted.includes(MEASURED.ref), provDrifted);
15361526
t('PROVENANCE — drift is reported in BOTH directions, and equality says so',
15371527
provDrifted.includes('-1/-1/-3/=')
1538-
&& provenanceLine({ ...full }).includes('=/=/=/=')
1539-
&& provenanceLine({ ...full, entries: MEASURED.entries + 15 }).includes('+15/'),
1528+
&& populationProvenanceLine({ ...full }).includes('=/=/=/=')
1529+
&& populationProvenanceLine({ ...full, entries: MEASURED.entries + 15 }).includes('+15/'),
15401530
provDrifted);
15411531
t('PROVENANCE — the PASS path actually prints it (a line nothing calls is the defect above)',
1542-
readFileSync(fileURLToPath(import.meta.url), 'utf8').includes(`console.log(${'provenanceLine'}({`),
1543-
'the pass path in main() no longer calls provenanceLine — the record would stop being reconciled in the log');
1532+
readFileSync(fileURLToPath(import.meta.url), 'utf8').includes(`console.log(${'populationProvenanceLine'}({`),
1533+
'the pass path in main() no longer calls populationProvenanceLine — the record would stop being reconciled in the log');
15441534
t('PROVENANCE — the delta is marked as information, never as a verdict',
15451535
/not a verdict/i.test(provDrifted) && !/✗|REFUSES/.test(provDrifted), provDrifted);
15461536

0 commit comments

Comments
 (0)