← 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);