From 727e443026a5b5acfb495c1488d54c58d0b11f3c Mon Sep 17 00:00:00 2001 From: garrytan Date: Sun, 4 Oct 2026 17:58:20 +0000 Subject: [PATCH] B2: read approved stale-fill decisions from more valid ledger records The structured ledger reader is authoritative before the prose fallback, but it missed two valid approved records in passing censuses (37186854666: a camelCase writeProfile subject; 37195203538: a slash-joined 'D1 / STALE-FILL' row id), which then rested on the prose detector that caused both historical reds. Both forms are accepted; an unresolved row, an unrelated decision, the copied fixture plan and non-write camelCase words stay refused. --- test/ceo-stale-fill-decision.test.ts | 31 +++++++++++++++++++++++++ test/helpers/ceo-stale-fill-decision.ts | 11 ++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/test/ceo-stale-fill-decision.test.ts b/test/ceo-stale-fill-decision.test.ts index 62fe98abb..092f63e5f 100644 --- a/test/ceo-stale-fill-decision.test.ts +++ b/test/ceo-stale-fill-decision.test.ts @@ -55,3 +55,34 @@ describe('CEO stale-fill decision from the skill-defined ledger and currentDecis expect(hasApprovedStaleFillDecision('```text\n' + report.replaceAll('```', '') + '\n```')).toBe(false); }); }); + +describe('structured reader alone on the plan-ceo-section-loading corpus', () => { + const corpus = path.join(import.meta.dir, 'fixtures', 'detector-corpora', 'plan-ceo-section-loading'); + const entry = (name: string): string => JSON.parse(fs.readFileSync(path.join(corpus, name), 'utf8')).input.report; + const camelWrite = entry('37186854666-t1-pass.json'); + const camelQuestion = camelWrite.split('\n').find(line => line.startsWith('Question: D1'))!; + + test('reads an approved decision whose question names the write as writeProfile (37186854666)', () => { + expect(camelQuestion).toContain('writeProfile'); + expect(hasApprovedStaleFillDecision(camelWrite)).toBe(true); + }); + + test('reads an approved decision under the slash-joined row ID D1 / STALE-FILL (37195203538)', () => { + expect(hasApprovedStaleFillDecision(entry('37195203538-t1-pass.json'))).toBe(true); + }); + + test('still refuses an unrelated decision, the copied plan and an unresolved ledger row', () => { + expect(hasApprovedStaleFillDecision(entry('37182865432-t1-fail-derived-unrelated-decision.json'))).toBe(false); + expect(hasApprovedStaleFillDecision(CEO_SECTION_CACHE_PLAN)).toBe(false); + expect(hasApprovedStaleFillDecision(entry('37174266054-t1-pass.json'))).toBe(false); + }); + + test.each(['rewriteProfile', 'isWriteable', 'writeable'])('a question whose only write term is %s is not the race', identifier => { + expect(hasApprovedStaleFillDecision(camelWrite.replace(camelQuestion, camelQuestion.replace('writeProfile', identifier)))).toBe(false); + }); + + test('a slash-joined ID still needs its own approved ledger row', () => { + const report = entry('37195203538-t1-pass.json'); + expect(hasApprovedStaleFillDecision(report.replaceAll('| D1 / STALE-FILL ', '| D1 / OTHER-ROW '))).toBe(false); + }); +}); diff --git a/test/helpers/ceo-stale-fill-decision.ts b/test/helpers/ceo-stale-fill-decision.ts index 84af8280e..7ff794832 100644 --- a/test/helpers/ceo-stale-fill-decision.ts +++ b/test/helpers/ceo-stale-fill-decision.ts @@ -12,6 +12,11 @@ const LEDGER_HEADER = /^\|\s*ID and owner\s*\|\s*Contract and evidence\s*\|\s*Current\s*\|\s*Proposed\s*\|\s*Status\s*\|\s*Exact approval and scope\s*\|\s*$/; const FILL = /\b(?:fill\w*|refill\w*|repopulat\w*)\b|\bcache\.set\b/i; const WRITE = /\b(?:writes?|written|writer|commit\w*)\b/i; +const CAMEL_WRITE = /\b[wW]rite[A-Z]\w*/; +const ROW_ID = String.raw`[A-Za-z][\w-]*(?:\s*\/\s*[A-Za-z][\w-]*)*`; +const ROW_ID_CELL = new RegExp(String.raw`^(${ROW_ID})(?=\s|$)`); +const DECISION_HEADING = new RegExp(String.raw`^#{2,3}\s+currentDecision \((${ROW_ID})\)\s*$`); +const normalizeId = (id: string | undefined) => id?.replace(/\s*\/\s*/g, '/'); const RACE = /\b(?:stale|race|racing|overwrit\w*|newer|old|older|before|after|overlap\w*|in[- ]flight|concurrent\w*)\b/i; /** Asserted lines only: fenced blocks, block quotes and indented code become blank. */ @@ -36,7 +41,7 @@ function ledgerRows(lines: string[]): Map { if (!LEDGER_HEADER.test(lines[i]!)) continue; for (let j = i + 2; j < lines.length && lines[j]!.trimStart().startsWith('|'); j++) { const cells = lines[j]!.trim().slice(1, -1).split('|').map(cell => cell.trim()); - const id = /^([A-Za-z][\w-]*)(?=\s|$)/.exec(cells[0] ?? '')?.[1]; + const id = normalizeId(ROW_ID_CELL.exec(cells[0] ?? '')?.[1]); // A row that does not split into the six ledger cells is not a record. if (!id || cells.length !== 6) continue; rows.set(id, cells[4]!); @@ -50,12 +55,12 @@ export function hasApprovedStaleFillDecision(report: string): boolean { const lines = proseLines(report); const rows = ledgerRows(lines); for (let i = 0; i < lines.length; i++) { - const rowId = /^#{2,3}\s+currentDecision \(([A-Za-z][\w-]*)\)\s*$/.exec(lines[i]!)?.[1]; + const rowId = normalizeId(DECISION_HEADING.exec(lines[i]!)?.[1]); if (!rowId) continue; for (let j = i + 1; j < lines.length && !/^#{1,6}\s/.test(lines[j]!); j++) { const subject = /^Question:\s*D[1-9]\d*\s*[—–-]\s*(.+)$/.exec(lines[j]!)?.[1]; if (!subject) continue; - if (FILL.test(subject) && WRITE.test(subject) && RACE.test(subject) && /^[*_]*approved\b/i.test(rows.get(rowId) ?? '')) return true; + if (FILL.test(subject) && (WRITE.test(subject) || CAMEL_WRITE.test(subject)) && RACE.test(subject) && /^[*_]*approved\b/i.test(rows.get(rowId) ?? '')) return true; break; } }