/** * Drop finding IDs from the frozen-baseline comparison (M-BUG-28). * * v5.0.0's baselines were captured when `{NNN}` came from an emission counter, * so a frozen ID records where a finding happened to land in that run — not * which check produced it. The ID now names the check, so those recorded * numbers are not a contract the live output can or should reproduce. * * The alternative — re-deriving positional IDs on the frozen side, the way * `strip-retired-gap.mjs` originally did — would assert the retired scheme * against itself, and #58's `isGapEntry` off-by-one is the measured example of * that misfiring. Both sides run through this normalizer, so masking (not * deleting) keeps the key present: a finding that loses its `id` entirely still * fails the comparison. * * What replaces the coverage: `tests/lib/finding-codes.test.mjs` pins every * published check→number pair exhaustively, and * `tests/scanners/finding-code-coverage.test.mjs` asserts no finding escapes the * registry. Arithmetic and identity are each asserted where they belong. */ const CA_ID = /^CA-[A-Z]{2,4}-\d{3}$/; const MASK = ''; /** * Replace every finding ID with a constant, in place. * @template T * @param {T} payload * @returns {T} */ export function maskFindingIds(payload) { walk(payload); return payload; } function walk(node) { if (Array.isArray(node)) { for (const item of node) walk(item); return; } if (!node || typeof node !== 'object') return; for (const key of ['id', 'findingId']) { if (typeof node[key] === 'string' && CA_ID.test(node[key])) node[key] = MASK; } for (const v of Object.values(node)) walk(v); }