Files
msd-core/tests/compact-content-partition-guard.test.cjs
Tom Boucher a0f8f956c4 enhance(#4139): Phase 3 — partition rules + the five checks (#4497)
* enhance(#4139): Phase 3 — partition rules + the five checks

ADR-4139 Decision 5, epic #4139 Phase 3. Issue #4403's own "Proposed behavior"
section lists four checks; the ADR's Decision 5 and its own phase table ("partition
rules + the five checks") list five — the same four plus "boundary moves are
declared, ongoing". Same issue-vs-ADR drift Phase 2 hit on the detail.md vs
detail/*.md layout: the ADR is the locked, reviewed document, so it wins. This PR
implements all five.

docs/PARTITION-RULES.md (new) is the partition-rules document: the partition rule
itself, the protected-content list and <!-- gsd:protected --> sentinel syntax
(relocated unchanged from gsd-core/references/compact-content-protected-content.md,
now deleted — it was never referenced by any runtime workflow Read, only by the
predecessor test as documentation, so nothing at runtime regresses, and removing it
from gsd-core/references/ also drops it from all 19 installed-project shipped-content
trees for a file nothing ever read), and the five checks explained for a human
reader. Referenced from a new CONTRIBUTING.md subsection under "Editing shipped
content".

tests/helpers/compact-content-split.cjs (new) is the shared mechanics: split
discovery (any gsd-core/workflows/<name>/detail/*.md paired with <name>.md — no
registry file, a pair is registered by existing on disk), line normalization
(carries forward Phase 2's bare-label-line isTrivial fix and the canonical
gsd_run-launcher-preamble exclusion), sentinel extraction, and a
Boundary-Move-Declared commit-trailer reader that is a direct structural port of
tests/helpers/emitted-runtime.cjs's Emitted-Drift-Ack-Hash/-Growth trailer reader
(ADR-3942) — same merge-base range, same fail-closed throw on an uncomputable range,
same dedupe/conflict rules.

tests/compact-content-partition-guard.test.cjs (new) is the actual guard, superseding
tests/plan-phase-compact-split.test.cjs (deleted — its per-pair checks are now the
general guard's job for plan-phase specifically). Checks 2 (disjointness) and 3
(registration + size cap) run unconditionally against every registered split. Checks
1 (completeness, fires once per split on the PR that introduces a new detail/ path),
4 (protected content — no trailer can ever excuse this one, unlike check 5) and 5
(boundary moves declared) are PR-diff-scoped against the resolved base ref and skip
cleanly when there's nothing to compare (a fresh clone, no PR in flight) — a
deliberate asymmetry from check 5's trailer reader, which must throw rather than
silently pass when ITS range is uncomputable, since that function is answering "did
this PR declare its moves" rather than "is there even a diff to look at". Each of the
five checks carries a RED (deliberately broken fixture) / GREEN (fixed) test pair,
built against synthetic temp files or real throwaway git repos, per this repo's rule
that a guard nobody has seen go red is not yet a guard. Building the real fixtures
caught and fixed one real bug before it shipped: check 4's line-presence test was
using the trivial-line-filtered normalizer, so a byte-identical spine falsely
reported its own protected code-fence line as "deleted" — fixed with a
non-filtering membership check.

Extending docs/INVENTORY.md's "Workflow Sub-Files" table for `detail` surfaced a
pre-existing, unrelated gap in the SAME area: gsd-core/workflows/<name>/templates/*.md
is a fourth workflow sub-file kind that already existed on disk and was already known
to lint-response-language-coverage.cjs's FRAGMENT_DIRS, but was invisible to
gen-inventory-manifest.cjs and undocumented in that table. Fixed alongside it, same
pattern, same PR, rather than deferred.

Also, mechanically required by the new fourth sub-file kind:
- scripts/lint-response-language-coverage.cjs: `detail` added to FRAGMENT_DIRS
  alongside modes/steps/templates — a detail/<part>.md inherits its parent's
  response_language coverage through the same per-file proof, not a parallel one.
- tests/workflow-size-budget.test.cjs: explicit regression test locking that
  detail/ files are governed solely by the hard, non-waivable NEW_FILE_CAP
  (tests/helpers/emitted-diff.cjs) and never by the XL/LARGE/DEFAULT spine tiers —
  true by construction (measureWorkflows/listWorkflowStems don't recurse), made
  explicit per the issue's own Done-when item rather than left true-by-omission.
- scripts/gen-inventory-manifest.cjs: `workflow_detail` and `workflow_templates`
  NESTED_FAMILIES entries; docs/INVENTORY-MANIFEST.json regenerated
  (plan-phase/detail/elaboration.md, discuss-phase/templates/*.md now tracked);
  docs/INVENTORY.md's table updated to four kinds.

Verified: `npm run lint:ci` clean with the eslint cache cleared.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(#4403): review findings + a real gsd-test failure in the new guard

Two orthogonal review passes (Standards + Spec, isolated sub-agents) plus a
separate security review ran against the prior commit. Fixed everything each
surfaced:

- Security (Low, path-traversal existence oracle): checkRegistration's
  dangling-reference check extracted detail-path-shaped substrings from spine
  PROSE via a regex that permits `.`/`/` freely, then joined them onto repoRoot
  and probed fs.existsSync with no containment check — a spine file containing
  `../../../etc/detail/passwd.md`-shaped text could make the guard test file
  existence outside the repo. Added a path.relative-based containment check
  before the fs.existsSync call; anything that resolves outside repoRoot is now
  reported as a dangling reference directly, never probed on disk.
- Standards (Boundary Coverage): the size-cap fixtures covered NEW_FILE_CAP and
  NEW_FILE_CAP-1 but not NEW_FILE_CAP+1 — added the third boundary-point case
  CLAUDE.md's TEST RULES require (limit-1/limit/limit+1).
- Standards (Property-Based Testing): extractProtectedBlocks (a sentinel
  parser) and the new parseBoundaryMoveTrailerValues (a declare/dedupe/conflict
  parser, bijective-shaped) had no fast-check property test. Added three: a
  render/parse bijectivity property for the trailer parser (mirroring the exact
  ADR-3942 sibling test's alphabet/idiom), a dedupe-is-idempotent property for
  the same parser, and a well-formed-sentinel-round-trips property for
  extractProtectedBlocks.

Then dispatched gsd-test on the resulting commit. It found a real bug the
reviews couldn't have caught (none of them can run inside gsd-test's sandbox):
checks 4/5's real-repo assertion failed against plan-phase's own split,
reporting DISK_PLANS/#3218-comment lines as "undeclared boundary moves" —
content Phase 2 (#4402) legitimately moved into detail/elaboration.md months
before this PR's Boundary-Move-Declared mechanism existed to require a
trailer for it. Root cause: `resolveBase()`'s own doc comment already documents
that no `origin/*` remote-tracking ref exists inside the gsd-test sandbox
container, and its fallback candidate (a bare `next` branch) can resolve to a
point in history that predates an already-merged, already-reviewed split —
making that split look "newly introduced" from the sandbox's vantage point.
Check 1 (completeness) already scopes itself correctly to only genuinely-new
detail paths (git diff status 'A'); checks 4 and 5 did not share that scoping,
so a stale base made them re-litigate a settled split retroactively. Fixed by
having checks 4/5 skip any split name check 1 already counted as newly-split —
their own premise ("did an EXISTING split shed/undeclare something") does not
apply to a split that is, from the resolved base's vantage point, brand new;
that is check 1's domain alone. Verified locally (25/25 tests pass via a
direct `node -e` require, since `node --test` is blocked in this repo) and via
re-reasoning through the exact real-repo scenario the gsd-test failure showed.

Also regenerated all 19 tests/fixtures/install-tree/*.json goldens — the
prior commit's deletion of gsd-core/references/compact-content-protected-content.md
was never reflected there, which is what golden-install-tree.test.cjs's other
19 failures in the same gsd-test run were.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* docs(#4403): backfill changeset pr number to 4497

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(#4403): isolate codex-config.test.cjs into its own chunk, root-causing the Windows CI failure

PR #4497's "full test (windows-latest, 24, shard 2/3)" job failed: run-tests
killed chunk 3/8 at the 600s per-chunk backstop, with codex-config.test.cjs
(weight 17.87, by far the chunk's dominant cost) packed alongside 39 other
files. Traced, not assumed:

- scripts/run-tests.cjs's own timeout-headroom comment for the OUTER
  per-shard timeout documents that "adding one test file reshuffled 115 of
  268 unit files between shards" — shard/chunk composition is architecturally
  known to be unstable to single-file additions, which is exactly what this
  PR's own new tests/compact-content-partition-guard.test.cjs is.
- A second comment, dated 2026-09-06 (one day before this PR, PR #4428's own
  CI), already documents the SAME chunk hitting the SAME 600s backstop with
  the SAME file (codex-config.test.cjs, "a genuinely MEASURED weight of
  17.87 — not a stale-table miss") dominating it — the fix then was cutting
  the Windows per-chunk budget from 60 to 40. That cut clearly was not
  enough: two documented incidents in two days, at two different budget
  settings, both centered on one file that alone consumes ~45% of even the
  reduced Windows budget.
- tests/test-timings.json's own header confirms its source data
  (test-events-linux-node22/24.jsonl) is Linux-only, and run-tests.cjs's own
  chunk-timeout diagnostic already prints "real Windows cost runs ~2.2x the
  recorded figure" — the packer's weight-balancing is working off data that
  is both stale (table last regenerated 2026-08-07) and known to
  underestimate the platform where the failure occurs.

Given codex-config.test.cjs is disproportionately heavy AND every companion
sharing its chunk is decided by a packing algorithm already documented as
reshuffling unpredictably on any new file, tuning the shared budget a third
time only moves the marginal line to wherever the next new file happens to
land — it does not remove the gamble. Isolating codex-config.test.cjs into
its own dedicated single-file chunk, unconditionally and on every platform,
removes it at the source: the file never enters the pool packChunks balances,
so no other file's packing changes, and no future single-file addition
(mine or anyone else's) can silently reintroduce this exact failure by
landing in its chunk.

Extracted as a small pure function, partitionIsolatedFiles (mirroring this
file's existing pattern of pulling packing/analysis logic out of main() for
in-process unit coverage — see computeSweepProtectSet, analyzeChunkEvents),
with 6 new tests in tests/run-tests-harness.test.cjs covering basename
matching across path separators, near-miss non-matches, the empty-list case,
and the isolated-set contents.

Root cause is now closed rather than papered over with a retry: this failure
is a property of one specific heavy file's chunk placement, not something
that recurs randomly. If codex-config.test.cjs itself is ever genuinely sped
up, this isolation can be revisited — this is a packing-side mitigation for
a known file's cost, not a claim the cost is irreducible.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 16:44:53 -04:00

839 lines
41 KiB
JavaScript

'use strict';
// docs-guard-exempt: mentions docs/PARTITION-RULES.md only as a "see" pointer in the
// module docstring below, never reads its content — the checks this file implements are
// verified against docs/PARTITION-RULES.md's own prose by a human reviewer at authoring
// time, not by this test re-parsing that document at runtime.
/**
* tests/compact-content-partition-guard.test.cjs — ADR-4139 Decision 5, epic #4139,
* Phase 3 #4403. This is the general-purpose CI guard PARTITION-RULES.md describes: it
* implements the five checks against every `<name>/detail/*.md` split registered under
* `gsd-core/workflows/`, and supersedes the one-off `tests/plan-phase-compact-split.test.cjs`
* pilot (deleted by this same change).
*
* See `docs/PARTITION-RULES.md` for the operational spec — it is the source of truth for
* what each check does; this file is the mechanics plus the failing-first fixtures that
* prove each check can actually fail (per this repo's rule that a guard nobody has seen go
* red is not yet a guard).
*
* Layout:
* - checks 2/3 (disjointness, registration) run unconditionally against the real repo —
* they need no PR diff, just the files on disk.
* - checks 1/4/5 (completeness, protected content, boundary moves) are PR-diff-scoped:
* they compare the resolved base ref (origin/next, mirroring the base-ref-resolution
* idiom `tests/helpers/emitted-runtime.cjs`'s `resolveBase` already implements) against
* HEAD. When no base ref resolves (a fresh clone, a shallow/detached CI context), they
* skip cleanly — this is the ambient-run guard the dispatch brief calls out, and it is
* deliberately asymmetric with `readBoundaryMoveTrailers`'s own contract: THAT function
* throws on an uncomputable range because it is answering "did THIS PR declare its
* moves", and silently passing would disarm it; the ambient guard here is answering "is
* there even a PR diff to look at", which is a different, structurally prior question.
* - each of the 5 checks gets a fixture `describe` block with a RED (deliberately broken)
* and a GREEN (fixed) sibling test, built against synthetic temp files/repos — never
* against this repo's own real splits, so the fixture is independent of whatever the
* real tree happens to contain.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
const crypto = require('node:crypto');
const fc = require('fast-check');
const { cleanup } = require('./helpers.cjs');
const { gitOrThrow, GIT_FIXTURE_TIMEOUT_MS } = require('./helpers/git-fixture.cjs');
const { runGit, OUTCOME } = require('./helpers/process-seam.cjs');
const { REPO_ROOT, GIT_TIMEOUT_MS, safeDirArgs, resolveBase } = require('./helpers/emitted-runtime.cjs');
const { ACK_TRAILER_DELIM } = require('./helpers/emitted-diff.cjs');
const {
DEFAULT_WORKFLOWS_DIR,
discoverRegisteredSplits,
normalizeNonTrivialLines,
extractProtectedBlocks,
readBoundaryMoveTrailers,
parseBoundaryMoveTrailerValues,
checkDisjointness,
checkRegistration,
checkDetailFileSizeCap,
NEW_FILE_CAP,
} = require('./helpers/compact-content-split.cjs');
// ─── git plumbing local to this guard (checks 1/4/5's PR-diff orchestration) ──────────
/** `<repoRoot>`-relative, forward-slash path, matching the trailer-key / reference shape
* PARTITION-RULES.md and `checkRegistration` both use. */
function toRepoRelative(absPath) {
return path.relative(REPO_ROOT, absPath).split(path.sep).join('/');
}
/** `git merge-base <baseRef> HEAD`, or `null` on any failure (ambient guard: this is a
* "nothing to compare" signal, never a hard error, for the reasons in the file header). */
function computeMergeBaseSha(baseRef) {
const result = runGit([...safeDirArgs(REPO_ROOT), 'merge-base', baseRef, 'HEAD'], {
cwd: REPO_ROOT,
timeoutMs: GIT_TIMEOUT_MS,
});
if (result.outcome !== OUTCOME.EXITED || result.exitCode !== 0) return null;
const sha = result.stdout.trim();
return /^[0-9a-f]{40}$/.test(sha) ? sha : null;
}
/** `git diff --name-status <mergeBaseSha>...HEAD -- gsd-core/workflows`, parsed into
* `{status, path}` rows (`path` is always the CURRENT path — the second field of an
* `R###\told\tnew` rename row, or the single field of every other status). `null` on
* any git failure (ambient guard). */
function diffNameStatusForWorkflows(mergeBaseSha) {
const result = runGit(
[...safeDirArgs(REPO_ROOT), 'diff', '--name-status', `${mergeBaseSha}...HEAD`, '--', 'gsd-core/workflows'],
{ cwd: REPO_ROOT, timeoutMs: GIT_TIMEOUT_MS },
);
if (result.outcome !== OUTCOME.EXITED || result.exitCode !== 0) return null;
return result.stdout
.split('\n')
.map((l) => l.trim())
.filter(Boolean)
.map((line) => {
const fields = line.split('\t');
return { status: fields[0][0], path: fields[fields.length - 1] };
});
}
/** `git show <ref>:<relPath>`, or `null` if the path is absent at `ref` (or on any other
* git failure — see the file header on why this check family never throws for that). */
function showAtRefOrNull(ref, relPath) {
const result = runGit([...safeDirArgs(REPO_ROOT), 'show', `${ref}:${relPath}`], {
cwd: REPO_ROOT,
timeoutMs: GIT_TIMEOUT_MS,
});
return result.outcome === OUTCOME.EXITED && result.exitCode === 0 ? result.stdout : null;
}
// ─── pure check logic (the "lower-level functions" the fixtures call directly) ────────
/**
* Check 1 (completeness): the union of the new spine + new detail parts must be a
* superset of the OLD spine's non-trivial lines, and the new spine must be smaller.
* `oldSpineContent === null` (no comparison base — a wholly new split with nothing to be
* complete relative to) short-circuits to no violations at all, per PARTITION-RULES.md.
*/
function checkCompletenessForPair({ splitName, oldSpineContent, newSpineContent, newDetailContents }) {
if (oldSpineContent === null) return [];
const violations = [];
const oldLines = normalizeNonTrivialLines(oldSpineContent);
const unionSet = new Set([
...normalizeNonTrivialLines(newSpineContent),
...newDetailContents.flatMap((c) => normalizeNonTrivialLines(c)),
]);
for (const line of oldLines) {
if (!unionSet.has(line)) violations.push({ kind: 'incomplete_split', splitName, line });
}
const oldSize = Buffer.byteLength(oldSpineContent, 'utf8');
const newSize = Buffer.byteLength(newSpineContent, 'utf8');
if (!(newSize < oldSize)) {
violations.push({ kind: 'spine_not_smaller', splitName, oldSize, newSize });
}
return violations;
}
/**
* All non-blank, trimmed lines of `content`, WITHOUT `normalizeNonTrivialLines`'s
* boilerplate filter. Check 4 needs this distinct membership set: a protected block can
* legitimately enclose an `isTrivial`-shaped line (most commonly a code-fence delimiter,
* e.g. a `<failing_direction_contract>` example's own ```` ``` ````), and that line is
* just as much "content the block covers" as any prose line — dropping it from the
* membership test would report a byte-identical spine as having "deleted" its own fence,
* a false positive caught by this guard's own real-repo sanity run against plan-phase.md
* before this fix (the fence lines were being tested for presence in a
* `normalizeNonTrivialLines`-filtered set, which strips exactly that shape).
*/
function allNonBlankLines(content) {
return content.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
}
/**
* Check 4 (protected content): every line inside a `<!-- gsd:protected -->` sentinel at
* the OLD spine must still be physically present in the NEW spine. A missing line that
* now appears in one of the spine's detail parts is reported as `protected_relocated`
* (named destination); any other missing line is `protected_deleted`. No trailer can ever
* excuse either — this function does not even look for one.
*/
function checkProtectedContentForPair({ splitName, oldSpineContent, newSpineContent, detailPathToContent }) {
if (oldSpineContent === null) return [];
const violations = [];
const oldBlocks = extractProtectedBlocks(oldSpineContent).blocks;
const newSpineLines = new Set(allNonBlankLines(newSpineContent));
for (const block of oldBlocks) {
for (const line of block.lines) {
if (newSpineLines.has(line)) continue;
let relocatedTo = null;
for (const [relPath, content] of detailPathToContent) {
if (allNonBlankLines(content).includes(line)) {
relocatedTo = relPath;
break;
}
}
violations.push({
kind: relocatedTo ? 'protected_relocated' : 'protected_deleted',
splitName,
blockFirstLine: block.firstLine,
line,
relocatedTo,
});
}
}
return violations;
}
/**
* Check 5's moved-line detector: non-trivial lines removed from the spine (present at the
* old ref, absent now) that are simultaneously newly present in one of the spine's OWN
* detail parts (absent from that detail part's old content — or the part is brand new —
* present now). `excludeLines` drops anything already counted as a check-4 protected
* violation: a protected line moving is ALWAYS a hard failure regardless of a trailer
* (check 4's job), so check 5 only concerns itself with ordinary lines.
*/
function findMovedLines({ oldSpineContent, newSpineContent, oldDetailContentByPath, newDetailContentByPath, excludeLines }) {
const oldSpineLines = new Set(normalizeNonTrivialLines(oldSpineContent));
const newSpineLines = new Set(normalizeNonTrivialLines(newSpineContent));
const removedFromSpine = new Set([...oldSpineLines].filter((l) => !newSpineLines.has(l)));
const moved = new Set();
for (const [relPath, newContent] of newDetailContentByPath) {
const oldContent = oldDetailContentByPath.has(relPath) ? oldDetailContentByPath.get(relPath) : null;
const oldDetailLines = new Set(oldContent === null ? [] : normalizeNonTrivialLines(oldContent));
for (const line of normalizeNonTrivialLines(newContent)) {
if (removedFromSpine.has(line) && !oldDetailLines.has(line) && !excludeLines.has(line)) {
moved.add(line);
}
}
}
return [...moved];
}
/**
* Check 5 (boundary moves declared): wraps `findMovedLines` with the trailer lookup. When
* there is nothing moved, `readBoundaryMoveTrailers` is never even called — no PR-shaped
* question to ask. When there IS a moved line, the trailer read is NOT guarded: a failure
* there must throw and propagate, per PARTITION-RULES.md check 5 / the file header's
* asymmetry note.
*/
function checkBoundaryMovesForSplit({
splitName,
spineRelPath,
oldSpineContent,
newSpineContent,
oldDetailContentByPath,
newDetailContentByPath,
excludeLines,
baseRef,
cwd,
}) {
const moved = findMovedLines({
oldSpineContent,
newSpineContent,
oldDetailContentByPath,
newDetailContentByPath,
excludeLines,
});
if (moved.length === 0) return [];
const { declarations } = readBoundaryMoveTrailers({ baseRef, cwd });
if (declarations.has(spineRelPath)) return [];
return moved.map((line) => ({ kind: 'undeclared_boundary_move', splitName, spinePath: spineRelPath, line }));
}
/**
* Full PR-diff-scoped orchestration for checks 1, 4, 5 against the REAL repo. Returns
* `{skipped: true, reason}` when there is genuinely nothing to compare (ambient guard);
* otherwise `{skipped: false, completeness, protectedContent, boundaryMoves}`.
*/
function runPrDiffScopedChecks() {
const base = resolveBase();
if (!base) return { skipped: true, reason: 'no resolvable base ref (origin/next unavailable)' };
const mergeBaseSha = computeMergeBaseSha(base.ref);
if (!mergeBaseSha) return { skipped: true, reason: `merge-base unresolvable for "${base.ref}..HEAD"` };
const diff = diffNameStatusForWorkflows(mergeBaseSha);
if (diff === null) return { skipped: true, reason: 'git diff --name-status failed' };
const splits = discoverRegisteredSplits().filter((s) => s.spineExists);
const completeness = [];
const protectedContent = [];
const boundaryMoves = [];
// Check 1: only for detail paths newly ADDED by this diff, grouped by split name.
const newlySplitNames = new Set();
for (const { status, path: p } of diff) {
if (status !== 'A') continue;
const m = /^gsd-core\/workflows\/([^/]+)\/detail\/[^/]+\.md$/.exec(p);
if (m) newlySplitNames.add(m[1]);
}
for (const name of newlySplitNames) {
const split = splits.find((s) => s.name === name);
if (!split) continue;
const spineRel = toRepoRelative(split.spinePath);
const oldSpineContent = showAtRefOrNull(mergeBaseSha, spineRel);
const newSpineContent = fs.readFileSync(split.spinePath, 'utf8');
const newDetailContents = split.detailPaths.map((p) => fs.readFileSync(p, 'utf8'));
completeness.push(
...checkCompletenessForPair({ splitName: name, oldSpineContent, newSpineContent, newDetailContents }),
);
}
// Checks 4 + 5: every registered split NOT already counted as newly-split above (an
// untouched spine trivially produces zero violations either way, since old === new
// content — see the file header).
//
// A split in `newlySplitNames` is excluded here, not merely redundant with check 1.
// Checks 4/5 both premise an OLD spine that already carried the content in question —
// "did an EXISTING split shed protected content or move something undeclared". A
// split whose detail path did not exist at the resolved base has no such premise: it
// is brand new relative to that base, which is check 1's domain alone. This also
// makes checks 4/5 robust to a base ref that resolves further back than expected (a
// known class of gotcha `resolveBase()`'s own doc comment describes — no `origin/*`
// remote-tracking refs inside the gsd-test sandbox): a stale/older base makes an
// already-merged, already-reviewed split look "newly introduced" from that base's
// vantage point, and retroactively demanding a Boundary-Move-Declared trailer for a
// split that predates the trailer mechanism's own existence is exactly the false
// positive this exclusion prevents. Verified against the real repo: plan-phase's
// split (Phase 2, #4402) landed before this PR (#4403) introduces Boundary-Move-Declared
// at all, so it must never be checked against that requirement retroactively.
for (const split of splits) {
if (newlySplitNames.has(split.name)) continue;
const spineRel = toRepoRelative(split.spinePath);
const oldSpineContent = showAtRefOrNull(mergeBaseSha, spineRel);
if (oldSpineContent === null) continue; // no old spine to compare against at this ref
const newSpineContent = fs.readFileSync(split.spinePath, 'utf8');
const detailPathToContent = new Map(split.detailPaths.map((p) => [toRepoRelative(p), fs.readFileSync(p, 'utf8')]));
const protectedViolations = checkProtectedContentForPair({
splitName: split.name,
oldSpineContent,
newSpineContent,
detailPathToContent,
});
protectedContent.push(...protectedViolations);
const excludeLines = new Set(protectedViolations.map((v) => v.line));
const oldDetailContentByPath = new Map();
for (const relPath of detailPathToContent.keys()) {
oldDetailContentByPath.set(relPath, showAtRefOrNull(mergeBaseSha, relPath));
}
boundaryMoves.push(
...checkBoundaryMovesForSplit({
splitName: split.name,
spineRelPath: spineRel,
oldSpineContent,
newSpineContent,
oldDetailContentByPath,
newDetailContentByPath: detailPathToContent,
excludeLines,
baseRef: base.ref,
cwd: REPO_ROOT,
}),
);
}
return { skipped: false, completeness, protectedContent, boundaryMoves };
}
// ─── real-repo assertions (checks 2/3 unconditional; 1/4/5 PR-diff-scoped) ────────────
describe('compact-content partition guard — real repo state (ADR-4139 Decision 5, #4403)', () => {
test('check 2 (disjointness): no non-trivial line duplicated between any spine and its detail parts', () => {
const splits = discoverRegisteredSplits().filter((s) => s.spineExists);
const violations = checkDisjointness(splits);
assert.deepStrictEqual(violations, [], `disjointness violations: ${JSON.stringify(violations, null, 2)}`);
});
test('check 3 (registration): every split is paired, referenced, and has no dangling reference', () => {
const splits = discoverRegisteredSplits();
const violations = checkRegistration(splits, DEFAULT_WORKFLOWS_DIR);
assert.deepStrictEqual(violations, [], `registration violations: ${JSON.stringify(violations, null, 2)}`);
});
test('check 3 (size cap): every detail file is under NEW_FILE_CAP', () => {
const splits = discoverRegisteredSplits().filter((s) => s.spineExists);
const violations = checkDetailFileSizeCap(splits);
assert.deepStrictEqual(violations, [], `size-cap violations: ${JSON.stringify(violations, null, 2)}`);
});
test('checks 1, 4, 5: PR-diff-scoped checks against the resolved base ref (skips cleanly when unresolvable)', () => {
const result = runPrDiffScopedChecks();
if (result.skipped) {
// Ambient guard: nothing to compare (no origin/next, no computable merge-base, no
// readable diff). Not a failure — see the file header for why this is different
// from check 5's own uncomputable-range throw.
return;
}
assert.deepStrictEqual(
result.completeness, [],
`completeness (check 1) violations: ${JSON.stringify(result.completeness, null, 2)}`,
);
assert.deepStrictEqual(
result.protectedContent, [],
`protected-content (check 4) violations: ${JSON.stringify(result.protectedContent, null, 2)}`,
);
assert.deepStrictEqual(
result.boundaryMoves, [],
`undeclared boundary-move (check 5) violations: ${JSON.stringify(result.boundaryMoves, null, 2)}`,
);
});
test('plan-phase spine references the shared compact-content gate exactly once (real-repo sanity, folded in from the retired pilot test)', () => {
const spine = fs.readFileSync(path.join(DEFAULT_WORKFLOWS_DIR, 'plan-phase.md'), 'utf8');
const matches = spine.match(/compact-content-gate\.md/g) || [];
assert.strictEqual(matches.length, 1, `expected exactly one reference to compact-content-gate.md, found ${matches.length}`);
});
test('every registered spine has no malformed gsd:protected sentinel', () => {
const splits = discoverRegisteredSplits().filter((s) => s.spineExists);
for (const split of splits) {
const { malformed } = extractProtectedBlocks(fs.readFileSync(split.spinePath, 'utf8'));
assert.deepStrictEqual(
malformed, [],
`${split.spinePath} has malformed gsd:protected sentinel(s): ${JSON.stringify(malformed, null, 2)}`,
);
}
});
test('plan-phase spine: protected-block count and the four named categories are represented (real-repo sanity, folded in from the retired pilot test)', () => {
const spinePath = path.join(DEFAULT_WORKFLOWS_DIR, 'plan-phase.md');
const spine = fs.readFileSync(spinePath, 'utf8');
const { blocks, malformed } = extractProtectedBlocks(spine);
assert.deepStrictEqual(malformed, []);
const paired = blocks.filter((b) => b.kind === 'paired');
const single = blocks.filter((b) => b.kind === 'single');
assert.ok(paired.length >= 4, `expected at least 4 paired protected blocks, found ${paired.length}`);
assert.ok(single.length >= 2, `expected at least 2 single-line protected markers, found ${single.length}`);
// Output-format contracts:
assert.match(spine, /<!-- gsd:protected:start -->\s*<quality_gate>/, 'quality_gate output-format contract must be protected');
assert.match(spine, /<!-- gsd:protected:start -->\s*<success_criteria>/, 'success_criteria output-format contract must be protected');
assert.match(spine, /<!-- gsd:protected:start -->\s*<downstream_consumer>/, 'downstream_consumer output-format contract must be protected');
// Few-shot example the workflow's own steps depend on:
assert.match(spine, /<!-- gsd:protected:start -->\s*<failing_direction_contract>/, 'failing_direction_contract few-shot example must be protected');
// Negative instruction / guardrail:
const guardrailCount = (spine.match(/<!-- gsd:protected -->\s*> \*\*ORCHESTRATOR RULE[^]*?Never call `ScheduleWakeup`/g) || []).length;
assert.strictEqual(guardrailCount, 2, `expected 2 protected ScheduleWakeup guardrail paragraphs, found ${guardrailCount}`);
});
});
// ─── failing-first fixtures: one describe block per check, RED then GREEN ─────────────
describe('failing-first fixture: check 1 (completeness)', () => {
test('RED — a line from the old spine is missing from the new spine+detail union, and the spine did not shrink', () => {
const oldSpineContent = 'Intro line.\n\nCritical instruction not carried over.\n\nClosing line.\n';
const newSpineContent = 'Intro line.\n\nClosing line.\n';
const newDetailContents = ['Some unrelated detail content.\n'];
const violations = checkCompletenessForPair({
splitName: 'broken-split', oldSpineContent, newSpineContent, newDetailContents,
});
assert.ok(violations.length > 0, 'expected at least one completeness violation');
assert.ok(
violations.some((v) => v.kind === 'incomplete_split' && v.line === 'Critical instruction not carried over.'),
`expected the missing line to be named: ${JSON.stringify(violations)}`,
);
});
test('GREEN — the union carries every old line and the spine shrank', () => {
const oldSpineContent = 'Intro line.\n\nCritical instruction not carried over.\n\nClosing line.\n';
const newSpineContent = 'Intro line.\n\nClosing line.\n';
const newDetailContents = ['Critical instruction not carried over.\n'];
const violations = checkCompletenessForPair({
splitName: 'fixed-split', oldSpineContent, newSpineContent, newDetailContents,
});
assert.deepStrictEqual(violations, []);
});
});
describe('failing-first fixture: check 2 (disjointness)', () => {
test('RED — a non-trivial line appears in both the spine and a detail part', () => {
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-partition-disjoint-'));
try {
const workflowsDir = path.join(tmpRoot, 'gsd-core', 'workflows');
fs.mkdirSync(path.join(workflowsDir, 'dup', 'detail'), { recursive: true });
const duplicatedLine = 'This exact sentence must never appear twice across the split.';
fs.writeFileSync(path.join(workflowsDir, 'dup.md'), `Spine intro.\n\n${duplicatedLine}\n\ngsd-core/workflows/dup/detail/part.md\n`);
fs.writeFileSync(path.join(workflowsDir, 'dup', 'detail', 'part.md'), `Detail intro.\n\n${duplicatedLine}\n`);
const splits = discoverRegisteredSplits(workflowsDir).filter((s) => s.spineExists);
const violations = checkDisjointness(splits);
assert.ok(violations.length > 0, 'expected at least one disjointness violation');
assert.ok(
violations.some((v) => v.line === duplicatedLine),
`expected the duplicated line to be named: ${JSON.stringify(violations)}`,
);
} finally {
cleanup(tmpRoot);
}
});
test('GREEN — the same line, rewritten distinctly on each side, is disjoint', () => {
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-partition-disjoint-ok-'));
try {
const workflowsDir = path.join(tmpRoot, 'gsd-core', 'workflows');
fs.mkdirSync(path.join(workflowsDir, 'dup', 'detail'), { recursive: true });
fs.writeFileSync(path.join(workflowsDir, 'dup.md'), 'Spine intro.\n\nSpine-only sentence.\n\ngsd-core/workflows/dup/detail/part.md\n');
fs.writeFileSync(path.join(workflowsDir, 'dup', 'detail', 'part.md'), 'Detail intro.\n\nDetail-only sentence.\n');
const splits = discoverRegisteredSplits(workflowsDir).filter((s) => s.spineExists);
const violations = checkDisjointness(splits);
assert.deepStrictEqual(violations, []);
} finally {
cleanup(tmpRoot);
}
});
});
describe('failing-first fixture: check 3 (registration)', () => {
test('RED — a detail/*.md exists with no matching spine (orphan)', () => {
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-partition-orphan-'));
try {
const workflowsDir = path.join(tmpRoot, 'gsd-core', 'workflows');
fs.mkdirSync(path.join(workflowsDir, 'orphan', 'detail'), { recursive: true });
fs.writeFileSync(path.join(workflowsDir, 'orphan', 'detail', 'part.md'), 'Orphaned detail content.\n');
const splits = discoverRegisteredSplits(workflowsDir);
const violations = checkRegistration(splits, workflowsDir);
assert.ok(violations.length > 0, 'expected at least one registration violation');
assert.ok(
violations.some((v) => v.kind === 'orphan_detail' && v.name === 'orphan'),
`expected the orphan split to be named: ${JSON.stringify(violations)}`,
);
} finally {
cleanup(tmpRoot);
}
});
test('GREEN — a spine is added that references the detail part by its full repo-relative path', () => {
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-partition-orphan-ok-'));
try {
const workflowsDir = path.join(tmpRoot, 'gsd-core', 'workflows');
fs.mkdirSync(path.join(workflowsDir, 'orphan', 'detail'), { recursive: true });
fs.writeFileSync(path.join(workflowsDir, 'orphan', 'detail', 'part.md'), 'Orphaned detail content.\n');
fs.writeFileSync(
path.join(workflowsDir, 'orphan.md'),
'Spine intro.\n\nSee gsd-core/workflows/orphan/detail/part.md for elaboration.\n',
);
const splits = discoverRegisteredSplits(workflowsDir);
const violations = checkRegistration(splits, workflowsDir);
assert.deepStrictEqual(violations, []);
} finally {
cleanup(tmpRoot);
}
});
test('RED (size cap) — a new detail file at or over NEW_FILE_CAP', () => {
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-partition-sizecap-'));
try {
const workflowsDir = path.join(tmpRoot, 'gsd-core', 'workflows');
fs.mkdirSync(path.join(workflowsDir, 'big', 'detail'), { recursive: true });
fs.writeFileSync(path.join(workflowsDir, 'big.md'), 'Spine.\n\ngsd-core/workflows/big/detail/part.md\n');
fs.writeFileSync(path.join(workflowsDir, 'big', 'detail', 'part.md'), 'x'.repeat(NEW_FILE_CAP));
const splits = discoverRegisteredSplits(workflowsDir).filter((s) => s.spineExists);
const violations = checkDetailFileSizeCap(splits);
assert.ok(violations.length > 0, 'expected at least one size-cap violation');
assert.ok(
violations.some((v) => v.detailPath.endsWith(path.join('big', 'detail', 'part.md'))),
`expected the oversized file to be named: ${JSON.stringify(violations)}`,
);
} finally {
cleanup(tmpRoot);
}
});
test('GREEN (size cap) — the same detail file kept just under NEW_FILE_CAP', () => {
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-partition-sizecap-ok-'));
try {
const workflowsDir = path.join(tmpRoot, 'gsd-core', 'workflows');
fs.mkdirSync(path.join(workflowsDir, 'big', 'detail'), { recursive: true });
fs.writeFileSync(path.join(workflowsDir, 'big.md'), 'Spine.\n\ngsd-core/workflows/big/detail/part.md\n');
fs.writeFileSync(path.join(workflowsDir, 'big', 'detail', 'part.md'), 'x'.repeat(NEW_FILE_CAP - 1));
const splits = discoverRegisteredSplits(workflowsDir).filter((s) => s.spineExists);
const violations = checkDetailFileSizeCap(splits);
assert.deepStrictEqual(violations, []);
} finally {
cleanup(tmpRoot);
}
});
test('RED (size cap) — a new detail file one byte OVER NEW_FILE_CAP (the third boundary point)', () => {
// Boundary coverage requires limit-1 / limit / limit+1 (CLAUDE.md TEST RULES).
// The two tests above already cover limit-1 (GREEN) and limit exactly (RED,
// ">= NEW_FILE_CAP" boundary); this is the third point, proving the cap does
// not silently stop firing past its own threshold.
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-partition-sizecap-over-'));
try {
const workflowsDir = path.join(tmpRoot, 'gsd-core', 'workflows');
fs.mkdirSync(path.join(workflowsDir, 'big', 'detail'), { recursive: true });
fs.writeFileSync(path.join(workflowsDir, 'big.md'), 'Spine.\n\ngsd-core/workflows/big/detail/part.md\n');
fs.writeFileSync(path.join(workflowsDir, 'big', 'detail', 'part.md'), 'x'.repeat(NEW_FILE_CAP + 1));
const splits = discoverRegisteredSplits(workflowsDir).filter((s) => s.spineExists);
const violations = checkDetailFileSizeCap(splits);
assert.ok(violations.length > 0, 'expected at least one size-cap violation');
assert.ok(
violations.some((v) => v.detailPath.endsWith(path.join('big', 'detail', 'part.md')) && v.size === NEW_FILE_CAP + 1),
`expected the oversized file to be named with its actual size: ${JSON.stringify(violations)}`,
);
} finally {
cleanup(tmpRoot);
}
});
});
describe('failing-first fixture: check 4 (protected content)', () => {
const oldSpineWithProtection =
'Intro.\n\n<!-- gsd:protected -->\n> Never do the dangerous thing.\n\nMiddle prose.\n';
test('RED — a protected line is deleted from the spine entirely', () => {
const newSpineContent = 'Intro.\n\nMiddle prose.\n';
const violations = checkProtectedContentForPair({
splitName: 'broken-split',
oldSpineContent: oldSpineWithProtection,
newSpineContent,
detailPathToContent: new Map(),
});
assert.ok(violations.length > 0, 'expected at least one protected-content violation');
const v = violations.find((x) => x.line === '> Never do the dangerous thing.');
assert.ok(v, `expected the deleted protected line to be named: ${JSON.stringify(violations)}`);
assert.strictEqual(v.kind, 'protected_deleted');
});
test('RED (relocated) — a protected line moved into a detail part is reported distinctly, not merely "deleted"', () => {
const newSpineContent = 'Intro.\n\nMiddle prose.\n';
const detailPathToContent = new Map([
['gsd-core/workflows/broken/detail/part.md', 'Detail intro.\n\n> Never do the dangerous thing.\n'],
]);
const violations = checkProtectedContentForPair({
splitName: 'broken-split',
oldSpineContent: oldSpineWithProtection,
newSpineContent,
detailPathToContent,
});
const v = violations.find((x) => x.line === '> Never do the dangerous thing.');
assert.ok(v, `expected the relocated protected line to be named: ${JSON.stringify(violations)}`);
assert.strictEqual(v.kind, 'protected_relocated');
assert.strictEqual(v.relocatedTo, 'gsd-core/workflows/broken/detail/part.md');
});
test('GREEN — the protected line is still physically present in the spine', () => {
const newSpineContent = 'Intro.\n\n<!-- gsd:protected -->\n> Never do the dangerous thing.\n\nMiddle prose.\n';
const violations = checkProtectedContentForPair({
splitName: 'fixed-split',
oldSpineContent: oldSpineWithProtection,
newSpineContent,
detailPathToContent: new Map(),
});
assert.deepStrictEqual(violations, []);
});
});
describe('failing-first fixture: check 5 (boundary moves declared)', () => {
/** A throwaway git repo, mirroring the makeTempRepo idiom in
* tests/emitted-ack-trailer.test.cjs (this module's `readBoundaryMoveTrailers` sibling
* under tests/helpers/compact-content-split.cjs is a direct port of that same
* mechanism, so the fixture idiom mirrors it byte-for-byte). */
function makeTempRepo(prefix) {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
gitOrThrow(['init', '-q', '-b', 'main'], { cwd: dir, timeoutMs: GIT_FIXTURE_TIMEOUT_MS });
gitOrThrow(['config', 'user.email', 'partition-guard-fixture@example.com'], { cwd: dir, timeoutMs: GIT_FIXTURE_TIMEOUT_MS });
gitOrThrow(['config', 'user.name', 'Partition Guard Fixture'], { cwd: dir, timeoutMs: GIT_FIXTURE_TIMEOUT_MS });
gitOrThrow(['config', 'commit.gpgsign', 'false'], { cwd: dir, timeoutMs: GIT_FIXTURE_TIMEOUT_MS });
return dir;
}
/** Commit from a message written to a file (never `-m`), so a trailer block lands where
* git's own trailer parser expects it — same reasoning as emitted-ack-trailer.test.cjs. */
function commitMessage(dir, message) {
const msgFile = path.join(os.tmpdir(), `gsd-partition-guard-msg-${crypto.randomBytes(6).toString('hex')}.txt`);
fs.writeFileSync(msgFile, message);
try {
gitOrThrow(['add', '-A'], { cwd: dir, timeoutMs: GIT_FIXTURE_TIMEOUT_MS });
gitOrThrow(['commit', '-q', '-F', msgFile], { cwd: dir, timeoutMs: GIT_FIXTURE_TIMEOUT_MS });
} finally {
cleanup(msgFile);
}
}
function headSha(dir) {
return gitOrThrow(['rev-parse', 'HEAD'], { cwd: dir, timeoutMs: GIT_FIXTURE_TIMEOUT_MS }).trim();
}
const SPINE_KEY = 'gsd-core/workflows/spine.md'; // the trailer key our fixture declares against
test('RED — a line moved from spine to detail with no Boundary-Move-Declared trailer', (t) => {
const dir = makeTempRepo('gsd-partition-boundary-red-');
t.after(() => cleanup(dir));
fs.writeFileSync(path.join(dir, 'spine.md'), 'Line A.\n\nOrdinary line that will move.\n\nLine C.\n');
fs.writeFileSync(path.join(dir, 'detail.md'), 'Line D.\n');
commitMessage(dir, 'init\n\nbaseline, no move yet\n');
const baseSha = headSha(dir);
const oldSpineContent = fs.readFileSync(path.join(dir, 'spine.md'), 'utf8');
const oldDetailContent = fs.readFileSync(path.join(dir, 'detail.md'), 'utf8');
fs.writeFileSync(path.join(dir, 'spine.md'), 'Line A.\n\nLine C.\n');
fs.writeFileSync(path.join(dir, 'detail.md'), 'Line D.\n\nOrdinary line that will move.\n');
commitMessage(dir, 'move a line without declaring it\n\nno trailer here\n');
const newSpineContent = fs.readFileSync(path.join(dir, 'spine.md'), 'utf8');
const newDetailContent = fs.readFileSync(path.join(dir, 'detail.md'), 'utf8');
const violations = checkBoundaryMovesForSplit({
splitName: 'spine',
spineRelPath: SPINE_KEY,
oldSpineContent,
newSpineContent,
oldDetailContentByPath: new Map([['detail.md', oldDetailContent]]),
newDetailContentByPath: new Map([['detail.md', newDetailContent]]),
excludeLines: new Set(),
baseRef: baseSha,
cwd: dir,
});
assert.ok(violations.length > 0, 'expected an undeclared boundary-move violation');
const v = violations.find((x) => x.line === 'Ordinary line that will move.');
assert.ok(v, `expected the moved line to be named: ${JSON.stringify(violations)}`);
assert.strictEqual(v.kind, 'undeclared_boundary_move');
assert.strictEqual(v.spinePath, SPINE_KEY);
});
test('GREEN — the same move, with a Boundary-Move-Declared trailer naming the spine', (t) => {
const dir = makeTempRepo('gsd-partition-boundary-green-');
t.after(() => cleanup(dir));
fs.writeFileSync(path.join(dir, 'spine.md'), 'Line A.\n\nOrdinary line that will move.\n\nLine C.\n');
fs.writeFileSync(path.join(dir, 'detail.md'), 'Line D.\n');
commitMessage(dir, 'init\n\nbaseline, no move yet\n');
const baseSha = headSha(dir);
const oldSpineContent = fs.readFileSync(path.join(dir, 'spine.md'), 'utf8');
const oldDetailContent = fs.readFileSync(path.join(dir, 'detail.md'), 'utf8');
fs.writeFileSync(path.join(dir, 'spine.md'), 'Line A.\n\nLine C.\n');
fs.writeFileSync(path.join(dir, 'detail.md'), 'Line D.\n\nOrdinary line that will move.\n');
commitMessage(
dir,
`move a line and declare it\n\nBoundary-Move-Declared: ${SPINE_KEY} — moved to detail deliberately, #4403\n`,
);
const newSpineContent = fs.readFileSync(path.join(dir, 'spine.md'), 'utf8');
const newDetailContent = fs.readFileSync(path.join(dir, 'detail.md'), 'utf8');
const violations = checkBoundaryMovesForSplit({
splitName: 'spine',
spineRelPath: SPINE_KEY,
oldSpineContent,
newSpineContent,
oldDetailContentByPath: new Map([['detail.md', oldDetailContent]]),
newDetailContentByPath: new Map([['detail.md', newDetailContent]]),
excludeLines: new Set(),
baseRef: baseSha,
cwd: dir,
});
assert.deepStrictEqual(violations, []);
});
test('uncomputable range still throws through checkBoundaryMovesForSplit (never silently passes)', (t) => {
const origin = makeTempRepo('gsd-partition-boundary-origin-');
t.after(() => cleanup(origin));
fs.writeFileSync(path.join(origin, 'spine.md'), 'Line A.\n\nOrdinary line that will move.\n');
commitMessage(origin, 'origin A\n\nfirst commit\n');
const shaA = headSha(origin);
fs.writeFileSync(path.join(origin, 'spine.md'), 'Line A.\n\nOrdinary line that will move.\n\nLine B.\n');
commitMessage(origin, 'origin B\n\nsecond commit\n');
const clone = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-partition-boundary-clone-'));
t.after(() => cleanup(clone));
gitOrThrow(['clone', '-q', '--depth', '1', `file://${origin}`, clone], { cwd: origin, timeoutMs: GIT_FIXTURE_TIMEOUT_MS });
assert.throws(
() => checkBoundaryMovesForSplit({
splitName: 'spine',
spineRelPath: SPINE_KEY,
oldSpineContent: 'Line A.\n\nOrdinary line that will move.\n',
newSpineContent: 'Line A.\n',
oldDetailContentByPath: new Map(),
newDetailContentByPath: new Map([['detail.md', 'Ordinary line that will move.\n']]),
excludeLines: new Set(),
baseRef: shaA,
cwd: clone,
}),
/./,
'a genuinely uncomputable merge-base must throw, never return an empty result',
);
});
});
// ─── property tests (CLAUDE.md TEST RULES: parsers/bijective contracts need fast-check) ─
describe('property: compact-content-split.cjs parsers', () => {
// Same alphabets as the ADR-3942 sibling this trailer mechanism ports from
// (tests/emitted-ack-trailer.test.cjs's KEY_ALPHABET/REASON_ALPHABET) — neither
// alphabet can produce ACK_TRAILER_DELIM itself, so a generated key/reason pair
// never accidentally embeds a second delimiter and corrupts its own round trip.
const KEY_ALPHABET = 'abcdefghijklmnopqrstuvwxyz0123456789_./-'.split('');
const REASON_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ,.#-'.split('');
const keyArb = fc.array(fc.constantFrom(...KEY_ALPHABET), { minLength: 1, maxLength: 40 })
.map((chars) => chars.join(''));
const reasonArb = fc.array(fc.constantFrom(...REASON_ALPHABET), { minLength: 1, maxLength: 60 })
.map((chars) => chars.join('').trim())
.filter((s) => s.length > 0);
test('prop: Boundary-Move-Declared trailer render/parse is bijective', () => {
fc.assert(
fc.property(keyArb, reasonArb, (key, reason) => {
const rendered = `${key}${ACK_TRAILER_DELIM}${reason}`;
const { declarations, errors } = parseBoundaryMoveTrailerValues([rendered]);
assert.deepStrictEqual(errors, []);
assert.strictEqual(declarations.get(key)?.reason, reason);
}),
{ seed: 4403, numRuns: 300 },
);
});
test('prop: two identical (key, reason) declarations always dedupe to exactly one entry, never an error', () => {
fc.assert(
fc.property(keyArb, reasonArb, (key, reason) => {
const rendered = `${key}${ACK_TRAILER_DELIM}${reason}`;
const { declarations, errors } = parseBoundaryMoveTrailerValues([rendered, rendered]);
assert.deepStrictEqual(errors, []);
assert.strictEqual(declarations.size, 1);
assert.strictEqual(declarations.get(key)?.reason, reason);
}),
{ seed: 4403, numRuns: 200 },
);
});
// Body lines deliberately exclude anything sentinel-shaped or blank, so the
// generated fixture can never accidentally produce a SECOND sentinel or an
// early terminator inside what is meant to be one continuous protected span.
const bodyLineArb = fc.array(fc.constantFrom(...'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 .,'.split('')), { minLength: 1, maxLength: 30 })
.map((chars) => chars.join('').trim())
.filter((s) => s.length > 0 && !s.includes('gsd:protected'));
test('prop: any well-formed <!-- gsd:protected:start/end --> span round-trips through extractProtectedBlocks with zero malformed entries', () => {
fc.assert(
fc.property(fc.array(bodyLineArb, { minLength: 1, maxLength: 8 }), (lines) => {
const content = ['Intro prose.', '', '<!-- gsd:protected:start -->', ...lines, '<!-- gsd:protected:end -->', '', 'Closing prose.'].join('\n');
const { blocks, malformed } = extractProtectedBlocks(content);
assert.deepStrictEqual(malformed, []);
const paired = blocks.filter((b) => b.kind === 'paired');
assert.strictEqual(paired.length, 1);
assert.deepStrictEqual(paired[0].lines, lines);
}),
{ seed: 4403, numRuns: 200 },
);
});
});