From 40b3d11ade47cbfced160e1c6c7cdd0be676a00e Mon Sep 17 00:00:00 2001 From: aarroyo Date: Thu, 30 Jul 2026 09:40:37 -0500 Subject: [PATCH] fix(standards): the sticky capture date was rewritten on every run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ports to develop the bug found while bringing the same design to main (#295). `classificationChanged` compared `JSON.stringify(previous.counts)` against a counts object rebuilt in KNOWN_CLASSES order, while the renderer emits them in CLASS_ORDER — a different order for the same six numbers. So it always reported "changed", `capturedOn` was refreshed on every run, and the GT-630 fixed-point replay would have failed on any day after a capture. WHY IT WAS INVISIBLE HERE. The reconciliation (#294) landed and was verified on the same day the snapshot was captured, so both runs stamped 2026-07-30 and the bytes matched. A green that depends on the date being unchanged is exactly the kind this chain exists to distrust. It surfaced on main only because that branch's committed snapshot carried 2026-07-29, so the two runs disagreed out loud. OBSERVED BOTH WAYS rather than declared, by forcing the committed date to 2026-07-28 and running the capture with the classification unchanged: without the fix -> capturedOn becomes 2026-07-30 (drift) with the fix -> capturedOn stays 2026-07-28 (sticky, as designed) Verified after restoring the committed snapshot: `--check` passes, the replay is byte-identical, the derived-artifact chain reports 5 links current and at a fixed point, and the ISO/IEC 5055 mapping guard is 9/9. Only the script changed. Co-Authored-By: Claude Opus 5 --- .../capture-native-evaluability-snapshot.mjs | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/rulesets/standards/capture-native-evaluability-snapshot.mjs b/src/rulesets/standards/capture-native-evaluability-snapshot.mjs index 1488ef20..4e129a9a 100644 --- a/src/rulesets/standards/capture-native-evaluability-snapshot.mjs +++ b/src/rulesets/standards/capture-native-evaluability-snapshot.mjs @@ -220,13 +220,26 @@ function today() { */ function classificationChanged(previous, triage) { if (!previous) return true; - const counts = Object.fromEntries( - KNOWN_CLASSES.filter((c) => triage.counts[c] !== undefined).map((c) => [c, triage.counts[c]]), - ); - return ( - JSON.stringify(previous.counts) !== JSON.stringify(counts) || - JSON.stringify(previous.classes) !== JSON.stringify(triage.classes) - ); + + // ORDER-INSENSITIVE, and that is not a detail. The first version compared + // `JSON.stringify(previous.counts)` against a counts object rebuilt in + // KNOWN_CLASSES order, while the renderer emits them in CLASS_ORDER — a + // different order for the same six numbers. So "changed" was ALWAYS true, the + // date was rewritten on every run, and the GT-630 fixed-point replay would have + // failed on any day after the capture. + // + // It passed the day it was written because both runs stamped the same date, + // which is precisely the kind of green this chain exists to distrust. Found on + // main, where the committed snapshot carried an older date and the two runs + // therefore disagreed; ported here, where it was invisible and would have bitten + // on the next day's first run. + const sameMap = (a = {}, b = {}) => { + const ka = Object.keys(a).sort(); + const kb = Object.keys(b).sort(); + return ka.length === kb.length && ka.every((k, i) => kb[i] === k && a[k] === b[k]); + }; + + return !sameMap(previous.counts, triage.counts) || !sameMap(previous.classes, triage.classes); } /**