fix(end-state): D-03 and D-04 measure behaviour, and the frozen denominator moves with them

The operator decided on 2026-09-18 that the two checks must stop measuring a
phrase and start measuring behaviour. The reason is the gate's own caveat: a
phrase probe closes on rewording, so "Phase 7 no longer says 'Skip for
trekplans'" and "the rubric no longer says 'exists and passes'" could both have
been satisfied by an edit that changed no behaviour at all. Both entries now
point at code a test exercises:

  D-03  lib/**/*.mjs exports runPlanVerification
  D-04  lib/**/*.mjs exports formatCriteriaEvidence

Verified open against the pre-fix tree (8d1669e) and closed against this one —
a check that cannot be seen to fire is not evidence.

What the probes can and cannot prove is now stated by the gate itself rather
than left to the reader. `PROBE_NOTES` carries one note per kind, the detail
line prints a note for every kind PRESENT in the row (and none for a kind no
entry declares), and the open/closed tags name the kind instead of labelling
`phrase` alone and leaving every other kind unlabelled. A behaviour probe
proves the capability exists; it does not prove a prose phase calls it — the
wiring is pinned by tests/lib/doc-consistency.test.mjs, and the gate says so.

THE DENOMINATOR MOVED ON PURPOSE. `tests/fixtures/end-state-frozen.json` and
the `FROZEN` literal in `tests/scripts/end-state-gate.test.mjs` carry new
signatures for D-03 and D-04, amended together and dated in both places. That
is the freeze mechanism working, not a bypass: the integrity check went
VIOLATED the moment the checks changed and stayed red until the amendment was
written down. The list may change — only as a decision said out loud. Nothing
else about the denominator moved: seven defect ids, three experiment ids, and
the agents/decisions/freeze configuration are unchanged, and no entry was
removed.

Gate after: defects 0 of 7, registry intact, still RED and exit 1 on the other
four rows — which is correct.

Suite: 1120 (1118/0/2), up 3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-18 01:36:44 +02:00
commit 6cafb4c04c
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
4 changed files with 90 additions and 27 deletions

View file

@ -160,14 +160,27 @@ export function verifyIntegrity(registry, frozen) {
const notMeasured = (detail) => ({ open: null, total: null, items: [], detail });
// What each probe kind can and cannot prove. An entry declares its kind; the
// gate states the limit out loud, because a closed check is only ever as strong
// as the thing it reads. Every kind a registry entry may declare belongs here —
// the entry-shape test rejects a kind the gate cannot explain.
export const PROBE_NOTES = {
phrase:
'a phrase probe closes on rewording: a closed phrase probe is evidence, not proof of behaviour',
behaviour:
'a behaviour probe reads code a test exercises: it proves the capability exists, not that a prose phase calls it (the wiring is pinned by the suite, not by this probe)',
byte:
"a byte probe reads the file's bytes, so it closes only on a real change to them",
};
function tallyEntries(root, entries) {
const items = entries.map((e) => ({ id: e.id, summary: e.summary, probe: e.probe, ...evaluateCheck(root, e.check) }));
const phrase = items.some((i) => i.probe === 'phrase');
const kinds = Object.keys(PROBE_NOTES).filter((k) => items.some((i) => i.probe === k));
return {
open: items.filter((i) => i.status !== 'closed').length,
total: items.length,
items,
detail: phrase ? 'a phrase probe closes on rewording: a closed phrase probe is evidence, not proof of behaviour' : '',
detail: kinds.map((k) => PROBE_NOTES[k]).join('; '),
};
}
@ -377,12 +390,12 @@ export function render(result) {
for (const i of r.items) {
if (i.status === 'closed') continue;
let tag = i.status === 'not-fellable' ? 'NOT FELLABLE, counted open' : 'open';
if (i.probe === 'phrase') tag += ' · phrase probe';
if (i.probe) tag += ` · ${i.probe} probe`;
out.push(` [${tag}] ${i.id}: ${i.summary}${i.detail ? ` — ${i.detail}` : ''}`);
}
const closed = r.items.filter((i) => i.status === 'closed' && i.probe);
if (closed.length > 0) {
out.push(` closed: ${closed.map((i) => (i.probe === 'phrase' ? `${i.id} (phrase probe)` : i.id)).join(', ')}`);
out.push(` closed: ${closed.map((i) => `${i.id} (${i.probe} probe)`).join(', ')}`);
}
}
return out.join('\n');