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>
This commit is contained in:
Tom Boucher
2026-08-29 02:51:54 -04:00
committed by GitHub
parent f4fefb0bef
commit 3a4c3cb83e
5 changed files with 182 additions and 4 deletions

View File

@@ -1789,6 +1789,13 @@ export = {
formatAuditReport,
listAuditPhaseTargets,
cmdAuditAcknowledge,
// #3805: exported so uat.cts's cmdAuditUat routes the SAME artifacts'
// suppression through the ONE predicate instead of hand-rolling a tenth
// copy outside this file's visibility (the exact defect class the
// predicate's own header warns about). The snapshot derivations ride
// along so the snapshotKeys cannot drift between the two consumers.
isAuditItemAcknowledged,
deriveUatGapSnapshotValue,
// #2142: exported so src/milestone.cts's archiveQuickTaskDirectories README
// index generator shares this ONE discovery rule rather than re-deriving it.
resolveQuickTaskSummaryFile,

View File

@@ -35,6 +35,9 @@ const { PHASE_NUMBER_TOKEN_SOURCE, scopeToPhase } = phaseIdMod;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import phaseLocator = require('./phase-locator.cjs');
const { listMilestonePhaseDirs, getAllArchivedPhaseDirs } = phaseLocator;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import auditMod = require('./audit.cjs');
const { isAuditItemAcknowledged, deriveUatGapSnapshotValue } = auditMod;
import { requireSafePath, sanitizeForDisplay } from './security.cjs';
// eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module
import configLoader = require('./config-loader.cjs');
@@ -158,6 +161,7 @@ function cmdAuditUat(cwd: string, raw: boolean): void {
}
const results: UatFileResult[] = [];
let acknowledgedFiles = 0;
// Active dirs are milestone-filtered; archived dirs deliberately are NOT.
// listMilestonePhaseDirs derives the CURRENT milestone's phase directories
@@ -198,7 +202,16 @@ function cmdAuditUat(cwd: string, raw: boolean): void {
const uatFilePath = path.join(phaseDir, file);
const content = readNormalizedDocument(uatFilePath);
const { items, headingsSeen } = parseUatItemsWithStats(content);
const status = (extractFrontmatter(content, uatFilePath).status as string || 'unknown');
const uatFm = extractFrontmatter(content, uatFilePath) as Record<string, unknown>;
const status = ((uatFm.status as string) || 'unknown').toLowerCase();
// #3805: honour the audit_acknowledged marker with the SAME snapshot
// key audit.cts's scanUatGaps uses ('gap_snapshot', derived value
// composed by the shared derivation) — one acknowledgement means the
// same thing to both commands.
if (isAuditItemAcknowledged(uatFm, { snapshotKey: 'gap_snapshot', currentValue: deriveUatGapSnapshotValue(status, content) })) {
acknowledgedFiles++;
continue;
}
// `parse_gap` means the file contained `### N.` test blocks that
// yielded no items — NOT merely "zero items and not complete" (#3707
// MAJOR: that broader signal false-positived on an all-pass file and on
@@ -258,8 +271,17 @@ function cmdAuditUat(cwd: string, raw: boolean): void {
for (const file of scopeToPhase(files.filter(f => f.includes('-VERIFICATION') && f.endsWith('.md')), dir)) {
const verificationFilePath = path.join(phaseDir, file);
const content = readNormalizedDocument(verificationFilePath);
const status = extractFrontmatter(content, verificationFilePath).status as string || 'unknown';
const verFm = extractFrontmatter(content, verificationFilePath) as Record<string, unknown>;
const status = ((verFm.status as string) || 'unknown').toLowerCase();
// #3805: same marker, same 'status' snapshot key as scanVerificationGaps,
// and the same ORDERING — the open-status gate runs FIRST (a marker on
// a file that would never surface is not a suppressed item), then the
// acknowledgement suppresses what the gate surfaced.
if (status === 'human_needed' || status === 'gaps_found') {
if (isAuditItemAcknowledged(verFm, { snapshotKey: 'status', currentValue: status })) {
acknowledgedFiles++;
continue;
}
const items = parseVerificationItems(content, status, verificationFilePath);
if (items.length > 0) {
results.push({
@@ -349,7 +371,9 @@ function cmdAuditUat(cwd: string, raw: boolean): void {
}
}
output({ results, summary }, raw, undefined);
// #3805: acknowledged files surface as a COUNT (audit-open's honesty
// model: the marker fired, the items are suppressed, both facts visible).
output({ results, summary, acknowledged_files: acknowledgedFiles }, raw, undefined);
}
// ─── cmdRenderCheckpoint ──────────────────────────────────────────────────────