Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
155 lines
7.1 KiB
JavaScript
155 lines
7.1 KiB
JavaScript
'use strict';
|
||
|
||
// Issue #3304 — re-verification convergence: an out-of-contract, unevidenced
|
||
// blocker must not revert a completed gap-closure round or trigger another
|
||
// --gaps cycle. Content-assertion contract for the msd-verifier agent (same
|
||
// basis as tests/verifier-behavior-unverified.test.cjs and
|
||
// tests/verifier-deferred-items.test.cjs — the agent .md file IS the deployed
|
||
// product; testing its text tests the shipped contract).
|
||
//
|
||
// Maintainer approval was narrowed to condition C only (deterministic
|
||
// evidence gates a new-scope blocker), rejecting the reporter's broader A/B
|
||
// (advisory whenever untraceable to a requirement/decision/prior-gap,
|
||
// regardless of evidence) — several tests below pin that narrowing so a
|
||
// future edit can't silently widen it back out.
|
||
|
||
const test = require('node:test');
|
||
const assert = require('node:assert/strict');
|
||
const fs = require('node:fs');
|
||
const path = require('node:path');
|
||
|
||
const ROOT = path.join(__dirname, '..');
|
||
const verifierPath = path.join(ROOT, 'agents', 'msd-verifier.md');
|
||
const verifier = fs.readFileSync(verifierPath, 'utf-8');
|
||
const gatePath = path.join(ROOT, 'msd-core', 'references', 'verifier-evidence-gate.md');
|
||
const gate = fs.readFileSync(gatePath, 'utf-8');
|
||
|
||
test('msd-verifier.md required_reading loads the evidence-gate reference', () => {
|
||
assert.match(
|
||
verifier,
|
||
/<required_reading>[\s\S]*?@~\/\.claude\/msd-core\/references\/verifier-evidence-gate\.md[\s\S]*?<\/required_reading>/,
|
||
);
|
||
});
|
||
|
||
test('verifier-evidence-gate.md reference file exists and is non-trivial', () => {
|
||
assert.ok(fs.existsSync(gatePath), 'msd-core/references/verifier-evidence-gate.md should exist');
|
||
assert.ok(gate.length > 500, 'reference file should contain the full algorithm, not a stub');
|
||
});
|
||
|
||
test('Step 7 gates on re-verification mode only', () => {
|
||
assert.match(verifier, /Re-verification evidence gate[\s\S]{0,80}#3304/);
|
||
assert.match(verifier, /in re-verification mode[\s\S]{0,400}?blocks unconditionally only if/);
|
||
});
|
||
|
||
test('debt marker gate stays self-evidencing and unconditional (not routed through the new gate)', () => {
|
||
assert.match(verifier, /Re-verification evidence gate/);
|
||
assert.match(verifier, /other than an unresolved debt marker \(always self-evidencing\)/);
|
||
});
|
||
|
||
test('carried-forward gaps and regressions still block unconditionally, evidence or not', () => {
|
||
assert.match(verifier, /carried-forward gap \(Step 0's `gaps:`\)/);
|
||
assert.match(verifier, /git-modified since the prior `verified:` timestamp/);
|
||
});
|
||
|
||
test('unresolvable git history fails closed toward blocking', () => {
|
||
assert.match(verifier, /fail closed: unresolvable history counts as modified/);
|
||
assert.match(gate, /\*\*Fail closed\*\*/);
|
||
});
|
||
|
||
test('unevidenced new-scope findings route to advisory, not gaps_found', () => {
|
||
assert.match(verifier, /Unevidenced → 📋 Advisory/);
|
||
assert.match(verifier, /exclude from Step 9 Rule 1/);
|
||
assert.match(verifier, /never revert a completed must-have/);
|
||
});
|
||
|
||
test('Categorize line adds the Advisory bucket alongside Blocker/Warning/Info', () => {
|
||
const line = verifier.match(/^Categorize:.*$/m);
|
||
assert.ok(line, 'Categorize line must exist');
|
||
assert.match(line[0], /🛑 Blocker/);
|
||
assert.match(line[0], /⚠️ Warning/);
|
||
assert.match(line[0], /ℹ️ Info/);
|
||
assert.match(line[0], /📋 Advisory/);
|
||
});
|
||
|
||
test('VERIFICATION.md frontmatter template carries the advisory: list', () => {
|
||
assert.match(
|
||
verifier,
|
||
/advisory: # Only if unevidenced new-scope findings exist \(Step 7, re-verification only\)/,
|
||
);
|
||
assert.match(verifier, /advisory:[\s\S]{0,200}?category: architectural \| security \| other/);
|
||
assert.match(verifier, /advisory:[\s\S]{0,300}?evidence_status:/);
|
||
});
|
||
|
||
test('report body template includes an Advisory section, positioned after Deferred Items', () => {
|
||
const deferredIdx = verifier.indexOf('### Deferred Items');
|
||
const advisoryIdx = verifier.indexOf('### Advisory (New Scope, Unevidenced)');
|
||
assert.notEqual(deferredIdx, -1, 'Deferred Items section must exist');
|
||
assert.notEqual(advisoryIdx, -1, 'Advisory section must exist');
|
||
assert.ok(advisoryIdx > deferredIdx, 'Advisory section should follow Deferred Items');
|
||
});
|
||
|
||
test('Advisory section instructs inclusion even when empty', () => {
|
||
const sectionStart = verifier.indexOf('### Advisory (New Scope, Unevidenced)');
|
||
const nextSection = verifier.indexOf('### Required Artifacts', sectionStart);
|
||
assert.notEqual(sectionStart, -1);
|
||
assert.notEqual(nextSection, -1);
|
||
const section = verifier.slice(sectionStart, nextSection);
|
||
assert.match(section, /even "None"/);
|
||
});
|
||
|
||
// ─── Reference file: definitions and rejected-scope pins ──────────────────
|
||
|
||
test('reference file scopes the gate to Step 7 only — truths/artifacts/key-links excluded', () => {
|
||
assert.match(
|
||
gate,
|
||
/Truths, artifacts, and key links[\s\S]{0,200}?can \*\*never\*\* produce this\s*\nfailure mode/,
|
||
);
|
||
});
|
||
|
||
test('reference file explicitly rejects conditions A/B from the issue (contract-traceability alone)', () => {
|
||
const m = gate.match(/## What this deliberately does NOT implement[\s\S]{0,700}/);
|
||
assert.ok(m, 'the "does NOT implement" section must exist');
|
||
assert.match(m[0], /conditions A and B/);
|
||
assert.match(m[0], /maintainer approved \*\*condition C only\*\*/);
|
||
assert.match(m[0], /Do not implement A\/B/);
|
||
});
|
||
|
||
test('reference file defines deterministic evidence as a run-red test or a reproducible artifact', () => {
|
||
assert.match(gate, /named test that FAILS when actually run/);
|
||
assert.match(gate, /Another concrete, reproducible artifact/);
|
||
assert.match(gate, /is not evidence, however well-reasoned/);
|
||
});
|
||
|
||
test('reference file uses file-level git check, not line-level (reliability tradeoff is disclosed)', () => {
|
||
assert.match(gate, /git log --since="\$PREV_VERIFIED_TS" --oneline -- "\$file"/);
|
||
assert.match(gate, /Check file-level, not\s*\n\s*line-level/);
|
||
});
|
||
|
||
test('reference file carries a worked example grounded in the reported incident', () => {
|
||
assert.match(gate, /## Worked example \(from the issue's reported incident\)/);
|
||
assert.match(gate, /→ \*\*advisory\*\*, does not\s*\n\s*block/);
|
||
});
|
||
|
||
// ─── Parity: advisory is a per-finding annotation, never an overall status ─
|
||
|
||
test('PARITY: advisory never leaks into the overall-status vocabulary', () => {
|
||
const unionLines = verifier.match(/^status:\s+[a-z_]+(?:\s*\|\s*[a-z_]+)+\s*$/gm) || [];
|
||
assert.ok(unionLines.length > 0, 'expected at least one status union line');
|
||
for (const line of unionLines) {
|
||
assert.doesNotMatch(line, /advisory/i);
|
||
}
|
||
});
|
||
|
||
test('PARITY: overall-status enum in verification.cts does not gain an "advisory" status', () => {
|
||
const verificationLib = require(path.join(ROOT, 'msd-core', 'bin', 'lib', 'verification.cjs'));
|
||
const { VERIFIER_STATUSES } = verificationLib;
|
||
assert.ok(Array.isArray(VERIFIER_STATUSES), 'VERIFIER_STATUSES array must be present');
|
||
assert.ok(
|
||
!VERIFIER_STATUSES.includes('advisory'),
|
||
'VERIFIER_STATUSES must not gain a per-finding "advisory" state — it stays a per-finding annotation',
|
||
);
|
||
for (const s of ['passed', 'gaps_found', 'human_needed']) {
|
||
assert.ok(VERIFIER_STATUSES.includes(s), `VERIFIER_STATUSES must still contain ${s}`);
|
||
}
|
||
});
|