fix(end-state): the D-03/D-04 probes RUN a named test instead of grepping for a symbol

Measured 2026-09-18 (PM checkpoint on 6cafb4c, mutant M6): a two-line file

  export function runPlanVerification() { return { ok: true }; }
  export function formatCriteriaEvidence() { return ''; }

dropped into an otherwise UNFIXED tree closed both D-03 and D-04, and the gate
reported "defects 0 of 7, registry integrity: intact". The same checkpoint felled
5 of 5 mutants with the SUITE and 0 of 5 with the GATE. Both probes called
themselves `behaviour` while asking a grep whether an export name was present -
a phrase probe pointed at code.

A check condition may now name a test instead of a pattern:

  { "test": "tests/lib/criteria-runner.test.mjs", "name": "<exact test name>",
    "expect": "fails" }

The gate spawns the test runner on that one test and reads its TAP line.
`expect: "fails"` holds - the defect stays open - while the test does not pass.
A missing file, a name that matches nothing, a runner that will not start: all
throw, which counts as NOT FELLABLE and therefore open. A check that cannot fire
is never "fixed".

The child's `NODE_TEST_*` env is stripped. The gate usually runs UNDER the test
runner, and an inherited `NODE_TEST_CONTEXT` makes the grandchild report over
the parent's IPC channel instead of stdout - the TAP line would never arrive and
the probe would silently stop felling anything.

M6 is now a permanent mutant in the gate's own test, so the cosmetic close
cannot come back. Verified end to end against a real stub tree:

  git archive 8d1669e | tar -x -C $TMP
  cp scripts/end-state-gate.mjs scripts/end-state-registry.json $TMP/scripts/
  cp tests/fixtures/end-state-frozen.json $TMP/tests/fixtures/; cp STATE.md $TMP/
  printf 'export function runPlanVerification…' > $TMP/lib/cosmetic/stub.mjs
  node scripts/end-state-gate.mjs --root $TMP
  -> defects 2 of 7, both NOT FELLABLE, exit 1

The gate also now states the limit out loud, as its own line above the table:
a behaviour probe proves the capability WORKS; that Phase 7 and Phase 4.5 CALL
it is pinned by TEXT in doc-consistency, not proven deterministically - real
proof is a headless plugin-eval run against a fixture plan (week 40).

FROZEN DENOMINATOR AMENDED, second time in one day, by the work order that
carries this change: D-03 and D-04 signatures moved because their checks
changed on purpose. tests/fixtures/end-state-frozen.json and the FROZEN literal
in tests/scripts/end-state-gate.test.mjs are updated together, both dated, both
saying why. No entry was removed.

Gate after this change: defects 0 of 7, registry integrity intact, exit 1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-18 02:08:40 +02:00
commit a7af76e5ff
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
4 changed files with 158 additions and 15 deletions

View file

@ -43,8 +43,12 @@ const FROZEN = {
'D-01': '48f7f5cca070091e8f9b9ca0c7cdb7a1880733691935c25d07ea1d3e8d508fc9',
'D-02': 'dfc94dba04a9116ae9be2097a0c5a9d8313b02c7de1ca6b49a472c1823a6d1e1',
// Amended 2026-09-18 (operator decision): D-03/D-04 became behaviour probes.
'D-03': '20d2837af1a1247220c65e2a04069b41a3e922c2452369294da4d62bb985636b',
'D-04': '200a142e7792eb00a0f0a58bbfb3e548585d9b45ba36a5c5bf94f28f2c896ffb',
// Amended again the same day, by work order after the PM checkpoint: those
// behaviour probes were greps for an export name, and a two-line stub
// exporting both names closed them on an unfixed tree (mutant M6, pinned
// below). They now RUN a named test, so both signatures moved on purpose.
'D-03': '4eb4c7e447d927b389c7537c2e76d10e6c53ae46c12a61aed1c19adde2ffeb08',
'D-04': '32e3c2aba4b1dc8c41581bf849f39658552e4e8b251a40824bf9dd75c846d19a',
'D-05': '8fe6df52b43496c3302400507cb98002dcf8d2a12d92085e14bda4c7819bc7ba',
'D-06': '72a787c8154d1c18849e9856dcf1a67703d6b5541f88db81d6bab13cab14b418',
'D-07': '1bf3e0cd36c621720697475b0a3fd7db78611d66b58a9ce035e7695e57c69f1b',
@ -759,3 +763,75 @@ test('the detail line carries one note per probe kind PRESENT, and none for kind
assert.equal(rowById(out, 'experiments').detail, '', 'an empty row explains nothing');
} finally { cleanup(dir); }
});
// --- a probe that RUNS a test, and the M6 mutant ---------------------------
//
// Measured 2026-09-18 (PM checkpoint on 6cafb4c): a two-line file
// export function runPlanVerification() { return { ok: true }; }
// export function formatCriteriaEvidence() { return ''; }
// dropped into an otherwise UNFIXED tree closed both D-03 and D-04, and the
// gate reported "defects 0 of 7, registry intact". A grep condition can only
// ask whether a string is present, so a behaviour probe built from one is a
// phrase probe pointed at code. A condition that RUNS a named test and reads
// its TAP result cannot be closed that way.
const M6_STUB = [
'export function runPlanVerification() { return { ok: true }; }',
"export function formatCriteriaEvidence() { return ''; }",
'',
].join('\n');
const TEST_FILE = [
"import { test } from 'node:test';",
"import { strict as assert } from 'node:assert';",
"test('green one', () => { assert.ok(true); });",
"test('red one', () => { assert.ok(false, 'red on purpose'); });",
'',
].join('\n');
test('a test condition RUNS the named test and reads its result', () => {
const dir = fixture({ 'tests/t.test.mjs': TEST_FILE });
try {
const cond = (name, expect) => ({ test: 'tests/t.test.mjs', name, expect });
assert.equal(evaluateCondition(dir, cond('green one', 'passes')), true);
assert.equal(evaluateCondition(dir, cond('green one', 'fails')), false);
assert.equal(evaluateCondition(dir, cond('red one', 'passes')), false);
assert.equal(evaluateCondition(dir, cond('red one', 'fails')), true);
} finally { cleanup(dir); }
});
test('a test condition naming a test that does not run is NOT FELLABLE, never closed', () => {
const dir = fixture({ 'tests/t.test.mjs': TEST_FILE });
try {
for (const cond of [
{ test: 'tests/t.test.mjs', name: 'no such test', expect: 'fails' },
{ test: 'tests/missing.test.mjs', name: 'green one', expect: 'fails' },
]) {
const r = evaluateCheck(dir, [cond]);
assert.equal(r.status, 'not-fellable', JSON.stringify(cond));
assert.ok(r.detail.length > 0, 'the gate must say WHY the check could not fire');
}
} finally { cleanup(dir); }
});
test('M6 mutant: a stub exporting both symbols does NOT close D-03 or D-04', () => {
const dir = fixture({ 'lib/cosmetic/stub.mjs': M6_STUB });
try {
const entries = loadRegistry(ROOT).defects.filter((e) => e.id === 'D-03' || e.id === 'D-04');
assert.equal(entries.length, 2, 'D-03 and D-04 are still in the registry');
for (const e of entries) {
assert.notEqual(
evaluateCheck(dir, e.check).status, 'closed',
`${e.id} closed on a tree where nothing is fixed: ${JSON.stringify(e.check)}`,
);
}
} finally { cleanup(dir); }
});
test('the gate states out loud that the wiring is pinned by text, not proven', () => {
const r = spawnSync(process.execPath, [GATE], { encoding: 'utf8', cwd: ROOT });
assert.ok(r.status === 0 || r.status === 1, r.stderr);
assert.match(r.stdout, /wiring: /);
assert.match(r.stdout, /pinned by TEXT/);
assert.match(r.stdout, /plugin-eval/);
});