Files
msd-core/tests
Tom Boucher 70e50eb1b2 chore(#3235): hoist the preamble-strip conditional out of the replace operand (#3236)
* chore(#3235): hoist the preamble-strip conditional out of the replace operand

CodeQL js/identity-replacement (alert 53, medium, CWE-116) fired on
src/roadmap-parser.cts:637. The #2947 fix (2bc53baa0) made the preamble
phase-detail strip conditional by swapping the REGEX OPERAND rather than the
operation, using `/$/` as a "match nothing" sentinel:

  .replace(currentSectionHasPhaseDetails ? /^#{2,4}\s*Phase\s+.../gim : /$/, '')

`str.replace(/$/, '')` substitutes the zero-width end-of-input match with the
empty string, so the branch is inert. Behavior was correct; the hazard is that
both branches shared ONE replacement argument, so a later change of `''` to a
non-empty string would silently give the no-op branch a real effect -- on a seam
whose blast radius is CRITICAL (200+ affected symbols, 45 files, 22 processes).

Hoist the conditional around the .replace() call instead. The do-not-strip
branch now performs no replacement at all. The `Phase Details` heading strip
stays UNCONDITIONAL in both branches, which is what #730 depends on.

Equivalence is not asserted, it is measured: a deterministic-seed differential
probe compared the old and new formulations over 320 generated inputs x both
branch values -- 640 comparisons, 0 mismatches. Corpus covered empty string,
CRLF, heading depths 1-5, the #1729 pre-colon tag form, decimal phase IDs,
`## Phase Details`, fenced blocks and horizontal rules.

Six characterization tests pin both branches, including the one case no existing
#2947 fixture covers: that the unconditional `Phase Details` strip still runs on
the do-not-strip branch. Boundary rows cover the strip regex's #{2,4} bounds at
limit-1 (h1), limit (h2/h4) and limit+1 (h5).

Refs #3235

* test(#3235): add parser property test; kill two weak regression tests

Review findings from the orthogonal passes, all fixed inline.

Standards axis (hard violation, CLAUDE.md -> TEST RULES & CONVENTIONS):
"Parsers, budget limits, and bijective contracts must include at least one
fast-check (fc) property test." roadmap-parser is a parser module and the six
new tests were all example-based. Adds one property test over generated
preambles, asserting four invariants across BOTH branches:
  1. no `Phase Details` heading ever survives (the unconditional strip)
  2. headings outside the strip regex's #{2,4} bound always survive
  3. hasOwnDetails=true  -> preamble phase headings are gone
  4. hasOwnDetails=false -> preamble phase headings are all retained
Generator is document-shaped -- a fixed alphabet of literal line shapes, NOT
derived from the parser's own regexes (CONTRIBUTING.md #2371 fixture
provenance: a writer-seeded generator can only confirm what the author already
believed). Seed pinned to 20260809, numRuns bounded at 300, verbose replay on
failure, per the determinism rule.

Isolated adversarial review ran mutation testing against the compiled lib and
found two weak tests:

- CRLF test killed no mutant the LF-only sibling did not already kill. Its
  fixture now carries a `## Phase Details` heading, which exercises the
  `[^\n]*` / `\n?` tail of the Phase Details regex under CRLF -- a region no
  LF-only fixture can reach -- and asserts no orphaned CR is left behind.
- "no preamble does not throw" caught none of five targeted mutants.
  `.replace()` cannot throw on any string, so no mutation confined to this
  change could fail it. Deleted rather than propped up: the empty-preamble case
  is now genuinely covered by the property test's minLength:0 generator, which
  produces empty preambles on both branches. Strictly subsumed.

Refs #3235

---------

Co-authored-by: sim <sim@local>
2026-08-09 10:32:46 -04:00
..