Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8,501 changes: 8,501 additions & 0 deletions .harness/fixtures/standards-rule-class/iso-5055-mapping.pre-gt-666.json

Large diffs are not rendered by default.

70 changes: 54 additions & 16 deletions .harness/scripts/ci/65-validate-standards-rule-class.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
* generator and watched fail. The two that pass either way are labelled
* REGRESSION rather than counted as proof of the fix.
*
* GT-668 — that real pre-fix artifact is now a COMMITTED fixture
* (`.harness/fixtures/standards-rule-class/iso-5055-mapping.pre-gt-666.json`)
* rather than a `git show` of a moving branch ref. See `PRE_FIX_FIXTURE` below.
*
* Run it with: node --test .harness/scripts/ci/65-validate-standards-rule-class.test.mjs
*/
import { test, describe } from 'node:test';
Expand All @@ -35,8 +39,26 @@ const HERE = path.dirname(fileURLToPath(import.meta.url));
const GUARD = path.join(HERE, '65-validate-standards-rule-class.mjs');
const REPO_ROOT = path.resolve(HERE, '../../..');

/** The last commit that carried the defect. `develop` is where this branch left. */
const PRE_FIX_REF = process.env.GT666_PRE_FIX_REF ?? 'origin/develop';
/**
* The pre-fix artifact, COMMITTED (GT-668).
*
* This used to be `git show origin/develop:<mapping>` — evidence anchored to a
* moving ref, which is not evidence. It read the defect for exactly as long as
* the fix lived on a branch; the moment GT-666 merged, `origin/develop` began
* serving the CORRECTED artifact and the case asserting 64 findings found 0. A
* test whose meaning changes without anyone editing it cannot be relied on to
* mean anything. Worse, it carried a shallow-clone skip, so in a slightly
* different checkout the same rot would have passed in silence.
*
* The intent — a REAL pre-fix artifact rather than a reconstruction, because a
* reconstruction only ever agrees with whatever the author believed was wrong —
* was right, and is preserved by freezing the artifact in the repository instead
* of fetching it at test time. See the fixture's own `_fixture` header.
*/
const PRE_FIX_FIXTURE = path.join(
REPO_ROOT,
'.harness/fixtures/standards-rule-class/iso-5055-mapping.pre-gt-666.json',
);

/** The reason text the defect published, verbatim from the pre-fix artifact. */
const GOVERNANCE_NOTE =
Expand Down Expand Up @@ -272,25 +294,41 @@ describe('65-validate-standards-rule-class', () => {

test('the REAL pre-fix artifact, against the REAL packs, is RED — 16 rules, 4 findings each', () => {
// The strongest available fixture: the mapping exactly as it was committed
// before this fix, read out of git rather than reconstructed, checked against
// today's packs. Reconstructed fixtures agree with whatever the author
// believed was wrong; this one cannot.
const before = spawnSync('git', ['show', `${PRE_FIX_REF}:${MAPPING_FILE}`], {
cwd: REPO_ROOT,
encoding: 'utf8',
maxBuffer: 64 * 1024 * 1024,
});
if (before.status !== 0) {
// A shallow clone or a detached history cannot answer this. Skipping is
// honest; passing silently would not be.
return void assert.ok(true, `SKIPPED: git show ${PRE_FIX_REF}:${MAPPING_FILE} is unavailable here`);
}
// before GT-666, checked against today's packs. Reconstructed fixtures agree
// with whatever the author believed was wrong; this one cannot — its body is
// byte-identical to the blob GT-666's parent commit carried.
//
// No skip and no env override on purpose. Both were escape hatches out of the
// one case that proves the guard was ever observed failing, and an escape
// hatch out of THAT can only ever hide a failure. The fixture is in the
// repository, so there is no checkout in which this cannot be answered; if it
// is unreadable, the right outcome is red.
const mapping = JSON.parse(readFileSync(PRE_FIX_FIXTURE, 'utf8'));
const { packs } = findPacks(REPO_ROOT);
const findings = checkClassification({ packs, undeclaredInDir: [], mapping: JSON.parse(before.stdout) });
const findings = checkClassification({ packs, undeclaredInDir: [], mapping });
assert.equal(findings.length, 64, 'each of the 16 rules should fail the class rule, the naming rule and both false-claim rules');
assert.equal(findings.filter((f) => /is classified `governance`/.test(f.message)).length, 16);
});

test('the frozen fixture is still the DEFECT, not a refresh of the fixed mapping', () => {
// The failure mode this fixture invites: somebody "updates" it from the live
// artifact, every assertion above still passes shape-wise with zero findings,
// and the only observed failure of this guard quietly becomes a copy of the
// thing it catches. The 64/16 counts above already go red if that happens;
// this states the reason out loud, and pins that the fixture is NOT the file
// the repository ships today.
const frozen = JSON.parse(readFileSync(PRE_FIX_FIXTURE, 'utf8'));
const live = JSON.parse(readFileSync(path.join(REPO_ROOT, MAPPING_FILE), 'utf8'));
assert.match(frozen._fixture ?? '', /FROZEN NEGATIVE FIXTURE/, 'the fixture lost its provenance header');
const keyOf = (r) => `${r.ruleId}@${r.sourceFile}`;
const frozenRows = new Map(frozen.rules.map((r) => [keyOf(r), r]));
const standardsRows = live.rules.filter((r) => r.ruleClass === STANDARD_CLASS).map(keyOf);
assert.equal(standardsRows.length, 16, 'the live mapping should carry 16 international-standard rows');
for (const key of standardsRows) {
assert.equal(frozenRows.get(key)?.ruleClass, 'governance', `${key} is no longer frozen at the defect`);
}
});

// --- REGRESSION (passes before and after the fix; kept, not counted as proof)

test('REGRESSION: a valid row yields no findings', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10055,6 +10055,31 @@
],
"dependencyDisposition": "none",
"dependencyRationale": "No dependency added or changed, and no new file: the generator reads a field the corpus already carries, and the change is that derivation plus the failure it raises when the corpus and the table disagree. DECIDED AND WRITTEN DOWN rather than left implicit: (1) analyser.examples now names the DECLARED analyser (\"eslint\", the shipped default) instead of staying empty - an adoptable:yes row that names nothing is the unfalsifiable claim this artifact exists to remove, and because the value is read from the rule, a corpus configured for semgrep regenerates to say semgrep, where an enumerated alternatives list would be stale the moment the config changed and would be the generator asserting over the corpus again. (2) The eight SSDF and four SLSA rules correctly REMAIN at adoptable:no, which needed an argument rather than a silence because they are the same rule class (international-standard) as the four that moved: they carry no enforce block at all, SsdfRuleHandler and SlsaRuleHandler decide them natively, and no off-the-shelf analyser decides \"does this repository satisfy SSDF PW.4.1\" or \"is provenance distributed with every published artifact\" - those predicates read this repository's own workflows, package manifests and evidence files, so the work genuinely had to be authored and it was (GT-659, GT-665). Deliberately NOT done: no new CI guard. GT-666 shipped 65-validate-standards-rule-class.mjs for a claim nothing on the generator side could refuse; this correction is enforced by the generator itself, which throws on a MAP-versus-declaration conflict, and by build-iso-5055-mapping.mjs --check inside the derived-artifact chain, which fails the moment the committed artifact stops matching what the corpus declares. A second guard re-asserting a derivation the build already refuses to violate would add a denominator to maintain and no coverage. Also NOT done: any change to the corpus - no rule was added, removed or re-authored, which is why every pinned count in rule-corpus-triage.spec.ts is unchanged and why that is stated as the assertion rather than as an incidental."
},
{
"id": "GT-668",
"closedAt": "2026-08-09",
"closureCommit": "a620ed3d",
"evidence": [
".harness/scripts/ci/65-validate-standards-rule-class.test.mjs",
".harness/fixtures/standards-rule-class/iso-5055-mapping.pre-gt-666.json"
],
"validationCommands": [
"THE DEFECT, REPRODUCED BEFORE IT WAS ACTED ON rather than taken from the handover: on develop at 59d62bae, `node --test .harness/scripts/ci/65-validate-standards-rule-class.test.mjs` -> 19 tests, 18 pass, 1 fail. The failing case is 'the REAL pre-fix artifact, against the REAL packs, is RED' with AssertionError 0 !== 64. Cause located in the case itself: it read the pre-fix mapping with spawnSync('git', ['show', `${PRE_FIX_REF}:${MAPPING_FILE}`]) and PRE_FIX_REF defaulted to 'origin/develop' — a MOVING ref that served the defective artifact only while the fix lived on a branch. 59d62bae merged it, so origin/develop now serves the CORRECTED artifact and the case finds 0 findings where it asserts 64.",
"THE SECOND DEFECT, worse than the stale number: the same case carried `if (before.status !== 0) return void assert.ok(true, 'SKIPPED: ...')` for shallow clones. Under fetch-depth:1, a detached history, or a fork without origin/develop, the identical rot PASSES IN SILENCE instead of failing — an escape hatch out of the one case that establishes this guard has ever been OBSERVED failing.",
"THE FIXTURE, taken from git history and not reconstructed: `git rev-parse 59d62bae^` -> 0130834678599d7b7ba3f1295ee9ea4a7ee0e1bd (the last commit carrying the defect); `git rev-parse 59d62bae^:src/rulesets/standards/iso-5055-mapping.json` -> blob 6684e8a42e9d179d36c308a0cf22909c210a18e3. Committed verbatim at .harness/fixtures/standards-rule-class/iso-5055-mapping.pre-gt-666.json — the fixtures location this repository already uses (.harness/fixtures/<subject>/, alongside rag-eval/), not a new home.",
"NOT TRIMMED, and the claim is checkable rather than asserted: all 412 rows kept (284 KB, same order as the live artifact already tracked). One top-level `_fixture` provenance key added because JSON has no comments; checkClassification reads only `rules` and `summary.byClass`, so it is inert. Dropping `_fixture` and re-serialising reproduces the blob BYTE FOR BYTE against `git cat-file blob 6684e8a4` -> true.",
"THE FINDINGS, MEASURED against the frozen fixture and the live packs: 64 total, broken down as 16 `is classified governance, not international-standard` + 16 `no international structural equivalent` + 16 `a governance invariant over Evolith artifacts` + 8 `never names NIST SP 800-218` + 4 `never names ISO/IEC 5055:2021` + 4 `never names SLSA`. Identical to the counts the case asserted before this change: nothing was weakened into a shape test.",
"THE GIT SHOW, THE ENV VAR AND THE SKIP ARE DELETED. No replacement skip: a shallow clone constrains a test that shells out to git, not one that reads a file in its own working tree, so an unreadable fixture is a genuine failure and red is the right outcome.",
"THE REFRESH FAILURE MODE, OBSERVED not argued: simulated by writing the live src/rulesets/standards/iso-5055-mapping.json into the fixture (keeping the _fixture header) and re-running -> 20 tests, 18 pass, 2 FAIL ('the REAL pre-fix artifact ...' and the new 'the frozen fixture is still the DEFECT, not a refresh of the fixed mapping'). Fixture then restored and re-verified byte-identical to blob 6684e8a4.",
"node --test .harness/scripts/ci/65-validate-standards-rule-class.test.mjs -> 20 tests, 20 pass, 0 fail, 0 skipped (19/18-pass/1-fail before).",
"node .harness/scripts/ci/42-validate-guard-denominators.mjs -> 78 guards classified, 57/57 scanning guards refuse a zero-element scan, 0 PENDING.",
"node .harness/scripts/ci/43-validate-guard-negative-fixtures.mjs -> 54/54 exercised guards turned RED on the empty fixture, 0 reported a pass.",
"SCOPE, and why it does not collide with concurrent work: no corpus file, no generator and no derived artifact was touched. src/rulesets/standards/build-iso-5055-mapping.mjs and src/rulesets/standards/iso-5055-mapping.json are deliberately untouched — a parallel session is changing analyser.adoptable on the four ISO5055-* rows, which alters neither ruleClass nor the pack rule count, so the frozen 64/16 figures are unaffected by it.",
"ID ALLOCATION: node .harness/scripts/ci/49-validate-gap-id-allocation.mjs -> 0 collisions. Registered as GT-668 and NOT GT-667: GT-667 is claimed by branch name (guard 50 claims by `gt-NNN` branch) on two concurrent branches, gt-667-analyser-adoptable and claude/gt-667-iso5055-adoptable, both carrying the analyser.adoptable follow-on GT-666's closure record deferred. Guard 49 compares against origin/main and could not have seen either; the id was taken by UNION OF BRANCHES."
],
"dependencyDisposition": "none",
"dependencyRationale": "No dependency added or changed, and none could be: the change deletes a dependency rather than adding one. The test previously depended on git AND on the remote-tracking ref origin/develop being present and pointing at the pre-fix artifact — an external, mutable input. It now reads a file committed in this repository, using only node:fs, which the file already imported. The fixture itself is inert data with no imports. Nothing is imported by relative path from domain code: this is a harness test reading a harness fixture, and the corpus JSON under src/rulesets is untouched."
}
]
}
Loading
Loading