Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .changeset/9081-census-control-names-one-citation.md
Original file line number Diff line number Diff line change
@@ -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.
125 changes: 123 additions & 2 deletions scripts/__tests__/cross-file-line-citation-census.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
inTestTitle,
evaluateClassifier,
evaluateControls,
finalVerdict,
bucketOf,
CONTROLS,
FALSE_VERDICTS,
Expand Down Expand Up @@ -317,13 +318,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<Record<string, unknown>> = {}) => ({
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`
Expand Down Expand Up @@ -357,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');
Expand Down
164 changes: 138 additions & 26 deletions scripts/cross-file-line-citation-census.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) };
});
}

Expand All @@ -738,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();
Expand Down Expand Up @@ -887,18 +1000,17 @@ function main(argv) {
}
}

const failedControls = [...controls, ...classifier].filter((c) => !c.ok);
if (failedControls.length > 0) {
console.error(`\n✗ ${failedControls.length} control(s) failed -- this run is NOT a reading.`);
return 1;
}
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;
const verdict = finalVerdict({ controls, classifier, populationSize: population.length });
if (verdict.refusal) {
for (const line of verdict.refusal) console.error(line);
return verdict.exit;
}
return 0;
// 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. ⛔ 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)) {
Expand Down
Loading