'use strict'; /** * emitted-provenance.test.cjs — provenance table + totality guard (#2722, * ADR-2719 §2, epic #2719 Phase 2). * * Asserts that every emitted path in every committed golden-parity manifest is * attributable, through the declarative table in tests/helpers/emitted-provenance.cjs, * to the repo source path(s) that can legitimately explain a change to it. * * Three failure modes are all hard failures, because a hand-maintained table's * characteristic risk is rotting into a silent gap: * - unmatched: an emitted path no rule claims (the installer grew a family) * - ambiguous: an emitted path two rules claim (rules overlap) * - dead: a rule nothing matches (the table drifted from reality) * * The residual this does NOT close is false attribution — a rule can point at the * WRONG source and still be total. The spot-checks below pin the pairs where that * is most likely, and ADR-2719 designates the Phase 3 (#2723) dual-run as the * mitigation for the rest. Two real instances of that class were caught while * building this table, both of which passed totality * while resolving to repo files that do not exist — which is why the * "every attributed source exists" test below is a first-class gate, not a nicety. * * Phase 2 scope only: nothing here reads a git diff, builds a live manifest, or * touches a fixture. The differential check, drift-ack file, and size ratchet are * #2723. */ const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const fc = require('fast-check'); const { EXPECTED_MANIFEST_COUNT, PROVENANCE_RULES, COMMANDS_SRC, HOOKS_WINDOWS_SHIM_SRC, AGENT_TRANSFORM_SRCS, RUNTIME_NOTE_FILTER_TRANSFORM_SRCS, stripSkillPrefix, matchRules, attributeEmittedPath, loadManifests, assertTotality, assertNoIdentityTransforms, } = require('./helpers/emitted-provenance.cjs'); const REPO_ROOT = path.join(__dirname, '..'); /** * Manifests are loaded once, but LAZILY — never at module scope. * `RULESET.TESTS.guard-toplevel-readFileSync` (CONTEXT.md:456): a module-level * read throws before any `test()` registers, so a missing or corrupt fixture dir * would crash the file at require time and report an opaque error instead of one * named failing test. Memoizing here keeps the "read 19 fixtures once" saving * without the crash-before-registration risk. */ let _manifests = null; function manifests() { if (_manifests === null) _manifests = loadManifests(); return _manifests; } // ─── Totality (issue #2722's headline acceptance criterion) ────────────────── test('totality: every emitted path across all runtime manifests matches exactly one rule', () => { // Assert the COUNT, not just "some files": a glob that silently matched fewer // fixtures would otherwise report a vacuous pass over a shrunken universe. assert.equal( manifests().length, EXPECTED_MANIFEST_COUNT, `expected ${EXPECTED_MANIFEST_COUNT} runtime manifests, found ${manifests().length}`, ); const { checked, byRule } = assertTotality(manifests()); // Per-manifest floor (not a corpus-wide literal) so the bar scales with the // number of runtime families instead of silently going stale when one is added/removed. assert.ok( checked > 500 * EXPECTED_MANIFEST_COUNT, `expected the full emitted corpus, only checked ${checked} across ${EXPECTED_MANIFEST_COUNT} manifests`, ); // No dead rules — assertTotality already throws on one; this pins the contract // so a future refactor cannot quietly downgrade it to a warning. for (const [ruleId, count] of byRule) { assert.ok(count > 0, `rule "${ruleId}" matched nothing`); } }); test('totality covers all 19 runtimes, asserted by count', () => { const runtimes = new Set(manifests().map((m) => m.file)); assert.equal(runtimes.size, EXPECTED_MANIFEST_COUNT); for (const m of manifests()) { assert.ok(m.keys.length > 0, `manifest ${m.file} has no keys`); } }); test('every attributed source exists in the repo (identity/rewrite/derived rules)', () => { // This is the gate that catches FALSE ATTRIBUTION — a rule that is total but // points at the wrong place. A source path that does not exist is proof the rule // is wrong, and it is how the three real bugs in this table were found. const missing = []; const missingTransforms = []; for (const { runtime, file, keys } of manifests()) { for (const rel of keys) { const { ruleId, kind, sources, transforms } = attributeEmittedPath(rel, runtime); if (kind === 'synthesized') { assert.equal(sources.length, 0, `synthesized rule "${ruleId}" must have no sources`); continue; } assert.ok(sources.length > 0, `rule "${ruleId}" produced no sources for ${rel}`); for (const src of sources) { // A `descriptor` source may legitimately be absent — the installer itself // fs.existsSync-guards it and no-ops (design negative-space). Identity, // rewrite, derived and code-derived sources must exist. if (kind === 'descriptor') continue; const full = path.join(REPO_ROOT, src); if (!fs.existsSync(full)) missing.push(`${file}: ${rel} -> ${src} (rule ${ruleId})`); } // Same existence gate for `transforms` (#2757/#2767). This is what actually // exercises a FUNCTION-valued transforms field (e.g. hooks-built's `.cmd` // special-case) against every REAL emitted key on the platform that emits // it — the static "every declared transform path exists" test below cannot // do this for a function, since it never invokes it. for (const t of transforms) { const full = path.join(REPO_ROOT, t); if (!fs.existsSync(full)) missingTransforms.push(`${file}: ${rel} -> ${t} (rule ${ruleId})`); } } } assert.deepEqual(missing, [], `attributed sources that do not exist:\n ${missing.slice(0, 10).join('\n ')}`); assert.deepEqual( missingTransforms, [], `attributed transforms that do not exist:\n ${missingTransforms.slice(0, 10).join('\n ')}`, ); }); // ─── Spot-checks: known emitted/source pairs (#2722 "add spot-check tests") ── test('spot-check: flat skill attributes to commands/msd, NOT the generated repo skills/ dir', () => { const got = attributeEmittedPath('skills/msd-add-tests/SKILL.md', 'claude'); assert.equal(got.ruleId, 'skills-from-commands'); assert.deepEqual(got.sources, [`${COMMANDS_SRC}/add-tests.md`]); // The trap, asserted explicitly. The repo DOES contain // skills/msd-add-tests/SKILL.md, but scripts/gen-plugin-skills.cjs generates it // from commands/msd/add-tests.md — attributing to it would be false attribution // that still passes totality. assert.ok( !got.sources.includes('skills/msd-add-tests/SKILL.md'), 'emitted skills must never attribute to the generated repo skills/ directory', ); assert.ok( fs.existsSync(path.join(REPO_ROOT, 'skills', 'msd-add-tests', 'SKILL.md')), 'precondition: the generated repo skills/ dir exists, which is why the trap is live', ); }); test('spot-check: nested skill attributes to its child stem, not its router', () => { const nested = attributeEmittedPath('skills/msd-ns-manage/skills/config/SKILL.md', 'claude'); assert.equal(nested.ruleId, 'skills-nested-from-commands'); assert.deepEqual(nested.sources, [`${COMMANDS_SRC}/config.md`]); assert.ok( !nested.sources.includes(`${COMMANDS_SRC}/ns-manage.md`), 'a nested skill must attribute to the CHILD stem, not the routing ns-* parent', ); // The router itself is a normal flat skill and resolves to its own stem. const router = attributeEmittedPath('skills/msd-ns-manage/SKILL.md', 'claude'); assert.equal(router.ruleId, 'skills-from-commands'); assert.deepEqual(router.sources, [`${COMMANDS_SRC}/ns-manage.md`]); }); test('spot-check: alternate skills roots (codex .agents) strip correctly', () => { // codex — NOT antigravity: codex's skills kind carries a `home` override to // $HOME/.agents/skills (ADR-1239 upgrade 3 / #2088), which is why its emitted // skills sit under `.agents/skills/` rather than `skills/`. assert.deepEqual( attributeEmittedPath('.agents/skills/msd-add-tests/SKILL.md', 'codex').sources, [`${COMMANDS_SRC}/add-tests.md`], ); }); test('spot-check: nativePlugin source is per-runtime, read from the descriptor', () => { const opencode = attributeEmittedPath('plugins/msd-core.js', 'opencode'); assert.equal(opencode.ruleId, 'native-plugin'); assert.deepEqual(opencode.sources, ['.opencode/plugins/msd-core.js']); }); test('spot-check: agents identity and Codex toml derivation', () => { assert.deepEqual( attributeEmittedPath('agents/msd-planner.md', 'claude').sources, ['agents/msd-planner.md'], ); assert.deepEqual( attributeEmittedPath('agents/msd-planner.toml', 'codex').sources, ['agents/msd-planner.md'], ); }); test('spot-check: hooks attribute to repo source, not the dist build artifact', () => { assert.deepEqual( attributeEmittedPath('hooks/msd-statusline.js', 'claude').sources, ['hooks/msd-statusline.js'], ); assert.deepEqual( attributeEmittedPath('hooks/lib/git-cmd.js', 'claude').sources, ['hooks/lib/git-cmd.js'], ); }); test('spot-check: Windows-only .cmd shim is code-derived from its generator, not from the .js hook it wraps (#3426/#2767)', () => { // Regression coverage for the windows-latest-only CI failure: the shim is emitted // ONLY when the installer actually runs on win32 (ensureCodexHooksJsonSessionStart / // ensureCodexHooksJsonEvent), so this drives attributeEmittedPath directly rather // than through `manifests()` — that keeps the test meaningful on every OS this // suite runs on, not just the one CI lane that happens to emit the key for real. // // Verified empirically: zero `.cmd` files are tracked anywhere in the repo // (`git ls-files | grep '\\.cmd$'` is empty), so the original generic `hooks-built` // attribution (source = the emitted path itself) resolved every `.cmd` shim to a // file that exists on no platform — exactly the false-attribution class "every // attributed source exists" exists to catch, and it only fired on windows-latest // because that is the only lane where the key is ever actually emitted. // // A SECOND false-attribution then replaced the first (caught by isolated review, // #2767): pointing `sources` at `hooks/.js` asserts the wrapped script's // BYTES flow into the `.cmd` bytes. Traced against buildCodexHookWindowsShimIR // (src/runtime-hooks-surface.cts), they do not — the `.cmd` bytes are // `@ECHO OFF\r\n@SETLOCAL\r\n@