diff --git a/internal/check-done-evidence.mjs b/internal/check-done-evidence.mjs index 27e76949e..a3f54b47c 100644 --- a/internal/check-done-evidence.mjs +++ b/internal/check-done-evidence.mjs @@ -75,6 +75,30 @@ let checked = 0, done = 0 const offenders = [] const staleBaseline = [] +// What counts as evidence on a done card, by schema family: +// 1. `bluf` or a non-empty `fields` object (the original schema); +// 2. `one_line` or `verify` (the second schema; see the note in the loop below); +// 3. a non-empty `evidence` LIST with at least one non-blank entry. 44 cards record their proof +// this way, and 12 of them carry none of the other fields, so each false-failed on closing +// (DESIGN-TEAM-ROLES-MEMBER-DESIGNER-ADMIN did, with its proof sitting in the list). +function carriesEvidence(c) { + const str = (v) => String(v || '').trim().length > 0 + if (str(c.bluf) || str(c.one_line) || str(c.verify)) return true + if (c.fields && Object.keys(c.fields).length > 0) return true + if (Array.isArray(c.evidence) && c.evidence.some((e) => str(typeof e === 'string' ? e : JSON.stringify(e ?? '')))) return true + return false +} +// Self-test, both sides: a non-empty list passes; an empty list, or one of blanks, still fails. +{ + const ok = carriesEvidence({ status: 'done', evidence: ['check:x PASS 12/12'] }) + const empty = carriesEvidence({ status: 'done', evidence: [] }) + const blanks = carriesEvidence({ status: 'done', evidence: ['', ' '] }) + if (!ok || empty || blanks) { + console.error('DONE-EVIDENCE: SELF-TEST FAILED — an evidence list is not judged by its content') + process.exit(1) + } +} + for (const rel of BACKLOGS) { const p = join(QA, rel) if (!existsSync(p)) continue @@ -84,8 +108,6 @@ for (const rel of BACKLOGS) { checked++ if (c.status !== 'done') continue done++ - const hasBluf = String(c.bluf || '').trim().length > 0 - const hasFields = c.fields && Object.keys(c.fields).length > 0 // ★ THE BOARD USES TWO CARD SCHEMAS, and this gate only knew one. Older cards carry // `bluf` + `fields`; a second family carries `one_line` (what was done), `why`, and // `verify` (the artifact and how it was checked). MEASURED 2026-09-24 across 443 done @@ -96,9 +118,7 @@ for (const rel of BACKLOGS) { // which is exactly what "evidence" means here. Demanding a redundant `bluf` alongside // it would have taught the next agent to pad the field the checker reads rather than // write the record — the gate training the behaviour it exists to prevent. - const hasVerify = String(c.verify || '').trim().length > 0 - const hasOneLine = String(c.one_line || '').trim().length > 0 - if (hasBluf || hasFields || hasVerify || hasOneLine) { + if (carriesEvidence(c)) { // A baselined id that now HAS evidence is stale — report it so the list shrinks. if (BASELINE.has(c.id)) staleBaseline.push(`${c.id} (${rel})`) continue @@ -119,11 +139,11 @@ if (offenders.length) { for (const o of offenders) console.error(` ✗ ${o}`) console.error( '\n Add ONE of: `bluf` or `one_line` saying what was done, `verify` naming how it was checked,\n' + - ' or a `fields` entry naming the artifact. Any ONE of the four is enough — the point is a\n' + + ' a `fields` entry naming the artifact, or an `evidence` list. Any ONE is enough — the point is a\n' + ' record a reader can reconcile against the code, not a particular field name.\n' + ' it was verified. Write it now, while the work is fresh — reconstructing it later produces a\n' + ' plausible story indistinguishable from a real record.' ) process.exit(1) } -console.log('DONE-EVIDENCE: PASS — every done card carries evidence (bluf, one_line, verify or fields).') +console.log('DONE-EVIDENCE: PASS — every done card carries evidence (bluf, one_line, verify, fields or an evidence list).')