Files
msd-core/tests/audit-uat-acknowledged.test.cjs
Tom Boucher 3a4c3cb83e fix(#3805): audit-uat honours the audit_acknowledged marker via the shared predicate (#4025)
* test(#3805): audit-uat must honour the audit_acknowledged marker

* fix(#3805): route audit-uat's UAT and VERIFICATION scans through the shared acknowledged predicate

* chore(#3805): changeset fragment (pr number backfilled after PR creation)

* chore(#3805): backfill changeset PR number (4025)

---------

Co-authored-by: sim <sim@local>
2026-08-29 02:51:54 -04:00

142 lines
5.5 KiB
JavaScript

'use strict';
// ─────────────────────────────────────────────────────────────────────────────
// #3805 — audit-uat must honour the audit_acknowledged marker that
// audit-open honours.
//
// isAuditItemAcknowledged is the ONE shared suppression predicate, but its
// "every scanner below" scope ended at audit.cts's file boundary:
// cmdAuditUat (src/uat.cts) hand-rolled discovery over the SAME UAT and
// VERIFICATION artifacts and never read the marker — so the documented,
// self-invalidating "this item is moot" seam worked for audit-open and was
// ignored by audit-uat, leaving no honest way to close a moot item.
// ─────────────────────────────────────────────────────────────────────────────
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { createTempProject, cleanup, runGsdTools } = require('./helpers.cjs');
const UAT_BODY = [
'## Tests',
'',
'### 1. Deleted Feature Probe',
'expected: The deleted admin route no longer exists',
'result: pending',
'',
].join('\n');
function seedUatPhase(tmpDir, phaseDir, frontmatterExtra) {
const dir = path.join(tmpDir, '.planning', 'phases', phaseDir);
fs.mkdirSync(dir, { recursive: true });
const fm = [
'---',
'status: human_needed',
...(frontmatterExtra || []),
'---',
'',
].join('\n');
fs.writeFileSync(path.join(dir, `${phaseDir}-UAT.md`), fm + UAT_BODY);
return dir;
}
function seedVerificationPhase(tmpDir, phaseDir, frontmatterExtra) {
const dir = path.join(tmpDir, '.planning', 'phases', phaseDir);
fs.mkdirSync(dir, { recursive: true });
const fm = [
'---',
'status: human_needed',
...(frontmatterExtra || []),
'---',
'',
].join('\n');
fs.writeFileSync(path.join(dir, `${phaseDir}-VERIFICATION.md`), fm + [
'# Verification',
'',
'## Human Verification',
'',
'1. **Test:** the deleted route stays deleted',
' - **Result:** pending',
'',
].join('\n'));
return dir;
}
function roadmapWith(phases) {
const lines = ['# Roadmap', ''];
for (const p of phases) lines.push(`### Phase ${p}: P${p}`, '- [ ] w', '');
return lines.join('\n');
}
function runAudit(cwd) {
const r = runGsdTools(['query', 'audit-uat'], cwd);
assert.ok(r.success, r.error);
return JSON.parse(r.output);
}
describe('#3805: audit-uat honours audit_acknowledged', () => {
test('#3805: an acknowledged UAT file is suppressed and counted', (t) => {
const tmpDir = createTempProject('gsd-3805-uat-');
t.after(() => cleanup(tmpDir));
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), roadmapWith(['02']));
seedUatPhase(tmpDir, '02-probe', [
'audit_acknowledged:',
' milestone: v1.0',
' at: 2026-08-24',
' gap_snapshot: human_needed::scenarios=1',
]);
// Note: the gap_snapshot must match the CURRENT derived value for the
// marker to suppress (self-invalidation on edit).
const out = runAudit(tmpDir);
assert.equal(out.summary.total_items, 0,
`#3805: the acknowledged UAT item must be suppressed; got ${JSON.stringify(out.results)}`);
assert.equal(out.acknowledged_files, 1,
'#3805: the suppressed file must be visible in acknowledged_files (audit-open honesty model)');
});
test('#3805: a STALE gap_snapshot (content changed since ack) still surfaces', (t) => {
const tmpDir = createTempProject('gsd-3805-uatstale-');
t.after(() => cleanup(tmpDir));
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), roadmapWith(['03']));
// Marker snapshots scenarios=2 but the file now has 1 — self-invalidation.
seedUatPhase(tmpDir, '03-probe', [
'audit_acknowledged:',
' milestone: v1.0',
' at: 2026-08-24',
' gap_snapshot: human_needed::scenarios=2',
]);
const out = runAudit(tmpDir);
assert.ok(out.summary.total_items > 0,
'a marker whose snapshot no longer matches must NOT suppress (self-invalidation)');
assert.equal(out.acknowledged_files, 0, 'a stale marker does not count as acknowledged');
});
test('#3805: an acknowledged VERIFICATION file is suppressed (status snapshot)', (t) => {
const tmpDir = createTempProject('gsd-3805-ver-');
t.after(() => cleanup(tmpDir));
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), roadmapWith(['04']));
seedVerificationPhase(tmpDir, '04-probe', [
'audit_acknowledged:',
' milestone: v1.0',
' at: 2026-08-24',
' status: human_needed',
]);
const out = runAudit(tmpDir);
assert.equal(out.summary.total_items, 0,
`#3805: the acknowledged VERIFICATION item must be suppressed; got ${JSON.stringify(out.results)}`);
assert.equal(out.acknowledged_files, 1,
'#3805: the suppressed VERIFICATION file must be visible in acknowledged_files');
});
test('#3805 control: an unacknowledged open UAT file still surfaces', (t) => {
const tmpDir = createTempProject('gsd-3805-ctl-');
t.after(() => cleanup(tmpDir));
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), roadmapWith(['05']));
seedUatPhase(tmpDir, '05-probe');
const out = runAudit(tmpDir);
assert.ok(out.summary.total_items > 0, 'no marker → item surfaces (pre-existing behavior)');
});
});