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.
203 lines
10 KiB
JavaScript
203 lines
10 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Golden parity, hermetic (ADR-3473 §8.1, #3881, phase test-matrix §D — redesigned).
|
|
*
|
|
* The parser moved from a hand-rolled line scanner to vendored js-yaml. This suite proves
|
|
* nothing was silently changed by diffing the CURRENT parser's output against a golden
|
|
* captured from the LEGACY parser, independently of it — but unlike the original design,
|
|
* every fixture entry carries its OWN literal document text. Nothing here enumerates the
|
|
* tree, reads a tracked repo path, or shells out to git. That is deliberate:
|
|
*
|
|
* The original design keyed ~376 golden entries by tracked repo path (every git-tracked
|
|
* command file, workflow file, agent file, and doc under the repo's markdown surfaces). This repo
|
|
* merges roughly 21 commits/day; a 14-day sample measured 937 touches of exactly those
|
|
* covered files. Any PR that edits one of those files' frontmatter — adding an
|
|
* `argument-hint`, changing `allowed-tools`, editing a description — changed its parse and
|
|
* turned this suite red for a change that had nothing to do with the parser. The reflex fix
|
|
* was "regenerate the golden," which overwrites the very snapshot meant to catch a real
|
|
* regression — training people to blow away their own regression fixture on every unrelated
|
|
* touch. It was also a guaranteed merge-conflict magnet: the JSON was one big file every
|
|
* such PR would need to touch. Excluding `.changeset/**` (a prior, narrower fix) was not
|
|
* enough — the design itself was wrong.
|
|
*
|
|
* This redesign carries no tracked-path dependency at all: each entry stores a stable `id`,
|
|
* the literal `documentText` (shrunk from a real ddde001af-era corpus document — see
|
|
* provenance below), and an `expectedParse` captured from the legacy parser. A PR editing
|
|
* `commands/msd/help.md` cannot affect this suite. The only thing that can ever conflict
|
|
* here is two PRs both editing the parser itself.
|
|
*
|
|
* Golden provenance (do NOT re-derive `expectedParse` from the current parser — that would
|
|
* make the comparison circular and prove nothing): `tests/fixtures/golden/
|
|
* frontmatter-legacy-golden.json` was captured by compiling `src/frontmatter.cts` AS IT
|
|
* EXISTED AT COMMIT ddde001af (`git show ddde001af:src/frontmatter.cts`) standalone with
|
|
* tsc, against this repo's sibling support modules (`io.cts`, `shell-command-projection.
|
|
* cts`, `validate.cts`, `text-lines.cts`, `unusable-input.cts`, `pattern.cts`, `phase-id.
|
|
* cts`) — all byte-identical between ddde001af and HEAD (`git diff ddde001af..HEAD --stat`
|
|
* over those paths is empty), so borrowing the current sources of those pure helpers does
|
|
* not change what the legacy frontmatter parser itself computed. The legacy
|
|
* `extractFrontmatter` was run once, at capture time, over a representative sample of real
|
|
* ddde001af-era documents — every document then listed as a known divergence, every
|
|
* adversarial fixture under `tests/fixtures/adversarial/frontmatter/`, and a sample chosen
|
|
* for breadth across the distinct YAML shapes present in the corpus (block scalars, inline
|
|
* arrays, dashed lists, object-lists, nested maps, unicode keys, CRLF, empty values,
|
|
* comments, multi-doc-looking bodies) — each result was structurally serialized (see
|
|
* `tests/helpers/frontmatter-golden-serializer.cjs`, D2) and committed alongside the
|
|
* shrunk document text it was captured from. This is a one-time capture: the compiled
|
|
* legacy module is not part of this repo and is not re-run by the suite below, which only
|
|
* ever reads the committed golden.
|
|
*
|
|
* Shrinking: most entries store the frontmatter region plus a short stub body rather than a
|
|
* whole file. Every entry was verified AT CAPTURE TIME that both the current parser and
|
|
* the legacy parser produce the same structurally-serialized result over the original
|
|
* full document and the shrunk `documentText` — any candidate where either parser's
|
|
* output changed under shrinking was dropped rather than stored (0 of 51 candidates were
|
|
* dropped by this check in this capture; 1 additional file, the adversarial
|
|
* `unclosed-block.md` fixture, has no closing fence to truncate at and is stored
|
|
* unshrunk, verbatim).
|
|
*
|
|
* Divergences: entries with `diverges: true` are documented, deliberate legacy/current
|
|
* mismatches — see each entry's `justification`. They are asserted to STILL diverge
|
|
* (D3), never silently absorbed as a wildcard exemption. Non-diverging entries are
|
|
* asserted to match `expectedParse` exactly (D1).
|
|
*/
|
|
|
|
const { describe, test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const { extractFrontmatter } = require('../msd-core/bin/lib/frontmatter.cjs');
|
|
const { serializeFrontmatterValue } = require('./helpers/frontmatter-golden-serializer.cjs');
|
|
|
|
const GOLDEN_PATH = path.join(__dirname, 'fixtures', 'golden', 'frontmatter-legacy-golden.json');
|
|
const golden = JSON.parse(fs.readFileSync(GOLDEN_PATH, 'utf8'));
|
|
const ENTRIES = golden.entries;
|
|
|
|
/** Serialized current-parser output for one entry's embedded document text. */
|
|
function currentSerialized(entry) {
|
|
return serializeFrontmatterValue(extractFrontmatter(entry.documentText));
|
|
}
|
|
|
|
describe('frontmatter golden parity, hermetic (ADR-3473 §8.1, #3881, §D)', () => {
|
|
test('fixture sanity: entries are present and every id is unique', () => {
|
|
assert.ok(Array.isArray(ENTRIES) && ENTRIES.length > 0, 'expected at least one golden entry');
|
|
const ids = ENTRIES.map((e) => e.id);
|
|
assert.equal(new Set(ids).size, ids.length, 'duplicate entry id(s) in the golden fixture');
|
|
console.log(`frontmatter-golden-parity: ${ENTRIES.length} hermetic golden entries`);
|
|
});
|
|
|
|
test('D1: every non-diverging entry matches its legacy-parser expectedParse exactly', () => {
|
|
const failures = [];
|
|
for (const entry of ENTRIES) {
|
|
if (entry.diverges) continue;
|
|
const actual = currentSerialized(entry);
|
|
if (actual !== entry.expectedParse) {
|
|
failures.push({ id: entry.id, expected: entry.expectedParse, actual });
|
|
}
|
|
}
|
|
assert.deepEqual(
|
|
failures,
|
|
[],
|
|
'undocumented parity divergence(s) — either the parser silently changed behavior, or '
|
|
+ `this is a deliberate divergence that must be marked diverges:true with a justification: ${JSON.stringify(failures, null, 2)}`,
|
|
);
|
|
});
|
|
|
|
test('D1 sensor: the parity check is not vacuous — a mutated expectedParse is caught', () => {
|
|
const rel = ENTRIES.find((e) => !e.diverges);
|
|
assert.ok(rel, 'expected at least one non-diverging entry to sensor-check against');
|
|
const actual = currentSerialized(rel);
|
|
const mutated = `${rel.expectedParse}__MUTATED__`;
|
|
assert.notEqual(
|
|
actual,
|
|
mutated,
|
|
'sensor failed: a mutated expectedParse must not equal the real parser output',
|
|
);
|
|
});
|
|
|
|
test('D3: every diverging entry actually diverges from its expectedParse today', () => {
|
|
const stillMatching = [];
|
|
for (const entry of ENTRIES) {
|
|
if (!entry.diverges) continue;
|
|
const actual = currentSerialized(entry);
|
|
if (actual === entry.expectedParse) stillMatching.push(entry.id);
|
|
}
|
|
assert.deepEqual(
|
|
stillMatching,
|
|
[],
|
|
'entries marked diverges:true that no longer diverge must have diverges FLIPPED TO '
|
|
+ `false (it must never become a wildcard exemption): ${JSON.stringify(stillMatching)}`,
|
|
);
|
|
});
|
|
|
|
test('D3: every diverging entry carries a non-empty justification', () => {
|
|
for (const entry of ENTRIES) {
|
|
if (!entry.diverges) continue;
|
|
assert.ok(
|
|
typeof entry.justification === 'string' && entry.justification.trim().length > 0,
|
|
`${entry.id} is marked diverges:true but has no justification`,
|
|
);
|
|
}
|
|
});
|
|
|
|
test('hermeticity: fixture entries carry no filesystem path operands', () => {
|
|
// sourcePath is provenance-only metadata (never read at test time) — everything else on
|
|
// an entry must be inert data, not something that could be mistaken for a live path
|
|
// lookup.
|
|
for (const entry of ENTRIES) {
|
|
assert.equal(typeof entry.documentText, 'string');
|
|
assert.equal(typeof entry.expectedParse, 'string');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('frontmatter golden serializer protects the gate itself (ADR-3473 §8.1, #3881, §D2)', () => {
|
|
test('D2: JSON.stringify silently drops a named property on an Array', () => {
|
|
// Reproduce the exact legacy shape: `k:\n - test: a\n other: b` parses to an
|
|
// array whose element 0 is "test: a" and which ALSO carries `.other === "b"`.
|
|
const namedArray = ['test: a'];
|
|
namedArray.other = 'b';
|
|
|
|
assert.equal(
|
|
JSON.stringify(namedArray),
|
|
'["test: a"]',
|
|
'JSON.stringify must (still) silently drop the named array property — this pins the '
|
|
+ 'exact defect a JSON-based golden would have',
|
|
);
|
|
});
|
|
|
|
test('D2: the structural serializer distinguishes a plain array from the same array carrying a named property', () => {
|
|
const plainArray = ['test: a'];
|
|
const namedArray = ['test: a'];
|
|
namedArray.other = 'b';
|
|
|
|
const plainSerialized = serializeFrontmatterValue(plainArray);
|
|
const namedSerialized = serializeFrontmatterValue(namedArray);
|
|
|
|
assert.notEqual(
|
|
plainSerialized,
|
|
namedSerialized,
|
|
'the serializer must distinguish ["test: a"] from the same array carrying .other = "b"',
|
|
);
|
|
assert.ok(
|
|
namedSerialized.includes('"other"') && namedSerialized.includes('"b"'),
|
|
`named-property serialization must surface the property and its value; got: ${namedSerialized}`,
|
|
);
|
|
// And it must not have been captured by dropping straight to JSON.stringify.
|
|
assert.notEqual(namedSerialized, JSON.stringify(namedArray));
|
|
});
|
|
|
|
test('D2: the named-array-property shape is real, not hypothetical — it appears in the captured golden', () => {
|
|
// msd-core__templates__summary-complex's `requires` entry is captured golden proof this
|
|
// shape occurs on a real, tracked document (verified live against the legacy parser
|
|
// while writing this suite): its expectedParse must carry a named-property tail.
|
|
const entry = ENTRIES.find((e) => e.id === 'msd-core__templates__summary-complex');
|
|
assert.ok(entry, 'expected the summary-complex divergence entry in the golden set');
|
|
assert.ok(
|
|
/"requires":\["[^"]*"\]\{"provides":/.test(entry.expectedParse),
|
|
`expected the expectedParse for ${entry.id} to carry a named-property tail on 'requires'; got: ${entry.expectedParse}`,
|
|
);
|
|
});
|
|
});
|