From 8d1a15c76a74d7c878254fc4aa47b528ba78042a Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 04:02:54 +0000 Subject: [PATCH 1/2] fix(census): address each control by ONE citation, not by a file pair `census:cross-file-line-citations` exited 1 on every run of `main` -- `1 control(s) failed -- this run is NOT a reading` -- and had for some 580 commits, so no tree-wide row count on this script could be re-derived by anyone. The controls were addressed by FILE PAIR, and `evaluateControls` folded every row matching that pair into one answer: `every` row not-false for a non-firing control, `some` row false for a firing one. Two epitaphs in `packages/types/src/crud.ts` address `packages/plugin-detail/src/index.tsx` with one number between them. That number rotted, so one sentence took the whole census down with it. Both directions of the fold were wrong. A non-firing control was sunk by any second citation between the same two files; a firing control could be satisfied by a row that is NOT its subject, reporting the instrument as proven while the case it was written for had silently stopped firing. A control now carries a SUBJECT phrase out of its citing prose and must name exactly ONE citation -- zero matches or more than one is a failure rather than something to fold away. Still addressed by content: no control names a line number of its own. The non-firing control also now requires `resolves` rather than merely not-false, because `anchor-absent` is the census declining to judge and a control must not pass on a verdict nobody reached. The rotted `crud.ts` address is NOT renumbered and NOT repaired here. objectui#8875 clause 4 repairs an address "by converting it to a content anchor, never by moving the number to a different number", and a converted citation leaves no row for a control to score -- so the control moved to another citation and the rot stays in the population, where this census now reports it instead of refusing to run. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr --- .../9081-census-control-names-one-citation.md | 22 ++++ .../cross-file-line-citation-census.test.ts | 88 ++++++++++++++- scripts/cross-file-line-citation-census.mjs | 106 +++++++++++++++--- 3 files changed, 197 insertions(+), 19 deletions(-) create mode 100644 .changeset/9081-census-control-names-one-citation.md diff --git a/.changeset/9081-census-control-names-one-citation.md b/.changeset/9081-census-control-names-one-citation.md new file mode 100644 index 0000000000..1575a934e7 --- /dev/null +++ b/.changeset/9081-census-control-names-one-citation.md @@ -0,0 +1,22 @@ +--- +--- + +Internal tooling only, no package source changed. + +`census:cross-file-line-citations` refused every run on `main` — `exit 1`, +`✗ 1 control(s) failed -- this run is NOT a reading` — because its controls were +addressed by FILE PAIR while `evaluateControls` folded every row matching that +pair into one answer. Two epitaphs in `packages/types/src/crud.ts` address +`packages/plugin-detail/src/index.tsx` with one number between them; the number +rotted, and one unrelated sentence took the whole census down with it. + +A control now names ONE citation: the citing file, the file it cites, and a +SUBJECT phrase out of the citing prose — still by content, still never by a line +number of its own. Zero matches, or more than one, is a failure rather than +something to fold away. The non-firing control also now requires positive +evidence (`resolves`) instead of merely the absence of a false verdict, so a +verdict the census declined to reach can no longer pass it. + +The rotted `crud.ts` address was NOT re-numbered and NOT repaired here: +objectui#8875 clause 4 reserves how an address is repaired, and it stays in the +population where this census reports it. diff --git a/scripts/__tests__/cross-file-line-citation-census.test.ts b/scripts/__tests__/cross-file-line-citation-census.test.ts index 20b305c335..d5e71d626c 100644 --- a/scripts/__tests__/cross-file-line-citation-census.test.ts +++ b/scripts/__tests__/cross-file-line-citation-census.test.ts @@ -317,13 +317,97 @@ describe('the test-name classifier, whose zero is only a reading if it is shown }); }); +type Control = { id: string; from: string; to: string; subject: string; want: string }; +type Scored = { ok: boolean; detail: string }; + +/** A row as the census hands one to `evaluateControls`, for the control `c`. */ +const rowFor = (c: Control, verdict: string, over: Partial> = {}) => ({ + file: c.from, + citedPath: c.to, + line: 10, + citedLine: 20, + text: `* the prose that says ${c.subject} and cites a line`, + verdict, + ...over, +}); + describe('the controls are addressed by CONTENT, and the tree still satisfies them', () => { it('names no line number of its own — a control pinned by line address is the defect', () => { - for (const c of CONTROLS as { from: string; to: string }[]) { - expect(`${c.from} ${c.to}`).not.toMatch(/:\d+/); + for (const c of CONTROLS as Control[]) { + expect(`${c.from} ${c.to} ${c.subject}`).not.toMatch(/:\d+/); + } + }); + + it('gives every control a subject — a FILE PAIR is not a citation', () => { + // REGRESSION objectui#9081: the controls were addressed by file pair alone. + // `packages/types/src/crud.ts` addresses `plugin-detail/src/index.tsx` from + // two epitaphs, so the pair named two rows and never one citation. + for (const c of CONTROLS as Control[]) { + expect(c.subject, `${c.id} has no subject`).toBeTruthy(); + } + }); + + it('is not sunk by a SECOND citation between the same two files', () => { + // REGRESSION objectui#9081: `evaluateControls` required that NO row matching + // the pair be false, so one unrelated rotted sentence refused every run of + // the whole census — for some 580 commits of `main`. + const nonFiring = (CONTROLS as Control[]).find((c) => c.want !== 'false') as Control; + const subject = rowFor(nonFiring, 'resolves'); + const sibling = rowFor(nonFiring, 'drifted', { + line: 400, + text: '* an unrelated sentence citing the same file', + }); + const scored = (evaluateControls([subject, sibling]) as Scored[]) + .find((_, i) => (CONTROLS as Control[])[i].id === nonFiring.id) as Scored; + expect(scored.ok).toBe(true); + }); + + it('does not let a sibling citation satisfy a FIRING control on its behalf', () => { + // The other direction of the same fold, and the quieter one: `some` over the + // pair meant any rotted neighbour reported the instrument as proven while the + // case the control was written for had stopped firing. + const firing = (CONTROLS as Control[]).find((c) => c.want === 'false') as Control; + const subject = rowFor(firing, 'resolves'); + const sibling = rowFor(firing, 'drifted', { + line: 700, + text: '* an unrelated sentence citing the same file', + }); + const [scored] = evaluateControls([subject, sibling]) as Scored[]; + expect(scored.ok).toBe(false); + }); + + it('refuses when its subject names more than one citation', () => { + const firing = (CONTROLS as Control[]).find((c) => c.want === 'false') as Control; + const [scored] = evaluateControls([ + rowFor(firing, 'drifted'), + rowFor(firing, 'drifted', { line: 11 }), + ]) as Scored[]; + expect(scored.ok).toBe(false); + expect(scored.detail).toContain('AMBIGUOUS'); + }); + + it('takes positive evidence only — a verdict the census declined to reach is not a pass', () => { + // objectui#9081: `anchor-absent` is this census refusing to judge. The + // retired `crud.ts` pair carried exactly that on one of its two rows while + // the address was rotted, so `not-false` would have passed on it. + const nonFiring = (CONTROLS as Control[]).find((c) => c.want !== 'false') as Control; + for (const verdict of ['anchor-absent', 'no-anchor', 'drifted']) { + const scored = (evaluateControls([rowFor(nonFiring, verdict)]) as Scored[]) + .find((_, i) => (CONTROLS as Control[])[i].id === nonFiring.id) as Scored; + expect(scored.ok, `${nonFiring.id} passed on ${verdict}`).toBe(false); } }); + it('says so when the pair still carries citations but none of them is the subject', () => { + const firing = (CONTROLS as Control[]).find((c) => c.want === 'false') as Control; + const [scored] = evaluateControls([ + rowFor(firing, 'drifted', { text: '* a sentence that is not this control' }), + ]) as Scored[]; + expect(scored.ok).toBe(false); + expect(scored.detail).toContain('NOT FOUND'); + expect(scored.detail).toContain('none carries this subject'); + }); + it('still reproduces the card off-by-one, verified by content rather than by number', () => { // objectui#8875: `check-doc-component-types.mjs` cites the action vocabulary // at `ActionRunner.ts:112`; that line closes the docblock and `ActionDef` diff --git a/scripts/cross-file-line-citation-census.mjs b/scripts/cross-file-line-citation-census.mjs index cb47a7425f..4ead90e58b 100644 --- a/scripts/cross-file-line-citation-census.mjs +++ b/scripts/cross-file-line-citation-census.mjs @@ -696,39 +696,106 @@ export function bucketOf(relPath) { } /** - * The two controls, addressed BY CONTENT -- the citing file and the file it - * cites -- and never by their own line numbers. A control pinned by line - * address would be an instance of the defect this census measures. + * The two controls, addressed BY CONTENT -- the citing file, the file it cites, + * and a SUBJECT phrase lifted out of the citing prose -- and never by their own + * line numbers. A control pinned by line address would be an instance of the + * defect this census measures. + * + * ## Why the subject exists, and why it is the control's identity (objectui#9081) + * + * A FILE PAIR IS NOT A CITATION. Two sentences in one file may address the same + * file, and until objectui#9081 a control was addressed by pair alone while + * `evaluateControls` folded EVERY row matching that pair into one answer. Both + * directions of that fold were wrong, quietly and in opposite ways: + * + * - a NON-FIRING control was sunk by any second citation between the same two + * files, whatever that citation said. That is what happened. Two epitaphs in + * `packages/types/src/crud.ts` address `packages/plugin-detail/src/index.tsx` + * with one number between them, the number rotted, and this census answered + * `exit 1 -- NOT a reading` to every run on `main` for some 580 commits. + * ⭐ The refusal was CORRECT -- the pinned address really had rotted -- but + * no reading could be taken downstream either, which is the outage. + * - a FIRING control could be satisfied by a row that is NOT its subject. A + * rotted sibling citation would report the instrument as proven while the + * case the control was written for had silently stopped firing. That is the + * unearned green this family exists to prevent, and it has no symptom. + * + * ⇒ a control names ONE citation. A subject matching zero rows, or more than + * one, is a FAILURE rather than something to fold away. ⛔ Neither direction is + * tolerant of a false row: this is strictly narrower than the fold it replaced. + * + * How exposed the old shape was is a number this file deliberately does not + * carry: run `--json --list-all` and group `rows` by `file` + `citedPath` to + * re-derive the share of pairs that carry more than one citation. + * + * ⚠️ `want: 'resolves'`, ⛔ NOT `not-false`. `anchor-absent` and `no-anchor` are + * this census DECLINING to judge, so a non-firing control that accepted them + * would pass on a citation whose health it never established -- and one of the + * two retired `crud.ts` rows judged exactly that, with its address rotted, at + * the moment the other one failed. A non-firing control takes positive evidence + * only: the anchor must sit ON the cited line. + * + * ## ⛔ When a control's subject rots, RE-PIN it -- never renumber it + * + * objectui#8875 clause 4 repairs an existing address "by converting it to a + * content anchor, never by moving the number to a different number". A control + * needs a live line address to score at all, so a converted citation leaves no + * row behind and the control has to move to a different citation. ⛔ It may + * never move to the same citation wearing a fresher number -- that is the + * clause, and it is why the `crud.ts` pair below was retired rather than + * re-addressed. Its rot stays in the population, where this census reports it. */ export const CONTROLS = [ { id: 'firing', from: 'scripts/check-doc-component-types.mjs', to: 'packages/core/src/actions/ActionRunner.ts', + subject: 'action vocabulary declared at', want: 'false', why: 'the docblock closes at the cited line and `ActionDef` opens on the next one (objectui#8875)', }, { id: 'non-firing', - from: 'packages/types/src/crud.ts', - to: 'packages/plugin-detail/src/index.tsx', - want: 'not-false', - why: "the cited line is the `ComponentRegistry.register('detail',` call the prose names", + from: 'packages/components/src/__tests__/layout-containers-declare-containment.test.tsx', + to: 'packages/components/src/renderers/layout/page.tsx', + subject: 'module-private, hence the four lines here', + want: 'resolves', + why: 'the cited line declares the `getJsxManifest` the citing docblock says it mirrors', }, ]; +/** How one row reads in a control's detail line. */ +const controlRow = (r) => `${r.file}:${r.line} -> ${r.citedPath}:${r.citedLine} [${r.verdict}]`; + +/** + * Scores each control against the ONE citation its subject names. `want: 'false'` + * accepts any FALSE verdict; every other `want` is the exact verdict required, + * so a control asking for positive evidence cannot be satisfied by a verdict + * this census declined to reach. + */ export function evaluateControls(rows) { return CONTROLS.map((c) => { - const matches = rows.filter((r) => r.file === c.from && r.citedPath === c.to); + const pair = rows.filter((r) => r.file === c.from && r.citedPath === c.to); + const matches = pair.filter((r) => r.text.includes(c.subject)); if (matches.length === 0) { - return { ...c, ok: false, detail: 'NOT FOUND -- the census did not see this citation at all' }; + // Said separately, because "the citation is gone" and "the citation is + // there but no longer says this" are different repairs. + const others = pair.length > 0 + ? ` (${pair.length} citation(s) do run between these two files; none carries this subject)` + : ''; + return { ...c, ok: false, detail: `NOT FOUND -- the census did not see this citation at all${others}` }; + } + if (matches.length > 1) { + return { + ...c, + ok: false, + detail: `AMBIGUOUS -- ${matches.length} citations carry this subject, so it names no single one: ` + + matches.map(controlRow).join('; '), + }; } - const anyFalse = matches.some((r) => FALSE_VERDICTS.has(r.verdict)); - const ok = c.want === 'false' ? anyFalse : !anyFalse; - const detail = matches - .map((r) => `${r.file}:${r.line} -> ${r.citedPath}:${r.citedLine} [${r.verdict}]`) - .join('; '); - return { ...c, ok, detail }; + const [row] = matches; + const ok = c.want === 'false' ? FALSE_VERDICTS.has(row.verdict) : row.verdict === c.want; + return { ...c, ok, detail: controlRow(row) }; }); } @@ -887,11 +954,16 @@ function main(argv) { } } - const failedControls = [...controls, ...classifier].filter((c) => !c.ok); + const allControls = [...controls, ...classifier]; + const failedControls = allControls.filter((c) => !c.ok); if (failedControls.length > 0) { - console.error(`\n✗ ${failedControls.length} control(s) failed -- this run is NOT a reading.`); + console.error(`\n✗ ${failedControls.length} of ${allControls.length} control(s) failed -- this run is NOT a reading.`); return 1; } + // Said out loud rather than left to be inferred from silence: a reader who + // cannot tell "every control held" from "the script died before printing" + // has no certification, only an exit code (objectui#9081). + if (!asJson) console.log(`✓ ${allControls.length} of ${allControls.length} control(s) passed -- this run IS a reading.`); if (population.length === 0) { console.error('\n✗ Empty population. A census that reads nothing because it is blind is'); console.error(' indistinguishable from a clean tree, so this exits non-zero rather than'); From 2944842db65ad93ca22cebc5f85cdfef5ec56dc4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 04:13:44 +0000 Subject: [PATCH 2/2] fix(census): certify a reading LAST, never above the empty-population guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The certification line added in the previous commit printed directly under the control check and ABOVE the empty-population guard, so a blind run announced `✓ ... this run IS a reading.`, then printed `✗ Empty population ...` and exited 1. A reader -- or a grep keying on that string -- took a certificate off a run that had refused. That is the unearned green this census exists to prevent, arriving through the door marked "say the good news out loud", in the one script whose whole subject is that an instrument must not report a reading it did not take. The empty- population guard exists precisely because a blind scanner is indistinguishable from a clean tree; the misplaced line made that failure mode print a certificate. The ordering is now one exported pure function, `finalVerdict`, so the ORDER is a fact a test can hold rather than a line position a later edit can move back. Every refusal is consulted first, and a refusing verdict carries NO certification string at all rather than a suppressed one -- there is nothing left for a later edit to print by accident. Pinned two-sided: an empty population must refuse and carry no `IS a reading` anywhere in the verdict, and a healthy population in the same suite must carry it, so the absence is a barrier rather than a dead assertion. A third leg covers the failed-control branch. ⛔ The empty-population guard itself is untouched, and `--json` is unaffected: the certification stays behind the same `!asJson` guard and refusals still go to stderr in both modes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr --- .../cross-file-line-citation-census.test.ts | 37 ++++++++++ scripts/cross-file-line-citation-census.mjs | 70 +++++++++++++++---- 2 files changed, 92 insertions(+), 15 deletions(-) diff --git a/scripts/__tests__/cross-file-line-citation-census.test.ts b/scripts/__tests__/cross-file-line-citation-census.test.ts index d5e71d626c..0cec60b4f1 100644 --- a/scripts/__tests__/cross-file-line-citation-census.test.ts +++ b/scripts/__tests__/cross-file-line-citation-census.test.ts @@ -43,6 +43,7 @@ import { inTestTitle, evaluateClassifier, evaluateControls, + finalVerdict, bucketOf, CONTROLS, FALSE_VERDICTS, @@ -441,6 +442,42 @@ describe('the controls are addressed by CONTENT, and the tree still satisfies th }); }); +type Verdict = { exit: number; certification: string | null; refusal: string[] | null }; + +describe('a run certifies itself LAST, after every refusal has been consulted', () => { + const held = [{ ok: true }, { ok: true }]; + const verdict = (controls: { ok: boolean }[], populationSize: number) => + finalVerdict({ controls, classifier: held, populationSize }) as Verdict; + + it('refuses an EMPTY population without certifying anything first', () => { + // REGRESSION objectui#9081: the `IS a reading` line was first printed under + // the control check and ABOVE this guard, so a blind run announced a reading + // and then exited 1 on the next line. A grep keying on that string took a + // certificate off a run that had refused — in the one script whose subject + // is that an instrument must not report a reading it did not take. + const v = verdict(held, 0); + expect(v.exit).toBe(1); + expect(v.certification).toBeNull(); + expect(JSON.stringify(v)).not.toContain('IS a reading'); + expect(v.refusal?.join('\n')).toContain('Empty population'); + }); + + it('DOES certify a healthy population — the control leg, so the absence above is a barrier', () => { + const v = verdict(held, 1); + expect(v.exit).toBe(0); + expect(v.refusal).toBeNull(); + expect(v.certification).toContain('IS a reading'); + }); + + it('refuses a failed control without certifying, whatever the population', () => { + const v = verdict([{ ok: true }, { ok: false }], 1274); + expect(v.exit).toBe(1); + expect(v.certification).toBeNull(); + expect(JSON.stringify(v)).not.toContain('IS a reading'); + expect(v.refusal?.join('\n')).toContain('NOT a reading'); + }); +}); + describe('reporting buckets', () => { it('splits packages and apps one level deep, and keeps the root visible', () => { expect(bucketOf('packages/plugin-form/README.md')).toBe('packages/plugin-form'); diff --git a/scripts/cross-file-line-citation-census.mjs b/scripts/cross-file-line-citation-census.mjs index 4ead90e58b..5953e57990 100644 --- a/scripts/cross-file-line-citation-census.mjs +++ b/scripts/cross-file-line-citation-census.mjs @@ -805,6 +805,52 @@ function tally(rows, key) { return [...m].sort((a, b) => b[1] - a[1] || String(a[0]).localeCompare(String(b[0]))); } +/** + * Whether a run may certify itself, and -- the part worth exporting -- in WHICH + * ORDER its refusals are consulted. + * + * ⚠️ CERTIFICATION IS LAST, and that is a fact this file has already got wrong + * once (objectui#9081). The first cut of the `IS a reading` line printed it + * directly under the control check and ABOVE the empty-population guard, so a + * blind run announced `✓ ... this run IS a reading.`, then printed + * `✗ Empty population ...` and exited 1. A reader -- or a grep keying on that + * string -- took a certificate off a run that had refused. In the one script + * whose whole subject is that an instrument must not report a reading it did + * not take, that is the unearned green arriving through the door marked "say + * the good news out loud". + * + * ⇒ every refusal is consulted first, and a refusing verdict carries NO + * certification string at all rather than a suppressed one: there is nothing + * for a later edit to print by accident. + */ +export function finalVerdict({ controls, classifier, populationSize }) { + const all = [...controls, ...classifier]; + const failed = all.filter((c) => !c.ok); + if (failed.length > 0) { + return { + exit: 1, + certification: null, + refusal: [`\n✗ ${failed.length} of ${all.length} control(s) failed -- this run is NOT a reading.`], + }; + } + if (populationSize === 0) { + return { + exit: 1, + certification: null, + refusal: [ + '\n✗ Empty population. A census that reads nothing because it is blind is', + ' indistinguishable from a clean tree, so this exits non-zero rather than', + ' printing a silent zero.', + ], + }; + } + return { + exit: 0, + refusal: null, + certification: `✓ ${all.length} of ${all.length} control(s) passed -- this run IS a reading.`, + }; +} + function main(argv) { const root = execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim(); const head = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(); @@ -954,23 +1000,17 @@ function main(argv) { } } - const allControls = [...controls, ...classifier]; - const failedControls = allControls.filter((c) => !c.ok); - if (failedControls.length > 0) { - console.error(`\n✗ ${failedControls.length} of ${allControls.length} control(s) failed -- this run is NOT a reading.`); - return 1; + const verdict = finalVerdict({ controls, classifier, populationSize: population.length }); + if (verdict.refusal) { + for (const line of verdict.refusal) console.error(line); + return verdict.exit; } // Said out loud rather than left to be inferred from silence: a reader who - // cannot tell "every control held" from "the script died before printing" - // has no certification, only an exit code (objectui#9081). - if (!asJson) console.log(`✓ ${allControls.length} of ${allControls.length} control(s) passed -- this run IS a reading.`); - if (population.length === 0) { - console.error('\n✗ Empty population. A census that reads nothing because it is blind is'); - console.error(' indistinguishable from a clean tree, so this exits non-zero rather than'); - console.error(' printing a silent zero.'); - return 1; - } - return 0; + // cannot tell "every control held" from "the script died before printing" has + // no certification, only an exit code. ⛔ Printed HERE and nowhere earlier -- + // see `finalVerdict` for why the position is the point. + if (!asJson) console.log(verdict.certification); + return verdict.exit; } if (isEntrypoint(import.meta.url)) {