diff --git a/lib/review-evidence.ts b/lib/review-evidence.ts index dae8ccc7d..8278c7dc9 100644 --- a/lib/review-evidence.ts +++ b/lib/review-evidence.ts @@ -352,7 +352,8 @@ export function reviewFreshness(rec: Record, currentWtree: string): // issues_found, and the legacy `critical` and Codex counters stay as they were. const open = typeof rec.findings_open === 'number' ? rec.findings_open : rec.issues_found; if (rec.status !== 'clean' || open > 0 || rec.critical > 0 || - (rec.skill === 'codex-review' && rec.findings > (rec.findings_fixed ?? 0))) { + (rec.skill === 'codex-review' && rec.findings > (rec.findings_fixed ?? 0)) || + (Array.isArray(rec.findings) && rec.findings.some(f => record(f) && f.action === 'open'))) { return { status: 'UNVERIFIED', reason: 'review has unresolved findings or did not finish clean' }; } return { status: 'CURRENT', reason: 'completed clean pass on unchanged content' }; diff --git a/test/review-notes-only-pass.test.ts b/test/review-notes-only-pass.test.ts index da4021bcb..b89756912 100644 --- a/test/review-notes-only-pass.test.ts +++ b/test/review-notes-only-pass.test.ts @@ -68,6 +68,17 @@ describe('a notes-only /ship review pass (GST-430)', () => { } }); + // Value: protects=a claimed clean row cannot carry a gate finding the unattended logger stamped open past Step 11.5; + // catches=a reader that trusts the claimed status and counts over the stamped finding (red-team#2, pass 1). + test('a claimed clean row with a stamped-open gate finding is not CURRENT', () => { + const token = run(['--start', 'review']).out.trim(); + const gate = note(1, 'kept', { gate: 'RH1', gate_id: 'g', reason: 'r', severity: 'CRITICAL' }); + expect(run([pass([gate], { status: 'clean', issues_found: 0, critical: 0, informational: 0 }), '--finish', token]).code).toBe(0); + const row = lastRow(); + expect([row.status, row.findings[0].action, row.review_binding.state]).toEqual(['clean', 'open', 'verified']); + expect(reviewFreshness(row, row.wtree)?.status).toBe('UNVERIFIED'); + }); + test('isOpenFinding: noted, deferred and auto-fixed actions are not open; skipped and missing actions are', () => { expect(['fixed', 'auto-fixed', 'deferred', 'noted', 'skipped', undefined].map(action => isOpenFinding({ action }))) .toEqual([false, false, false, false, true, true]);