← back to Approvals Viewer
test/freshness-guard.test.js
212 lines
'use strict';
// NEGATIVE TEST for freshness-guard.js (TK-11685).
//
// Proves the guard (1) goes STALE on a fixture where the target set was invalidated
// by a LATER concurrent finding — a faithful replay of the incident (a delete list
// whose target a sibling lane invalidated after the memo's as-of but before
// approval) — and (2) passes FRESH-clean when the same set has no newer finding.
//
// This is the "ship a check with a negative test proving it goes red on an injected
// fault" rule: we inject the fault, watch it go red, and confirm the control (no
// fault) stays green. Runs entirely against a throwaway temp fixture dir — it never
// reads or writes the real ~/.claude/yolo-queue.
const fs = require('fs');
const os = require('os');
const path = require('path');
const assert = require('assert');
const G = require('../freshness-guard');
let failures = 0;
function check(name, cond, extra) {
if (cond) {
console.log(' ok - ' + name);
} else {
failures++;
console.log(' FAIL- ' + name + (extra ? ' :: ' + extra : ''));
}
}
// ---- build a throwaway fixture tree -------------------------------------------
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'freshguard-'));
const queue = path.join(root, 'pending-approval');
const done = path.join(queue, '_done');
const execDir = path.join(root, 'executed-reversible');
fs.mkdirSync(done, { recursive: true });
fs.mkdirSync(execDir, { recursive: true });
// The SUBJECT memo: a delete list enumerated at 10:12Z, targeting variant
// 44453188108339 (DWRW-72230) and 44453188141107 (DWRW-72231). Mirrors TK-11657.
const subject = path.join(queue, '2026-09-13-TK-11657-rebelwalls-delete.md');
fs.writeFileSync(
subject,
[
'# TK-11657 — Delete 2 orphan variants (GATED)',
'',
'**Drafted:** 2026-09-13 · enumeration completed 2026-09-13T10:12:00Z',
'',
'| handle | delete variant |',
'|---|---|',
'| dwrw-72230 | `44453188108339` DWRW-72230-Sample |',
'| dwrw-72231 | `44453188141107` DWRW-72231-Sample |',
'',
'Re-verify first (read-only): `node ~/.claude/skills/dw-x/check.mjs`',
].join('\n'),
);
const cfg = {
queueDir: queue,
filedDirs: [done],
ledgerPaths: [path.join(execDir, 'ledger.jsonl')],
decisionLogs: [path.join(queue, '_decisions.jsonl')],
recurseDepth: 1,
};
// ---- CONTROL: no newer finding => FRESH (clean) --------------------------------
{
// an OLDER ledger row (before as-of) that mentions the same id must NOT flag.
fs.writeFileSync(
cfg.ledgerPaths[0],
JSON.stringify({
ts: '2026-09-13T09:00:00-00:00',
agent: 'earlier-lane',
ticket: 'TK-99999',
action: 'unrelated earlier touch of variant 44453188108339',
blast_radius: 1,
}) + '\n',
);
const r = G.checkMemo(subject, cfg);
check('control: verdict is FRESH when only OLDER findings exist', r.verdict === 'FRESH', r.verdict);
check('control: status PASS', r.status === 'PASS', r.status);
// 4 distinct target IDENTIFIERS: two variant ids + two DW-SKUs (each row carries both forms)
check('control: 4 target identifiers detected', r.targetCount === 4, 'targetCount=' + r.targetCount);
check('control: reverify hint surfaced', !!r.reverifyHint, r.reverifyHint);
}
// ---- INJECT THE FAULT: a sibling lane invalidates a target AFTER as-of ----------
// The incident exactly: enumeration at 10:12Z, a concurrent lane acts at 15:43Z,
// approval would fire blind at 18:26Z. The guard must catch the 15:43Z finding.
{
fs.appendFileSync(
cfg.ledgerPaths[0],
JSON.stringify({
ts: '2026-09-13T15:43:00-00:00',
agent: 'sibling-lane',
ticket: 'TK-11680',
action: 'productVariantsBulkDelete removed variant 44453188108339 (dwrw-72230) as part of the intra-product dup-SKU sweep',
blast_radius: 1,
undo_cmd: 'productVariantsBulkCreate ...',
}) + '\n',
);
const r = G.checkMemo(subject, cfg);
check('INJECTED FAULT: verdict flips to STALE', r.verdict === 'STALE', r.verdict);
check('INJECTED FAULT: status WARN (advisory, not a hard block)', r.status === 'WARN', r.status);
// the ledger row names both the variant id AND the SKU (dwrw-72230), so both
// identifiers of the deleted row light up — 2 of the 4 identifiers.
check('INJECTED FAULT: 2 of 4 identifiers stale (id + sku of the deleted row)', r.staleCount === 2 && r.targetCount === 4, `${r.staleCount}/${r.targetCount}`);
const st = (r.staleTargets || []).find((s) => s.target === '44453188108339');
check('INJECTED FAULT: the variant id 44453188108339 is flagged', !!st, JSON.stringify(r.staleTargets && r.staleTargets.map((s) => s.target)));
check('INJECTED FAULT: it is high severity (already executed)', st && st.worst === 'high', st && st.worst);
check('INJECTED FAULT: hit reason names the ledger execution',
st && st.hits[0].reason.includes('ALREADY EXECUTED'), st && st.hits[0] && st.hits[0].reason);
check('INJECTED FAULT: hit ts is the 15:43Z sibling finding',
st && st.hits[0].ts.startsWith('2026-09-13T15:43'), st && st.hits[0] && st.hits[0].ts);
check('INJECTED FAULT: headline says do NOT execute blind',
/DO NOT execute/i.test(r.headline || ''), r.headline);
}
// ---- second fault shape: a FILED memo (moved to _done) touches the other target -
{
fs.writeFileSync(
path.join(done, '2026-09-13-TK-11682-rebelwalls-more.md'),
[
'# TK-11682 — 4 more Rebel Walls collisions (RESOLVED 2026-09-13T16:30:00Z)',
'',
'Also cleaned `44453188141107` (dwrw-72231) under this ticket.',
].join('\n'),
);
const r = G.checkMemo(subject, cfg);
// now all 4 identifiers (both rows, id+sku each) are touched by a newer finding
check('SECOND FAULT: all 4 identifiers now stale', r.staleCount === 4, 'staleCount=' + r.staleCount);
const other = (r.staleTargets || []).find((s) => s.target === '44453188141107');
check('SECOND FAULT: filed-memo finding attributed to TK-11682',
other && other.hits.some((h) => h.ticket === 'TK-11682' && h.kind === 'filed'),
other && JSON.stringify(other.hits));
}
// ---- NO_TARGETS: a prose-only memo is correctly N/A (not a false STALE) ---------
{
const prose = path.join(queue, '2026-09-13-TK-00001-policy-note.md');
fs.writeFileSync(prose, '# TK-00001 — policy note\n\nWe should tidy things up on 2026-09-13. No ids here.');
const r = G.checkMemo(prose, cfg);
check('NO_TARGETS: prose memo => NO_TARGETS/PASS', r.verdict === 'NO_TARGETS' && r.status === 'PASS', r.verdict + '/' + r.status);
}
// ---- NOT_MEASURED: an enumerated memo with no parseable time => WARN, never PASS -
{
const notime = path.join(queue, 'no-date-TK-00002-delete.md');
fs.writeFileSync(notime, '# delete\n\nDelete variant `55553188108339`.');
const r = G.checkMemo(notime, { ...cfg }); // filename has no date
check('NOT_MEASURED: enumerated memo w/ no as-of => WARN (never a false PASS)',
r.verdict === 'NOT_MEASURED' && r.status === 'WARN', r.verdict + '/' + r.status);
}
// ---- FUTURE-DATE CLAMP: a future expiry in the body must NOT cause a false FRESH -
// and a future-dated finding must NOT flag. now is pinned so "future" is deterministic.
{
const fdir = path.join(root, 'future');
const fq = path.join(fdir, 'pending-approval');
const fexec = path.join(fdir, 'executed-reversible');
fs.mkdirSync(fq, { recursive: true });
fs.mkdirSync(fexec, { recursive: true });
const fcfg = {
queueDir: fq,
filedDirs: [],
ledgerPaths: [path.join(fexec, 'ledger.jsonl')],
decisionLogs: [],
recurseDepth: 0,
now: '2026-09-20T00:00:00Z', // pin "now"
};
// subject: drafted 09-13, targets id 77771234567890, but body ALSO carries a
// future GMC expiry 2026-12-31 (the TK-11633 googleExpirationDate shape).
const fsubj = path.join(fq, '2026-09-13-TK-42000-delete.md');
fs.writeFileSync(
fsubj,
[
'# TK-42000 — delete (GATED)',
'**Drafted:** 2026-09-13',
'Delete variant `77771234567890`.',
'googleExpirationDate 2026-12-31T22:03:45Z (self-clears then).',
].join('\n'),
);
// a REAL concurrent execution at 09-15 (after draft, before now) -> must flag
fs.appendFileSync(
fcfg.ledgerPaths[0],
JSON.stringify({ ts: '2026-09-15T12:00:00-00:00', agent: 'sib', ticket: 'TK-42001', action: 'deleted variant 77771234567890', blast_radius: 1 }) + '\n',
);
// a FUTURE-dated ledger row (after now) referencing the target -> must NOT flag
fs.appendFileSync(
fcfg.ledgerPaths[0],
JSON.stringify({ ts: '2026-10-05T12:00:00-00:00', agent: 'future', ticket: 'TK-42002', action: 'scheduled touch of variant 77771234567890', blast_radius: 1 }) + '\n',
);
const r = G.checkMemo(fsubj, fcfg);
check('FUTURE CLAMP: future expiry in body does NOT push as-of to the future',
r.asOf === '2026-09-13T00:00:00.000Z', 'asOf=' + r.asOf);
check('FUTURE CLAMP: real 09-15 execution still flags STALE (no false FRESH)',
r.verdict === 'STALE' && r.staleCount === 1, r.verdict + ' ' + r.staleCount);
check('FUTURE CLAMP: only the 09-15 finding counts, the 10-05 future row is ignored',
r.staleTargets && r.staleTargets[0].hits.length === 1 && r.staleTargets[0].hits[0].ts.startsWith('2026-09-15'),
r.staleTargets && JSON.stringify(r.staleTargets[0].hits.map((h) => h.ts)));
}
// ---- cleanup + report ----------------------------------------------------------
try { fs.rmSync(root, { recursive: true, force: true }); } catch {}
console.log('');
if (failures) {
console.log(`NEGATIVE TEST FAILED — ${failures} assertion(s) failed.`);
process.exit(1);
}
console.log('NEGATIVE TEST PASSED — guard reddens on the injected concurrent-invalidation fault and stays green when the set is fresh.');
process.exit(0);