Files
msd-core/tests/contributor-standards.test.cjs
Tom Boucher a613caaeef enhance(#2721): regenerating merge driver, regen:derived, and a name for the emitted-artifact family (#2730)
* test(#2721): failing-first suite for the gsd-regen driver and CONTEXT.md parity

Tests precede the implementation per the TDD gate. The driver module does not
exist yet, so tests/git-merge-regen-driver.test.cjs fails at require time; the
contributor-standards parity assertions fail against next as it stands today,
where the standards doc names two CONTEXT.md headings that have never existed.

Refs #2721

* feat(#2721): add the gsd-regen merge driver and regen:derived

The golden parity manifests and the two size baselines are pure functions of
the source tree, so their only correct merge is "recompute" -- something git's
ours/theirs interface cannot express. 140 of 143 conflicted-file instances
across the open PR queue are these files.

The driver deliberately does NOT regenerate. Four probes established that at
merge-driver time neither the working tree nor the index reflects the merge:
both hold the ours side, a file added by theirs does not exist yet, and
MERGE_HEAD is unwritten. Git also invokes the driver once per conflicted path
(20 here). A regenerating driver would therefore read the ours-side tree and
emit a plausible-but-wrong hash manifest -- worse than a conflict, because a
conflict is visible. So it accepts %A, runs zero subprocesses, records the
resolved paths, and prints one notice pointing at npm run regen:derived.
Staleness stays caught where it already was, by golden-install-parity in CI.

Every failure path degrades toward today's behaviour (a normal conflict).
install-tree is deliberately excluded per ADR-2719 section 7.

Also folded in, per the no-defer rule: workflow-size.cjs claimed .md files have
no eol=lf in .gitattributes; git check-attr shows eol: lf, set by .gitattributes
line 2 since #1088.

Refs #2721

* docs(#2721): document regen:derived and the gsd-regen merge driver

Adds the how-to a contributor actually reaches for when the generated parity
manifests or size baselines conflict, in both places they would look: the
merge-conflict path in CONTRIBUTING.md and the full guide in TESTING-SUITES.md,
including what the driver deliberately does not do (it does not clear GitHub's
CONFLICTING label, and it does not regenerate mid-merge).

Also scopes the new contributor-standards parity assertion to the doc's own
CONTEXT.md section. Its first run flagged `## Decision`, `## Consequences` and
`## Standards followed`, which the doc attributes to an ADR body and a PR body
rather than to CONTEXT.md -- a doc-wide extractor would have demanded CONTEXT.md
grow headings that do not belong to it.

Refs #2721

* fix(#2721): stop passing %P to the merge driver — shell injection

The isolated adversarial review found, and I independently reproduced, local
arbitrary command execution.

Git does not invoke a merge driver with an argv array. It substitutes %O %A %B
%L %P textually into the configured string and runs the whole thing through a
shell, and $(...) executes inside POSIX double quotes -- so quoting the
placeholder does not neutralise it. %O/%A/%B are git-generated temp names and
%L is an integer, but %P is the file's own path, chosen freely by any
contributor. A branch renaming a covered fixture to
evil$(touch PWNED_SENTINEL).json executed that command on the machine of every
maintainer who merged it, and the merge still reported success.

Fix removes the input rather than filtering it: %P is no longer registered, so
the driver receives no attacker-controlled argument at all. The marker records
a count instead of path names. A metacharacter filter would have been a guess
about shell grammar; passing nothing is a property. Re-ran the identical
exploit against the fixed command: nothing executed, conflict still resolved.

Two regressions guard it -- a platform-independent assertion that the
registered command carries no %P, and a real merge driven by the actual
planInstall output with a $(...) filename.

Also from review: CLI dispatch had no coverage at all (CONTRIBUTING's
"CLI and command routing" matrix), which is why runInstall/runStatus now take
{repoRoot} -- hardcoding REPO_ROOT was what made them untestable. Renamed
planResolution to resolveAndRecord since the plan* prefix promised purity it
did not have. Reconciled the eleven-vs-twelve generator count across
CONTEXT.md, CONTRIBUTING.md and the changeset.

Refs #2721

* test(#2721): scope safe.directory for the check-attr helper

The 66f4d85a run failed 11 assertions, all in the .gitattributes scoping block,
with "fatal: detected dubious ownership in repository at '/work'". The test
container checks the repo out at a path its user does not own, so git refuses
check-attr outright. Everything else passed (27,185).

`check-attr` is a pure read of .gitattributes -- no hooks, no filters -- so the
exemption is scoped to that one invocation. It is deliberately NOT applied to
the driver's own production `git config` calls, which run in the user's own
clone and should keep the protection.

Refs #2721

* test(#2721): delete the stale assertion that the driver command carries %P

The plex2 run on bdfd0856 left exactly two failures, both this test: it still
asserted the pre-fix command string, i.e. the vulnerable behaviour. Deleted
rather than relaxed, per RULESET.TESTS.delete-bad-tests -- its useful half is
already covered, in both directions, by
registeredDriverCommandNeverPassesThePlaceholderForTheFilePath.

Refs #2721

* test(#2721): drive the end-to-end merges from the real planInstall output

The e2e helper hand-rolled its own driver registration, and still carried %P.
That meant the five real-git tests were not exercising the production command
string at all -- planInstall could drift and they would keep passing. They now
register exactly what a contributor gets from npm run setup:merge-driver.

Refs #2721

* chore(#2721): backfill changeset pr number to 2730
2026-07-27 19:55:37 -04:00

231 lines
9.0 KiB
JavaScript

// allow-test-rule: source-text-is-the-product
// docs/contributor-standards.md is a contributor-facing contract doc — its headings
// and cross-links ARE what contributors read. Structural assertions on headings and
// links test the deployed contract, not implementation detail.
'use strict';
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const REPO_ROOT = path.join(__dirname, '..');
const STANDARDS_DOC = path.join(REPO_ROOT, 'docs', 'contributor-standards.md');
const CONTRIBUTING_MD = path.join(REPO_ROOT, 'CONTRIBUTING.md');
function readStandardsDoc() {
try {
return fs.readFileSync(STANDARDS_DOC, 'utf-8');
} catch (err) {
assert.fail(`docs/contributor-standards.md does not exist: ${err.message}`);
}
}
function parseH2Headings(content) {
return content
.split('\n')
.filter((line) => /^## /.test(line))
.map((line) => line.replace(/^## /, '').trim());
}
describe('docs/contributor-standards.md', () => {
test('file exists', () => {
assert.ok(fs.existsSync(STANDARDS_DOC), 'docs/contributor-standards.md must exist');
});
test('has required CONTEXT.md section', () => {
const content = readStandardsDoc();
const headings = parseH2Headings(content);
const hasContextSection = headings.some((h) => /context/i.test(h));
assert.ok(
hasContextSection,
`Expected an ## heading containing "context" (case-insensitive). Found headings: ${JSON.stringify(headings)}`
);
});
test('has required ADR section', () => {
const content = readStandardsDoc();
const headings = parseH2Headings(content);
const hasAdrSection = headings.some((h) => /adr/i.test(h));
assert.ok(
hasAdrSection,
`Expected an ## heading containing "ADR" (case-insensitive). Found headings: ${JSON.stringify(headings)}`
);
});
test('has required AI-agent section', () => {
const content = readStandardsDoc();
const headings = parseH2Headings(content);
const hasAgentSection = headings.some((h) => /ai.?agent|agent.?assist/i.test(h));
assert.ok(
hasAgentSection,
`Expected an ## heading containing "AI-agent" or "agent-assist" (case-insensitive). Found headings: ${JSON.stringify(headings)}`
);
});
test('references CONTEXT.md', () => {
const content = readStandardsDoc();
assert.ok(
content.includes('CONTEXT.md'),
'docs/contributor-standards.md must reference CONTEXT.md'
);
});
test('references docs/adr/', () => {
const content = readStandardsDoc();
assert.ok(
content.includes('docs/adr/'),
'docs/contributor-standards.md must reference docs/adr/'
);
});
});
/**
* Parity: docs/contributor-standards.md tells contributors which CONTEXT.md sections to
* write into. If it names a heading CONTEXT.md does not have, the instruction is
* unfollowable — and that is not hypothetical: on 2026-07-27 it named `## Domain terms`
* and `## AI Ops Memory`, neither of which has ever existed (#2721). Two surfaces, one
* truth; this asserts they cannot diverge again.
*/
const CONTEXT_MD = path.join(REPO_ROOT, 'CONTEXT.md');
/**
* The body of one `## ` section, exclusive of the next `## `. Split on /\r?\n/ so a CRLF
* checkout parses identically. Shared by every assertion below — two copies of the same
* section parser is exactly the silent divergence RULESET.SHARED-HELPERS-LINT-VS-TEST warns of.
*/
function sectionBody(content, heading) {
const lines = content.split(/\r?\n/);
const start = lines.findIndex((l) => l.trim() === heading);
if (start === -1) return null;
const rest = lines.slice(start + 1);
const end = rest.findIndex((l) => /^##\s/.test(l));
return (end === -1 ? rest : rest.slice(0, end)).join('\n');
}
/**
* Backticked heading references that the standards doc attributes to CONTEXT.md.
*
* Scoped to the doc's own `## CONTEXT.md` section on purpose. The doc also names headings
* belonging to *other* documents — `## Decision` and `## Consequences` describe an ADR
* body, `## Standards followed` describes an issue/PR body. Extracting doc-wide would
* demand CONTEXT.md grow headings that have nothing to do with it.
*
* `<Placeholder>` templates like `### <Module Name>` are skipped: they are shapes to
* follow, not headings to find.
*/
function extractContextHeadingRefs(standardsContent) {
const scope = sectionBody(standardsContent, '## CONTEXT.md');
if (scope === null) return [];
const refs = new Set();
for (const m of scope.matchAll(/`(#{2,6}\s+[^`]+)`/g)) {
const heading = m[1].trim();
if (heading.includes('<')) continue;
refs.add(heading);
}
return [...refs];
}
/** Headings actually present in CONTEXT.md. Anchored /m — CRLF-safe without a `\n` split. */
function actualHeadings(contextContent) {
return new Set([...contextContent.matchAll(/^#{2,6}\s+.*$/gm)].map((m) => m[0].trim()));
}
describe('docs/contributor-standards.md ↔ CONTEXT.md heading parity', () => {
test('everyContextHeadingNamedByTheStandardsDocExistsInContextMd', () => {
const refs = extractContextHeadingRefs(readStandardsDoc());
const actual = actualHeadings(fs.readFileSync(CONTEXT_MD, 'utf-8'));
assert.ok(refs.length > 0, 'the standards doc must name at least one CONTEXT.md heading');
const missing = refs.filter((r) => !actual.has(r));
assert.deepEqual(
missing,
[],
`docs/contributor-standards.md directs contributors to heading(s) that do not exist in ` +
`CONTEXT.md: ${JSON.stringify(missing)}. Fix the standards doc (or add the heading).`
);
});
test('contextMdStillHasTheGlossaryHeadingTheStandardsDocNamed', () => {
const actual = actualHeadings(fs.readFileSync(CONTEXT_MD, 'utf-8'));
assert.ok(
actual.has('## Glossary — Domain modules and seams'),
'the glossary heading is the one the standards doc points Module authors at'
);
});
// Negative space for the extractor itself. The RED run of this suite flagged
// `## Decision`, `## Consequences` and `## Standards followed` — all headings the doc
// attributes to an ADR body or a PR body, not to CONTEXT.md. A doc-wide extractor would
// demand CONTEXT.md sprout headings that do not belong to it.
test('doesNotTreatAdrOrPrBodyHeadingsAsContextMdClaims', () => {
const refs = extractContextHeadingRefs(readStandardsDoc());
for (const foreign of ['## Decision', '## Consequences', '## Standards followed']) {
assert.ok(
!refs.includes(foreign),
`${foreign} describes another document's structure and must not be read as a CONTEXT.md claim`
);
}
});
test('matchesAHeadingReferenceRegardlessOfLineEndingStyle', () => {
const crlf = '## Test rules and lint\r\n\r\n### Emitted Artifact Provenance\r\n';
const found = actualHeadings(crlf);
assert.ok(found.has('## Test rules and lint'), 'a CRLF checkout must not defeat the match');
assert.ok(found.has('### Emitted Artifact Provenance'));
});
});
/**
* The Emitted Artifact Provenance naming deliverable (#2721). Without these, the artifact
* family that half the open PR queue collides on still has no name a contributor can look
* up — which ADR-2719 identifies as a direct cause of the problem.
*/
describe('CONTEXT.md names the emitted-artifact family', () => {
test('emittedAttributionRulesetIsUnderTheTestRulesAndLintSection', () => {
const body = sectionBody(fs.readFileSync(CONTEXT_MD, 'utf-8'), '## Test rules and lint');
assert.ok(body, 'CONTEXT.md must have a `## Test rules and lint` section');
assert.ok(
body.includes('RULESET.EMITTED_ATTRIBUTION='),
'RULESET.EMITTED_ATTRIBUTION must be a sibling of the other test rules, not floating elsewhere'
);
});
test('emittedArtifactProvenanceIsRegisteredInTheGlossary', () => {
const body = sectionBody(
fs.readFileSync(CONTEXT_MD, 'utf-8'),
'## Glossary — Domain modules and seams'
);
assert.ok(body, 'CONTEXT.md must have the glossary section');
assert.ok(
body.includes('### Emitted Artifact Provenance'),
'the emitted-artifact family must be registered in the glossary'
);
});
test('emittedArtifactProvenanceIsAConceptNotAModule', () => {
const headings = actualHeadings(fs.readFileSync(CONTEXT_MD, 'utf-8'));
assert.ok(headings.has('### Emitted Artifact Provenance'));
assert.ok(
!headings.has('### Emitted Artifact Provenance Module'),
'it ships nothing, so it takes no `Module` suffix — follows the `### Resolution Provenance` precedent'
);
});
});
describe('CONTRIBUTING.md links contributor-standards.md', () => {
test('CONTRIBUTING.md contains link to contributor-standards.md', () => {
let contributing;
try {
contributing = fs.readFileSync(CONTRIBUTING_MD, 'utf-8');
} catch (err) {
assert.fail(`CONTRIBUTING.md does not exist: ${err.message}`);
}
assert.ok(
contributing.includes('contributor-standards.md'),
'CONTRIBUTING.md must link to docs/contributor-standards.md'
);
});
});