Skip to content

Commit d56bf2d

Browse files
committed
test(devx): self-test battery for row references, with the #15687 insertion as an ablation
23 cases: a positive control, one per spelling the page uses (`Row N`, `row N's`, `row N`, `rows N–M`, and `row-N` inside a `why:`), a `why:` disagreement, keys that resolve to no row and to two rows, the undeclared-reference population refusal, and the real page today. The ablation copies the REAL page into a temp dir, inserts one row above row 34 and renumbers — the exact edit PR #15687 made under a green gate — and asserts the check reddens naming `Row 34` (now row 35), the two `why:` strings for rows 34 and 60, and both stated row extents. A byte assertion proves the real page was never written. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
1 parent 0c70ef2 commit d56bf2d

1 file changed

Lines changed: 325 additions & 2 deletions

File tree

scripts/check-system-context-census.mjs

Lines changed: 325 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,10 @@
158158
* assertion reads it rather than adding to it.
159159
*/
160160

161-
import { readFileSync, writeFileSync } from 'node:fs';
161+
import { createHash } from 'node:crypto';
162+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
162163
import { execFileSync } from 'node:child_process';
164+
import { tmpdir } from 'node:os';
163165
import { join } from 'node:path';
164166
import { fileURLToPath } from 'node:url';
165167

@@ -201,11 +203,12 @@ const SELF_TEST_BATTERIES = Object.freeze({
201203
'the refusal has to SHOW its work (both counts, both classes, the diff)': 2,
202204
'WIRING: this gate, and its self-test, really run in CI': 2,
203205
'POPULATION DECLARATION: what the dispatch derivation is told this gate reads': 6,
206+
'⭐ ROW REFERENCES: held by seam, and the insertion that was silent (#15869)': 23,
204207
});
205208

206209
// DELETING an entry silences that battery's floor exactly as effectively as
207210
// zeroing it, so the roster's own size is pinned too.
208-
const SELF_TEST_BATTERY_FLOOR = 17;
211+
const SELF_TEST_BATTERY_FLOOR = 18;
209212

210213
// The key an assertion is filed under when no battery is open. It is not a
211214
// declared battery, so it reds by the same set difference rather than silently
@@ -1642,6 +1645,104 @@ function fixturePage({ anchor = 'pkg/a.ts:2', helper = 'pkg/a.ts:7' } = {}) {
16421645
].join('\n');
16431646
}
16441647

1648+
/**
1649+
* ── The ROW REFERENCE fixture (#15869) ──────────────────────────────────────
1650+
*
1651+
* A miniature of the real page's shape, and only its shape: one behaviour table
1652+
* whose numbering runs on into a `### 6.` carry-onward table, then the four
1653+
* spellings the real page uses to point back into them -- `Row N`,
1654+
* `row N's`, `row N`, and the two `rows N–M` extents.
1655+
*
1656+
* ⛔ Deliberately NOT a copy of the real page: the cases below drive one edit at
1657+
* a time, and a fixture small enough to read whole is the only way a reader can
1658+
* see that the edit is the only difference. The real page is exercised too, in
1659+
* the same battery, by the ablation.
1660+
*/
1661+
const ROW_FIXTURE_PAGE = [
1662+
'---',
1663+
'title: row fixture',
1664+
'---',
1665+
'',
1666+
'| # | Behaviour | Anchor |',
1667+
'|:--|:---|:---|',
1668+
'| 1 | **The short-circuit** runs first | `pkg/a.ts:2` |',
1669+
'| 2 | **`owner_id` is not stamped** on INSERT | `pkg/a.ts:3` |',
1670+
'| 3 | `revoke()` deletes directly, before the guard | `pkg/a.ts:4` |',
1671+
'',
1672+
'### 6. Reads that only carry the flag onward',
1673+
'',
1674+
'| # | Site | What it does |',
1675+
'|:--|:---|:---|',
1676+
'| 4 | `pkg/b.ts:5` | Propagates the flag onward |',
1677+
'| 5 | `pkg/b.ts:6` | Rebuilds the context |',
1678+
'',
1679+
'1. **`revoke()` skips its own conflict guard.** Row 3 is correct for the rule.',
1680+
"2. The shared verdict is the one function all of row 2's doors consult.",
1681+
'3. The step 3.5 guard is inside the block row 1 skips.',
1682+
'',
1683+
'| — behaviour-bearing (rows 1–3 above) | 9 |',
1684+
'| — carry the flag onward only (rows 4–5 above) | 2 |',
1685+
'',
1686+
].join('\n');
1687+
1688+
/** The fixture's own `PAGE_ROW_REFERENCES`: one entry per reference above. */
1689+
const ROW_FIXTURE_REFS = [
1690+
{
1691+
context: '**`revoke()` skips its own conflict guard.**',
1692+
seam: '`revoke()` deletes directly',
1693+
why: 'the bolded subject is `revoke()`',
1694+
},
1695+
{ context: 'doors consult', seam: '**`owner_id` is not stamped**', why: 'the doors are the stamp doors' },
1696+
{ context: 'is inside the block', seam: '**The short-circuit** runs first', why: 'the block is the short-circuit' },
1697+
{ context: 'behaviour-bearing (rows', section: 'behaviour', why: 'the behaviour extent' },
1698+
{ context: 'carry the flag onward only (rows', section: 'carry-onward', why: 'the carry-onward extent' },
1699+
];
1700+
1701+
/**
1702+
* The fixture's own `NON_READ_ANCHORS`, carrying all three `why:` shapes: a plain
1703+
* `row N`, a `why` naming TWO rows in one string, and the hyphenated `row-N`.
1704+
*/
1705+
const ROW_FIXTURE_LEDGER = [
1706+
{
1707+
file: 'pkg/a.ts',
1708+
needle: 'a',
1709+
why: 'row 3 -- the guard `revoke()` deletes in front of',
1710+
rowSeams: ['`revoke()` deletes directly'],
1711+
},
1712+
{
1713+
file: 'pkg/a.ts',
1714+
needle: 'b',
1715+
why: 'row 2 -- the stamp guard the row-1 short-circuit skips',
1716+
rowSeams: ['**`owner_id` is not stamped**', '**The short-circuit** runs first'],
1717+
},
1718+
{ file: 'pkg/a.ts', needle: 'c', why: 'rough edge 5 -- names no row at all', rowSeams: [] },
1719+
];
1720+
1721+
/**
1722+
* Insert one numbered row above row `n` and renumber every row at or below it --
1723+
* the edit PR #15687 made, which this gate used to be green through.
1724+
*
1725+
* @returns {{ text: string, inserted: boolean }}
1726+
*/
1727+
function insertRowAbove(pageText, n, cell) {
1728+
let inserted = false;
1729+
const out = [];
1730+
for (const line of pageText.split('\n')) {
1731+
const m = /^\|\s*(\d+)\s*\|(.*)$/.exec(line);
1732+
if (!m) {
1733+
out.push(line);
1734+
continue;
1735+
}
1736+
const number = Number(m[1]);
1737+
if (number === n && !inserted) {
1738+
out.push(`| ${n} |${cell}`);
1739+
inserted = true;
1740+
}
1741+
out.push(number >= n ? `| ${number + 1} |${m[2]}` : line);
1742+
}
1743+
return { text: out.join('\n'), inserted };
1744+
}
1745+
16451746
/**
16461747
* The page's UNENFORCED decomposition, as a fixture: the six rows plus the dated
16471748
* marker. Every knob is a way the page could decay -- a number going stale, a row
@@ -1760,6 +1861,7 @@ function selfTest() {
17601861
// ── ledger ─────────────────────────────────────────────────────────────────
17611862
battery('ledger');
17621863
const ledgerStale = evaluate({
1864+
pageRowReferences: [],
17631865
pageText: fixturePage(),
17641866
census: FIXTURE_CENSUS,
17651867
tracked: FIXTURE_TRACKED,
@@ -1770,6 +1872,7 @@ function selfTest() {
17701872
});
17711873
t('LEDGER: a needle that matches nothing is a finding', ledgerStale.problems.some((p) => p.startsWith('[ledger-stale]')));
17721874
const ledgerAmbig = evaluate({
1875+
pageRowReferences: [],
17731876
pageText: fixturePage(),
17741877
census: FIXTURE_CENSUS,
17751878
tracked: FIXTURE_TRACKED,
@@ -1780,6 +1883,7 @@ function selfTest() {
17801883
});
17811884
t('LEDGER: a needle matching two lines is a finding', ledgerAmbig.problems.some((p) => p.startsWith('[ledger-ambiguous]')));
17821885
const ledgerUnused = evaluate({
1886+
pageRowReferences: [],
17831887
pageText: fixturePage({ helper: 'pkg/a.ts:2' }),
17841888
census: FIXTURE_CENSUS,
17851889
tracked: FIXTURE_TRACKED,
@@ -1828,6 +1932,7 @@ function selfTest() {
18281932
t('COUNTS: the carry-onward split is read from section 6 itself', carryOnwardRowCount(sectionPage) === 2);
18291933
t('COUNTS: a renamed section 6 is underivable, not zero', carryOnwardRowCount('nothing here') === -1);
18301934
const underivable = evaluate({
1935+
pageRowReferences: [],
18311936
pageText: fixturePage(),
18321937
census: FIXTURE_CENSUS,
18331938
tracked: FIXTURE_TRACKED,
@@ -2001,6 +2106,7 @@ function selfTest() {
20012106
// touched cannot come back with a missing site, an unexplained anchor or an
20022107
// unused ledger row. Pinned behaviourally rather than argued in a comment.
20032108
const afterFix = evaluate({
2109+
pageRowReferences: [],
20042110
pageText: bothShifted.text,
20052111
census: FIXTURE_CENSUS,
20062112
tracked: FIXTURE_TRACKED,
@@ -2110,6 +2216,223 @@ function selfTest() {
21102216
// `check-aggregator-roster` and `check-ci-filter-parity` set -- and, like the second
21112217
// docs root that gate added, this needed NO workflow edit: `lint.yml` already invokes
21122218
// both legs, and it is the repo's busiest file.
2219+
// ── ⭐ ROW REFERENCES: held by SEAM, and the insertion that used to be silent ─
2220+
//
2221+
// The shape this battery exists for (#15869): a row INSERTED into the behaviour
2222+
// table renumbers every row below it, so every `row N` past the insertion point
2223+
// becomes false while this gate stays green -- because nothing compared a number
2224+
// to a row. It happened (#15687, three references), and it un-happened by
2225+
// coincidence (#15395, the same three), with nothing observing either event.
2226+
//
2227+
// ⭐ The ablation is run on a COPY of the REAL page, in a temp dir, because a
2228+
// toy fixture cannot show that the check reaches the references that actually
2229+
// rotted. The real page is never written -- asserted below, by bytes.
2230+
battery('⭐ ROW REFERENCES: held by seam, and the insertion that was silent (#15869)');
2231+
2232+
const rowRefs = (page, refs = ROW_FIXTURE_REFS, ledger = ROW_FIXTURE_LEDGER) =>
2233+
checkRowReferences({ pageText: page, ledger, pageRefs: refs });
2234+
const codes = (result) => result.problems.map((p) => p.slice(0, p.indexOf(']') + 1));
2235+
2236+
const rowGreen = rowRefs(ROW_FIXTURE_PAGE);
2237+
t(
2238+
'⭐ POSITIVE CONTROL: every reference whose number MATCHES its keyed row passes',
2239+
rowGreen.problems.length === 0,
2240+
rowGreen.problems.join(' | ')
2241+
);
2242+
t(
2243+
'the control really exercised all five page references and both `why:` mentions',
2244+
rowGreen.held.page === 5 && rowGreen.held.why === 3 && rowGreen.held.unheld === 0,
2245+
JSON.stringify(rowGreen.held)
2246+
);
2247+
2248+
// ── one case per SPELLING the page uses ────────────────────────────────────
2249+
const falsified = (page, expect) => {
2250+
const result = rowRefs(page);
2251+
return (
2252+
result.problems.some((p) => p.startsWith('[row-ref-falsified]') && p.includes(expect)) &&
2253+
result.problems.length === 1
2254+
);
2255+
};
2256+
t(
2257+
'SPELLING `Row N` (sentence-initial): a number that no longer matches its seam is falsified',
2258+
falsified(ROW_FIXTURE_PAGE.replace('Row 3 is correct', 'Row 2 is correct'), '`Row 2`')
2259+
);
2260+
t(
2261+
"SPELLING `row N's` (possessive): same",
2262+
falsified(ROW_FIXTURE_PAGE.replace("row 2's doors", "row 1's doors"), '`row 1`')
2263+
);
2264+
t(
2265+
'SPELLING `row N` (lowercase, mid-sentence): same',
2266+
falsified(ROW_FIXTURE_PAGE.replace('the block row 1 skips', 'the block row 3 skips'), '`row 3`')
2267+
);
2268+
t(
2269+
'SPELLING `rows N–M` (range): the behaviour extent is held to the section, not to a seam',
2270+
falsified(ROW_FIXTURE_PAGE.replace('(rows 1–3 above)', '(rows 1–4 above)'), '`rows 1–4`')
2271+
);
2272+
t(
2273+
'SPELLING `rows N–M` (range): and the carry-onward extent to ITS section',
2274+
falsified(ROW_FIXTURE_PAGE.replace('(rows 4–5 above)', '(rows 4–6 above)'), '`rows 4–6`')
2275+
);
2276+
2277+
// ── the incident shape, on the fixture: ONE insertion, several falsehoods ───
2278+
const inserted = insertRowAbove(ROW_FIXTURE_PAGE, 3, ' **An inserted row** | `pkg/a.ts:9` |');
2279+
const afterInsert = rowRefs(inserted.text);
2280+
t(
2281+
'⭐ THE INCIDENT SHAPE: inserting one row above row 3 falsifies every reference below it, ' +
2282+
'and the refusal names the reference, its number and the row the key resolves to',
2283+
inserted.inserted &&
2284+
afterInsert.problems.filter((p) => p.startsWith('[row-ref-falsified]')).length === 3 &&
2285+
afterInsert.problems.some((p) => p.includes('`Row 3`') && p.includes('is row 4')),
2286+
afterInsert.problems.join(' | ')
2287+
);
2288+
2289+
// ── keys that stop resolving ───────────────────────────────────────────────
2290+
t(
2291+
'a key that resolves to NO row is a refusal, not a pass -- the reference is now held by nothing',
2292+
codes(rowRefs(ROW_FIXTURE_PAGE.replace('`revoke()` deletes directly, before the guard', 'reworded'))).includes(
2293+
'[row-ref-key-unresolved]'
2294+
)
2295+
);
2296+
t(
2297+
'a key that resolves to TWO rows refuses and names both candidates',
2298+
(() => {
2299+
const twice = ROW_FIXTURE_PAGE.replace(
2300+
'| 2 | **`owner_id` is not stamped** on INSERT | `pkg/a.ts:3` |',
2301+
'| 2 | **`owner_id` is not stamped** and `revoke()` deletes directly | `pkg/a.ts:3` |'
2302+
);
2303+
const result = rowRefs(twice);
2304+
return result.problems.some((p) => p.startsWith('[row-ref-key-ambiguous]') && p.includes('rows 2, 3'));
2305+
})()
2306+
);
2307+
t(
2308+
'a context that no longer matches any line refuses rather than dropping the reference',
2309+
codes(rowRefs(ROW_FIXTURE_PAGE, [{ context: 'a sentence nobody wrote', seam: 'x', why: 'y' }])).includes(
2310+
'[row-ref-context-stale]'
2311+
)
2312+
);
2313+
2314+
// ── POPULATION: the reference nobody declared ──────────────────────────────
2315+
t(
2316+
'⭐ POPULATION: a `row N` the ledger does not claim is a refusal -- a ledger of what exists ' +
2317+
'today cannot see the reference someone writes tomorrow',
2318+
(() => {
2319+
const grown = `${ROW_FIXTURE_PAGE}\n4. And a brand new sentence about row 2.\n`;
2320+
const result = rowRefs(grown);
2321+
return result.problems.some((p) => p.startsWith('[row-ref-undeclared]') && p.includes('`row 2`'));
2322+
})()
2323+
);
2324+
t(
2325+
'an `unheld` entry is tolerated -- but still LOCATED, so it cannot decay into "not there"',
2326+
(() => {
2327+
const grown = `${ROW_FIXTURE_PAGE}\n4. And a sentence with no derivable key about row 2.\n`;
2328+
const declared = [...ROW_FIXTURE_REFS, { context: 'no derivable key about', unheld: 'no key' }];
2329+
const ok = rowRefs(grown, declared).problems.length === 0;
2330+
const gone = rowRefs(ROW_FIXTURE_PAGE, declared).problems;
2331+
return ok && gone.some((p) => p.startsWith('[row-ref-context-stale]'));
2332+
})()
2333+
);
2334+
t(
2335+
'ZERO numbered rows refuses instead of passing over a table it could not find',
2336+
codes(rowRefs(ROW_FIXTURE_PAGE.replace(/^\| \d+ \|/gm, '| x |'))).includes('[no-table-rows]')
2337+
);
2338+
2339+
// ── the ledger's own `why:` strings ────────────────────────────────────────
2340+
t(
2341+
"a `why:` string whose row number disagrees with its seam is falsified, naming the `why`",
2342+
(() => {
2343+
const drifted = ROW_FIXTURE_LEDGER.map((row) =>
2344+
row.why.startsWith('row 3') ? { ...row, why: 'row 2 -- the guard `revoke()` deletes in front of' } : row
2345+
);
2346+
const result = rowRefs(ROW_FIXTURE_PAGE, ROW_FIXTURE_REFS, drifted);
2347+
return result.problems.some((p) => p.startsWith('[why-row-falsified]') && p.includes('is row 3'));
2348+
})()
2349+
);
2350+
t(
2351+
"SPELLING `row-N` (hyphenated, inside a `why:`): held by its own seam like any other",
2352+
rowGreen.problems.length === 0 &&
2353+
ROW_FIXTURE_LEDGER.some((row) => row.why.includes('row-1')) &&
2354+
(() => {
2355+
const drifted = ROW_FIXTURE_LEDGER.map((row) =>
2356+
row.why.includes('row-1') ? { ...row, why: row.why.replace('row-1', 'row-3') } : row
2357+
);
2358+
return rowRefs(ROW_FIXTURE_PAGE, ROW_FIXTURE_REFS, drifted).problems.some((p) =>
2359+
p.startsWith('[why-row-falsified]')
2360+
);
2361+
})()
2362+
);
2363+
t(
2364+
'a `why:` that names a row and declares NO seam is refused -- an unkeyed number reads as current forever',
2365+
(() => {
2366+
const unkeyed = [{ file: 'pkg/a.ts', needle: 'n', why: 'row 3 -- unkeyed' }];
2367+
return codes(rowRefs(ROW_FIXTURE_PAGE, ROW_FIXTURE_REFS, unkeyed)).includes('[why-row-unkeyed]');
2368+
})()
2369+
);
2370+
2371+
// ── ⭐ THE REAL PAGE, and the ablation on a COPY of it ──────────────────────
2372+
let realPage = null;
2373+
try {
2374+
realPage = readFileSync(join(ROOT, PAGE), 'utf8');
2375+
} catch (err) {
2376+
t('⭐ the real page is readable', false, err.code ?? err.message);
2377+
}
2378+
if (realPage !== null) {
2379+
const realResult = checkRowReferences({ pageText: realPage });
2380+
t(
2381+
"⭐ TODAY'S TREE: every row reference on the real page and in the real ledger resolves to its keyed row",
2382+
realResult.problems.length === 0,
2383+
realResult.problems.join(' | ')
2384+
);
2385+
t(
2386+
'and the real run really resolved them (10 page references + 9 `why:` mentions, 2 declared unheld)',
2387+
realResult.held.page === 10 && realResult.held.why === 9 && realResult.held.unheld === 2,
2388+
JSON.stringify(realResult.held)
2389+
);
2390+
2391+
// ⛔ The ablation is written to a TEMP DIR and read back from disk. The real
2392+
// page is never opened for writing -- the byte assertion below is the proof.
2393+
const before = createHash('sha256').update(realPage).digest('hex');
2394+
const dir = mkdtempSync(join(tmpdir(), 'census-row-ablation-'));
2395+
try {
2396+
const mutated = insertRowAbove(
2397+
realPage,
2398+
34,
2399+
' **An inserted row, for the ablation** | plugin-sharing | Get: nothing | `sharing-service.ts:1` |'
2400+
);
2401+
const copy = join(dir, 'system-context.mdx');
2402+
writeFileSync(copy, mutated.text);
2403+
const readBack = readFileSync(copy, 'utf8');
2404+
t(
2405+
'ABLATION: the mutated COPY really reached the disk, one row heavier and renumbered',
2406+
mutated.inserted &&
2407+
readBack !== realPage &&
2408+
extractTableRows(readBack).length === extractTableRows(realPage).length + 1,
2409+
`${extractTableRows(realPage).length} -> ${extractTableRows(readBack).length} rows`
2410+
);
2411+
const ablated = checkRowReferences({ pageText: readBack });
2412+
const falsifiedRefs = ablated.problems.filter((p) => p.startsWith('[row-ref-falsified]'));
2413+
const falsifiedWhy = ablated.problems.filter((p) => p.startsWith('[why-row-falsified]'));
2414+
t(
2415+
'⭐ ABLATION: one row inserted above row 34 turns the gate RED, naming the falsified page ' +
2416+
'references -- this is the exact edit #15687 made under a green gate',
2417+
falsifiedRefs.some((p) => p.includes('`Row 34`') && p.includes('is row 35')) &&
2418+
falsifiedRefs.some((p) => p.includes('`rows 1–61`')),
2419+
ablated.problems.join(' | ')
2420+
);
2421+
t(
2422+
'⭐ ABLATION: and the `why:` strings for rows 34 and 60 -- the other two references #15687 falsified',
2423+
falsifiedWhy.some((p) => p.includes('`row 34`') && p.includes('is row 35')) &&
2424+
falsifiedWhy.some((p) => p.includes('`row 60`') && p.includes('is row 61')),
2425+
falsifiedWhy.join(' | ')
2426+
);
2427+
t(
2428+
'⛔ ABLATION SAFETY: the REAL page was never written -- same bytes before and after',
2429+
createHash('sha256').update(readFileSync(join(ROOT, PAGE), 'utf8')).digest('hex') === before
2430+
);
2431+
} finally {
2432+
rmSync(dir, { recursive: true, force: true });
2433+
}
2434+
}
2435+
21132436
battery('WIRING: this gate, and its self-test, really run in CI');
21142437
const SELF = 'scripts/check-system-context-census.mjs';
21152438
let lintYml = null;

0 commit comments

Comments
 (0)