Skip to content

Flaky: lean-spec-bridge INV-RT-001 pre-tracked round-trip fails on random seeds (pre-tracked all-w:ins paragraph) #339

@stevenobiajulu

Description

@stevenobiajulu

Summary

packages/docx-core/src/integration/lean-spec-bridge.test.ts
INV-RT-001: paired round-trip text equality on pre-tracked paragraph-only inplace comparison output
is a fast-check property test with no fixed seed ({ numRuns: NUM_RUNS }, line ~1268). It intermittently
fails in CI when the generator produces a pre-tracked paragraph whose content is entirely <w:ins> (an
insertion that already existed in the original input). It surfaced on the workspace-test (20)/(22) jobs
of PR #337, but it is pre-existing and unrelated to that PR (see "Not caused by #337" below).

Deterministic reproduction

Pin the seed on the failing property (temporary edit at lean-spec-bridge.test.ts:1268):

{ numRuns: NUM_RUNS, seed: -695140806, path: "62:2:1:1:2:2:2:2:2:2:2:2:7", endOnFailure: true },
npm run test:run -w @usejunior/docx-core -- src/integration/lean-spec-bridge.test.ts
# FAIL  INV-RT-001: paired round-trip text equality on pre-tracked paragraph-only inplace comparison output
# Got error: triage=inplace-fallback ... fallbackReason=round_trip_safety_check_failed failedChecks=["rejectText"]
# context: original {family:"w:del", paragraphs:["!","!","I"]} / revised {family:"w:ins", paragraphs:["6."], insertedText:"I"}

Root cause

The inplace comparison output (candidateXml) for this scenario is:

<w:p>…PPR-DEL… <w:del><w:r><w:delText>!</w:delText></w:r></w:del></w:p>
<w:p>…PPR-DEL… <w:del><w:r><w:delText>!</w:delText></w:r></w:del></w:p>
<w:p>
  <w:ins w:author="Comparison"><w:r><w:t>6.</w:t></w:r></w:ins>
  <w:ins w:author="Lean Bridge"><w:r><w:t>I</w:t></w:r></w:ins>   <!-- pre-tracked insertion from the ORIGINAL -->
</w:p>

The reject round-trip safety check (pipeline.ts:585-591) computes
compareTexts(originalTextForRoundTrip, extractTextWithParagraphs(rejectAllChanges(candidateXml))).

  • originalTextForRoundTrip = "!\n!\nI" — the original's text including its own pre-tracked <w:ins>I</w:ins>.
  • rejectAllChanges(candidateXml) removes all insertions — including the original's pre-tracked "I" — so the third paragraph's text is gone.

→ The round-trip can never reproduce "…\nI" because reject deletes the very "I" the oracle expects to
survive. The engine therefore (correctly) fails its own rejectText safety check and falls back to rebuild;
the test asserts inplace, so it fails. The flake appears only when the random generator happens to place an
all-w:ins (pre-tracked-insertion-only) paragraph such that reject's removal diverges from
originalTextForRoundTrip.

Not caused by PR #337 (mark-based reject)

Reproduced identically on main (4e00489) with the same pinned seed. PR #337 changes reject of an
untracked-mark all-w:ins paragraph from drop to keep-empty, but that is behavior-neutral here:
old reject → "!\n!", new reject → "!\n!\n", and normalizeText collapses both to ["!","!"]. The
failure is about the removed pre-tracked "I", not the drop-vs-keep-empty difference.

Remediation options (pick per design intent)

  1. Make the property test deterministic in CI — pin a seed and/or add examples covering the
    pre-tracked all-w:ins family, so this no longer randomly red-lights unrelated PRs. (Removes the flake;
    does not address the oracle gap below.)
  2. Fix the round-trip oracle for pre-tracked inputsoriginalTextForRoundTrip should be the
    reject projection of the original's own tracked changes (apples-to-apples with rejectAllChanges(candidate),
    which also rejects), rather than the original's accepted/as-authored text. The current oracle assumes the
    original carries no pre-existing insertions.
  3. Decide engine semantics for reject of a paragraph stacking insertions from two authors
    (Comparison + pre-tracked "Lean Bridge") and align the safety check accordingly.

Option 1 stops the CI flake immediately; options 2/3 address the underlying pre-tracked round-trip
modeling gap.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcitest

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions