@ansonlai/docx-redline-js 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,11 +10,11 @@
10
10
  ## 1. Executive Summary & Findings
11
11
 
12
12
  ### The Problem
13
- During contract review and multi-turn legal negotiations, a reviewer (Author B, e.g., "Lai, Anson") often needs to edit text that was previously inserted by another reviewer (Author A, e.g., "Lai, Barry") whose revision has not yet been accepted.
13
+ During contract review and multi-turn legal negotiations, Reviewer B often needs to edit text that was previously inserted by Reviewer A whose revision has not yet been accepted.
14
14
 
15
15
  In previous discussions and documentation, it was assumed that deleting or inserting text inside another author's pending insertion was either impossible in WordprocessingML (OOXML) or logically paradoxical. However, empirical inspection of Microsoft Word Desktop proves otherwise:
16
16
 
17
- 1. **Word Desktop supports deletions inside pending insertions**: When Author B deletes text inside Author A's pending `<w:ins>`, Word Desktop displays Author B's deletion visibly (strikethrough formatting, deletion tooltip attributed to Author B with timestamp: `Lai, Anson deleted: <text>`), while the surrounding insertion text remains attributed to Author A.
17
+ 1. **Word Desktop supports deletions inside pending insertions**: When Reviewer B deletes text inside Reviewer A's pending `<w:ins>`, Word Desktop displays Reviewer B's deletion visibly (strikethrough formatting and a deletion tooltip attributed to Reviewer B), while the surrounding insertion text remains attributed to Reviewer A.
18
18
  2. **Word Desktop supports insertions inside pending insertions**: When Author B types text in the middle of Author A's `<w:ins>`, Word Desktop displays Author B's new text attributed to Author B, while preserving Author A's attribution on the preceding and succeeding words.
19
19
 
20
20
  ### The Underlying OOXML Mechanics
@@ -24,15 +24,15 @@ Empirical inspection of Microsoft Word Desktop 365 (via automated COM fixture ge
24
24
  ECMA-376 Part 1 `CT_RunTrackChange` does NOT allow `<w:ins>` inside `<w:ins>`. Word Desktop splits the carrier `<w:ins>` into sibling fragments at the paragraph (`<w:p>`) level, splicing Author B's new `<w:ins>` between them:
25
25
  ```xml
26
26
  <!-- Sibling 1: Author A's insertion (leading fragment) -->
27
- <w:ins w:id="0" w:author="Barry Lai" w:date="2026-09-08T09:29:00Z">
27
+ <w:ins w:id="0" w:author="Reviewer A" w:date="2026-09-08T09:29:00Z">
28
28
  <w:r><w:t xml:space="preserve">amended by this </w:t></w:r>
29
29
  </w:ins>
30
30
  <!-- Sibling 2: Author B's insertion spliced in between -->
31
- <w:ins w:id="1" w:author="Anson Lai" w:date="2026-09-08T09:29:00Z">
31
+ <w:ins w:id="1" w:author="Reviewer B" w:date="2026-09-08T09:29:00Z">
32
32
  <w:r><w:t xml:space="preserve">MASTER </w:t></w:r>
33
33
  </w:ins>
34
34
  <!-- Sibling 3: Author A's insertion (trailing fragment) -->
35
- <w:ins w:id="2" w:author="Barry Lai" w:date="2026-09-08T09:29:00Z">
35
+ <w:ins w:id="2" w:author="Reviewer A" w:date="2026-09-08T09:29:00Z">
36
36
  <w:r><w:t>Agreement.</w:t></w:r>
37
37
  </w:ins>
38
38
  ```
@@ -40,9 +40,9 @@ Empirical inspection of Microsoft Word Desktop 365 (via automated COM fixture ge
40
40
  2. **Cross-Author Deletions (`delete-interior`) — Direct `<w:del>` Nesting inside `<w:ins>`**:
41
41
  Under ECMA-376 Part 1 Section 17.13.5.21 (`CT_RunTrackChange`), `<w:del>` is an explicitly permitted child element of `<w:ins>`. Microsoft Word Desktop does **not** split `<w:ins>` for deletions; instead, it nests `<w:del>` directly inside `<w:ins>`:
42
42
  ```xml
43
- <w:ins w:id="0" w:author="Barry Lai" w:date="2026-09-08T09:29:00Z">
43
+ <w:ins w:id="0" w:author="Reviewer A" w:date="2026-09-08T09:29:00Z">
44
44
  <w:r><w:t xml:space="preserve">The Services will process the Input to </w:t></w:r>
45
- <w:del w:id="1" w:author="Anson Lai" w:date="2026-09-08T09:29:00Z">
45
+ <w:del w:id="1" w:author="Reviewer B" w:date="2026-09-08T09:29:00Z">
46
46
  <w:r w:rsidDel="00F70C09"><w:delText xml:space="preserve">generate </w:delText></w:r>
47
47
  </w:del>
48
48
  <w:r><w:t>outputs for Customer.</w:t></w:r>
@@ -414,8 +414,8 @@ type ExistingRevisionsPolicy =
414
414
  ### Final Automated Test Completion [COMPLETED 2026-09-08]
415
415
  * **Coverage Added**:
416
416
  - Completed the executable synthetic matrix for SYN-01 through SYN-12d. The final additions generate SYN-02 in the engine, add a second cross-author deletion to produce SYN-11, and exercise Accept All, Accept Author A, Reject Author A, Reject Author B, and selective rejection of the third author.
417
- - Added strict package-facade differential tests for all five core Word Desktop scenarios. Each test reconstructs the pre-Anson package, applies `slice-cross-author` through `openDocx(...).applyOperations`, requires atomic package validation, and compares engine Accept-All/Reject-All text with the checked-in Word Desktop accepted/rejected DOCX files.
418
- - Added a two-round package test that generates the three-author stacked-deletion fixture through Anson and Chris operations, asserts all three reviewers survive inspection, and compares both lifecycle endpoints with Word Desktop.
417
+ - Added strict package-facade differential tests for all five core Word Desktop scenarios. Each test reconstructs the pre-Reviewer-B package, applies `slice-cross-author` through `openDocx(...).applyOperations`, requires atomic package validation, and compares engine Accept-All/Reject-All text with the checked-in Word Desktop accepted/rejected DOCX files.
418
+ - Added a two-round package test that generates the three-author stacked-deletion fixture through Reviewer B and Reviewer C operations, asserts all three reviewers survive inspection, and compares both lifecycle endpoints with Word Desktop.
419
419
  * **Files Touched**:
420
420
  - `tests/cross_author_slicing_synthetic_tests.mjs` (MODIFIED)
421
421
  - `tests/cross_author_slicing_real_tests.mjs` (NEW)
@@ -436,7 +436,7 @@ type ExistingRevisionsPolicy =
436
436
  - `npm run check:types` — PASS; all 123 runtime exports have declarations.
437
437
  - `git diff --check` — PASS (line-ending conversion notices only; no whitespace errors).
438
438
  * **Scope Note**:
439
- - The repository does not contain the private `agreement.docx` referenced by REAL-01/REAL-02 or the exact `c5bb43ede5...` corpus package referenced by REAL-03. Those named cases remain external acceptance scenarios rather than silently skipped automated tests.
439
+ - The repository does not contain the private source packages referenced by REAL-01 through REAL-03. Those cases remain external acceptance scenarios rather than silently skipped automated tests.
440
440
  - REAL-04/REAL-05 require Microsoft Word Desktop COM and visual review. The checked-in fixtures were produced by Word COM, while the normal automated suite deliberately remains deterministic and non-interactive.
441
441
 
442
442
  ### Bug Follow-Up: Hyperlink Boundary Round-Trip Mismatch [FIXED 2026-09-08]
@@ -467,6 +467,7 @@ type ExistingRevisionsPolicy =
467
467
  - Exact Accept-All equality with the submitted modified string.
468
468
  - Explicit fail-closed test proving mismatch status, error code, mismatch offset, and byte-exact original OOXML rollback.
469
469
  * **Verification**:
470
+ - `node tests/cross_author_slicing_replacement_anchor_tests.mjs` — PASS, 12 scenarios.
470
471
  - `node tests/cross_author_slicing_hyperlink_roundtrip_tests.mjs` — PASS.
471
472
  - `npm test` — PASS, 93 test files passed and 0 failed.
472
473
  - `npm run lint` — PASS.
@@ -504,11 +505,831 @@ type ExistingRevisionsPolicy =
504
505
  - Stress helpers `escapeXml`, `run`, `insertion`, `paragraph`, `parsed`, `acceptedText`, `authorOf`, `assertValid`, and `assertInsertionRoundTrip` (NEW).
505
506
  * **Verification**:
506
507
  - `node tests/cross_author_slicing_insertion_stress_tests.mjs` — PASS, 76 scenarios.
507
- - `npm test` — PASS, 94 test files passed and 0 failed.
508
+ - `npm test` — PASS, 95 test files passed and 0 failed.
508
509
  - `npm run lint` — PASS.
509
510
  - `npm run check:types` — PASS; all 123 runtime exports have declarations.
510
511
  - `git diff --check` — PASS (line-ending conversion notices only; no whitespace errors).
511
512
 
513
+ ### Hyperlink-Adjacent Replacement Follow-Up [COMPLETED 2026-09-08]
514
+ * **Bug Report**: Replacing the space immediately after a policy hyperlink with a comma and effective-date qualifier failed with `PATCH_ROUNDTRIP_MISMATCH`. The generated intermediate OOXML moved the qualifier and URL relative to the following definition text.
515
+ * **Root Cause**: `processDelete` split and removed the run containing the replaced boundary space, but `processInsert` subsequently resolved the paired insertion through the pre-mutation span index. That span still referenced the detached source run, so insertion placement fell back to the wrong paragraph location.
516
+ * **Fix**: `processDelete` now records a stable parent/reference-node anchor at a non-carrier deletion boundary. `processInsert` consumes that anchor for the immediately paired insertion when no explicit insertion affinity was requested. Foreign `w:ins` carriers continue to use their existing carrier-splitting anchor and explicit affinity remains authoritative.
517
+ * **Additional Defect Found by the Matrix**: Two pure insertions in the same source run could detach the shared pre-mutation span after the first insertion and relocate the second insertion to the paragraph end. Multi-insertion-only slicing now applies insertions from right to left and rebuilds the live span index between mutations.
518
+ * **Files Touched**:
519
+ - `engine/surgical-diff-application.js`
520
+ - `engine/surgical-mode.js`
521
+ - `tests/cross_author_slicing_hyperlink_roundtrip_tests.mjs`
522
+ - `tests/cross_author_slicing_replacement_anchor_tests.mjs` (NEW)
523
+ - `CHANGELOG.md`
524
+ - `docs/plans/2026-09-08-cross-author-revision-slicing.md`
525
+ * **Functions Touched**:
526
+ - `processDelete` (MODIFIED): records the live DOM insertion boundary while splitting a deleted run.
527
+ - `processInsert` (MODIFIED): consumes the stable replacement anchor before consulting stale pre-mutation spans.
528
+ - `applySurgicalMode` (MODIFIED): uses live right-to-left application for multiple insertion-only diffs.
529
+ - `collectInsertionOperations` (NEW): records original/new offsets for insertion-only mutations.
530
+ - Hyperlink round-trip test helpers and assertions (MODIFIED): cover low-level apply, Accept All, hyperlink relationship preservation, and the atomic strict-target document runner.
531
+ - Replacement-anchor matrix helpers `escapeXml`, `run`, `hyperlink`, `insertion`, `paragraph`, `parse`, `acceptedText`, and `hyperlinkIds` (NEW).
532
+ * **Additional Future-Regression Coverage**:
533
+ - 12 deterministic replacements at run starts, interiors, and ends; before and after hyperlinks; across an entire spacer run; beside bold/underlined runs, bookmarks, and comment markers; across multiple replacements; and beside/inside a foreign insertion carrier.
534
+ - Every case asserts exact current view, Accept All, Reject Reviewer, structural validation, unique revision IDs, and hyperlink relationship preservation.
535
+ - Four representative hyperlink and multi-replacement cases also execute through the atomic strict-target document runner used by the CLI.
536
+ * **Verification**:
537
+ - `node tests/cross_author_slicing_replacement_anchor_tests.mjs` — PASS, 12 scenarios.
538
+ - `node tests/cross_author_slicing_hyperlink_roundtrip_tests.mjs` — PASS.
539
+ - `node tests/cross_author_slicing_insertion_stress_tests.mjs` — PASS, 76 scenarios.
540
+ - `node tests/insertion_affinity_tests.mjs` — PASS.
541
+ - `npm test` — PASS, 95 test files passed and 0 failed.
542
+ - `npm run lint` — PASS.
543
+ - `npm run check:types` — PASS; all 123 runtime exports have declarations.
544
+ - `git diff --check` — PASS (line-ending conversion notices only; no whitespace errors).
545
+
546
+ ### WP08a — Fail-Closed Gate for Foreign Paragraph-Mark Deletions [COMPLETED 2026-09-08]
547
+
548
+ WP08a is separable from, and a prerequisite of, WP08b. It ships on its own as a patch release: it adds no new capability, only refuses an operation that currently returns `status: 'ok'` while producing a lifecycle-unsafe document. Every prior follow-up in this document shipped the fail-closed guard before the feature; WP08 follows the same order.
549
+
550
+ #### Scope
551
+
552
+ 1. At the mutation gate, refuse any operation that would add visible runs or `w:ins` content to a paragraph in the **resurrection state** defined in WP08b's trigger taxonomy (foreign paragraph-mark deletion + every pre-existing content node already deleted + new non-empty insertion). Return `FOREIGN_PARAGRAPH_MARK_DELETION` with the owning author, and the original document unchanged under atomic mode.
553
+ 2. Add the same predicate to `core/redline-validation.js` as a **warning**, not an error. Validation runs over documents this engine did not author; see the Validation Predicate section below for why the broader rule is unsafe.
554
+ 3. Regression fixture reproducing the reported restoration shape (foreign `w:pPr/w:rPr/w:del` plus an attempted same-paragraph `w:ins`), asserting the refusal, the error code, and byte-exact rollback.
555
+ 4. Lifecycle assertions in the fixture proving *why* the shape is refused: Accept All loses Reviewer B's text, and Reject Reviewer A yields duplicate visible text.
556
+
557
+ Package validation alone is not a sufficient oracle for this case — the unsafe shape is structurally valid. Accept/Reject lifecycle checks are mandatory.
558
+
559
+ #### Implementation Record
560
+
561
+ * **Result**: Added a shared, narrowly scoped resurrection-state predicate. Both the low-level paragraph engine and the document mutation runner now refuse a non-empty edit when a different author owns the paragraph-mark deletion and every pre-existing content child is deleted. The refusal returns `FOREIGN_PARAGRAPH_MARK_DELETION`, includes `ownerAuthor`, and leaves the original input unchanged. Same-author deleted paragraphs, foreign deleted marks with surviving content, and content-only deletions without a paragraph-mark deletion remain on their existing paths.
562
+ * **Validation**: `validateRedlineOoxml` uses the same structural model to emit a warning for already-authored unsafe shapes. The warning does not make otherwise valid OOXML invalid.
563
+ * **Files Touched**:
564
+ - `core/paragraph-revision-safety.js` (NEW)
565
+ - `core/redline-validation.js`
566
+ - `engine/oxml-engine.js`
567
+ - `services/document-operation-mutations.js`
568
+ - `index.d.ts`
569
+ - `tests/foreign_paragraph_mark_deletion_gate_tests.mjs` (NEW)
570
+ - `CHANGELOG.md`
571
+ - `docs/plans/2026-09-08-cross-author-revision-slicing.md`
572
+ * **Functions and Types Touched**:
573
+ - `inspectForeignDeletedParagraphTarget` (NEW): identifies the pre-mutation WP08 resurrection state and excludes same-author ownership.
574
+ - `findForeignDeletedParagraphResurrections` (NEW): identifies already-authored unsafe foreign-insertion shapes for warning-only validation.
575
+ - `paragraphMarkDeletion`, `hasVisibleInsertionContent`, `isAnchorOnlyRun`, and DOM/name/author helpers (NEW): implement the shared structural inspection without mutating the source DOM, while excluding non-visible comment/bookmark anchors from the content-state decision.
576
+ - `applyRedlineToOxml` (MODIFIED): fails closed before existing-revision normalization or diff application for paragraph-level calls.
577
+ - `applyToParagraphByExactText` (MODIFIED): fails closed immediately after strict target resolution and before preprocessing/mutation for document-runner calls.
578
+ - `validateRedlineOoxml` (MODIFIED): reports `FOREIGN_PARAGRAPH_MARK_DELETION` as a warning for structurally valid but lifecycle-unsafe authored output.
579
+ - `RedlineError` (MODIFIED): documents the new error code and optional `ownerAuthor` metadata.
580
+ - WP08a fixture helpers and assertions (NEW): cover the low-level tracked and direct-edit paths, strict atomic runner rollback, validator severity, Accept/Reject lifecycle evidence, and every non-triggering taxonomy row.
581
+ * **Verification**:
582
+ - `node tests/foreign_paragraph_mark_deletion_gate_tests.mjs` — PASS.
583
+ - Focused validation, revision-policy, replacement-anchor, and paragraph-boundary suites — PASS.
584
+ - `npm test` — PASS, 96 test files passed and 0 failed.
585
+ - `npm run lint` — PASS.
586
+ - `npm run check:types` — PASS; all 123 runtime exports have declarations.
587
+ - `npm run build` — PASS.
588
+ - `git diff --check` — PASS (line-ending conversion notices only; no whitespace errors).
589
+
590
+ ---
591
+
592
+ ### WP08b — Paragraph-Level Cross-Author Slicing for Deleted Paragraph Restoration [COMPLETED 2026-09-08]
593
+
594
+ #### Motivation
595
+
596
+ Restoring text from another reviewer's pending whole-paragraph deletion is the paragraph-level counterpart of run-level cross-author slicing. The accepted/current view of such a paragraph is empty, while its text exists only in the rejected view. Writing replacement text into that same paragraph can look correct before revisions are resolved, but it is lifecycle-unsafe because the foreign paragraph-mark deletion still owns the paragraph.
597
+
598
+ The observed unsafe shape is conceptually:
599
+
600
+ ```xml
601
+ <w:p>
602
+ <w:pPr><w:rPr><w:del w:author="Reviewer A"/></w:rPr></w:pPr>
603
+ <w:del w:author="Reviewer A">...</w:del>
604
+ <w:ins w:author="Reviewer B">restored text</w:ins>
605
+ </w:p>
606
+ ```
607
+
608
+ This passes structural package validation and looks correct in the current view, but Accept All removes the entire paragraph because Reviewer A's paragraph-mark deletion remains active. Rejecting Reviewer A can also expose both the original deleted text and Reviewer B's inserted copy.
609
+
610
+ Nesting Reviewer B's `<w:ins>` inside Reviewer A's `<w:del>` is not a solution: `w:del/w:ins` nesting is invalid for this use, and accepting the outer deletion would remove the nested text.
611
+
612
+ #### Required Paragraph-Level Slicing Model
613
+
614
+ Preserve the foreign deleted paragraph and materialize the restoring reviewer's counterproposal as a new adjacent tracked paragraph:
615
+
616
+ ```text
617
+ [restored/adjusted paragraph inserted by Reviewer B]
618
+ [paragraph deleted by Reviewer A]
619
+ ```
620
+
621
+ #### Paragraph-Mark Semantics (Normative)
622
+
623
+ This is the part the run-level slicing model has no analogue for, and it governs every lifecycle row below.
624
+
625
+ **Accepting a paragraph-mark deletion does not remove the paragraph — it merges the paragraph into the next paragraph.** `mergeParagraphIntoNextAndRemove` in `services/revision-comment-management.js` moves the deleted paragraph's surviving children into the following `w:p` and removes the emptied paragraph; the **following** paragraph's `pPr` is the one that survives. Any design statement phrased as "Reviewer A's paragraph is removed" is imprecise and must be read as "merged forward".
626
+
627
+ Three consequences are binding on the implementation:
628
+
629
+ 1. **Sibling order is a design decision, not cosmetic.** Placing Reviewer B's paragraph *after* Reviewer A's makes B the merge target when Reviewer A is accepted: A's surviving children land inside B. This is harmless only while A's content is 100% deleted, and stops being harmless the moment A retains content (a partially resolved deletion, or a third author's `w:ins` still pending inside A). Placing B *before* A leaves A's merge target exactly as it was before the restoration existed, so accepting A behaves identically with or without B.
630
+ * **Decision: place Reviewer B's paragraph immediately BEFORE Reviewer A's**, for merge-target neutrality. The current view is unaffected (A is invisible), and All-Markup view order is a rendering preference, not a correctness property. Fixtures must assert the merge target explicitly rather than inferring it from the resulting text.
631
+ 2. **Reviewer B's paragraph MUST carry its own inserted paragraph mark** (`w:pPr/w:rPr/w:ins` attributed to Reviewer B, with an allocator-issued ID). Adding a paragraph adds a paragraph mark. Without it, Reject Reviewer B removes B's content but leaves an empty stub paragraph permanently, silently violating the Reject-B lifecycle row and drifting the document's paragraph count.
632
+ 3. **The existing inserted-paragraph builders do not do this today.** `wrapParagraphContentInInsertion` and `buildFallbackInsertedPlainParagraph` in `services/document-operation-mutations.js` emit no paragraph-mark revision and clone `pPr` verbatim. Cloning `pPr` verbatim from the deleted source paragraph would copy Reviewer A's `w:rPr/w:del` onto Reviewer B's paragraph, reproducing the exact unsafe shape WP08 exists to prevent. Emitting the inserted mark and sanitizing `pPr` is new work in those builders, not reuse of them.
633
+
634
+ > **WP09e supersession:** A later Microsoft Word Desktop oracle places a two-paragraph counterproposal **after** the corresponding fully deleted source block, not before it. WP09e reopens the completed WP08b placement decision and requires Word-native ordering plus native lifecycle comparison. This historical WP08b record describes the v0.5.3 implementation, not the final target behavior.
635
+
636
+ #### Paragraph Property Sanitization (Normative Allowlist)
637
+
638
+ When deriving Reviewer B's paragraph from the deleted source, copy only:
639
+
640
+ * `w:pStyle`, `w:numPr`, `w:ind`, `w:jc`, `w:spacing`, `w:tabs`, `w:keepNext`/`w:keepLines`, `w:outlineLvl`, `w:contextualSpacing`.
641
+
642
+ Strip unconditionally:
643
+
644
+ * `w:rPr/w:del` and `w:rPr/w:ins` (foreign mark revisions — replaced by Reviewer B's own inserted mark),
645
+ * `w:sectPr` (section identity must never be duplicated; see refusals),
646
+ * `w:pPrChange`, `w:rPrChange`, and every other `*Change` element (they describe a revision of the *source* paragraph and are meaningless on the clone).
647
+
648
+ #### Paragraph Identity (Normative)
649
+
650
+ Reviewer B's paragraph MUST receive a **fresh `w14:paraId`**, and MUST drop `w14:textId` and all `w:rsid*` attributes. This is unconditional, not best-effort: `extractParagraphIdFromOoxml` in `core/ooxml-identifiers.js` resolves strict targets by `w14:paraId`, so a duplicated paraId makes Reviewer A's and Reviewer B's paragraphs indistinguishable to paragraph-ID targeting — including to this work package's own strict-targeting test case.
651
+
652
+ #### Trigger Taxonomy (Normative)
653
+
654
+ "Whole-paragraph deletion" is ambiguous across the four combinations of paragraph-mark state and content state. Only one routes to WP08b:
655
+
656
+ | Paragraph mark | Pre-existing content | Route |
657
+ |:--|:--|:--|
658
+ | Deleted by foreign author | All deleted | **WP08b sibling restoration** (the resurrection state) |
659
+ | Deleted by foreign author | Intact or partially deleted | Ordinary run-level cross-author slicing — the accepted view is non-empty; a pending forward merge is legal and Word-native |
660
+ | Not deleted | All deleted | Ordinary cross-author insertion — no foreign mark owns the paragraph; MUST NOT route to WP08b |
661
+ | Deleted by current author | All deleted | `merge-same-author`; MUST NOT route to WP08b |
662
+
663
+ #### Validation Predicate (Normative)
664
+
665
+ The reconciliation rule must match the resurrection state exactly. A broader rule of the form "foreign paragraph-mark deletion plus visible insertion in the same paragraph" is **wrong** — inserting text into a paragraph whose mark is deleted by another author is legal, Word-native, and common (it is an ordinary pending merge). Flagging it would reject valid third-party documents.
666
+
667
+ The predicate is: foreign `w:pPr/w:rPr/w:del` **AND** every pre-existing content node deleted **AND** a new non-empty foreign `w:ins`. It is a **warning** in `core/redline-validation.js` and an **error** only at the mutation gate (WP08a).
668
+
669
+ > **WP09e supersession:** Word Desktop itself emits that structural predicate when a second reviewer inserts at a rejected-view offset inside the deleted text. Authored shape alone cannot distinguish unsafe generic resurrection from intentional deletion-carrier slicing. WP09e narrows validation and routing by operation intent and anchoring evidence.
670
+
671
+ #### Required Behavior
672
+
673
+ 1. Detect the resurrection state per the trigger taxonomy when an operation attempts to restore non-empty text into the paragraph's empty accepted view.
674
+ 2. Never append visible runs or `w:ins` content to the paragraph still owned by the foreign paragraph deletion.
675
+ 3. Derive Reviewer B's paragraph as a sanitized sibling of the source paragraph per the allowlist and identity rules above, without mutating Reviewer A's original deleted paragraph.
676
+ 4. Track the new paragraph's content and its paragraph mark as Reviewer B insertions with document-scoped revision IDs.
677
+ 5. Preserve document order and ensure `w:sectPr`, tables, list boundaries, comments, bookmarks, and other structural anchors are neither displaced nor duplicated.
678
+ 6. **Restoring a multi-paragraph range emits one inserted sibling paragraph per restored source paragraph**, as a contiguous block preserving source order, with N paragraph-mark insertions — never one merged paragraph.
679
+ 7. **Idempotency**: re-running the same restoration must not emit a second Reviewer B paragraph. If an adjacent same-author inserted paragraph already carries the restoration, route the edit through ordinary run-level slicing of that paragraph.
680
+ 8. Rejected-view descriptors remain read-only targeting aids until rejected-view mutation is deliberately supported. Do not silently treat `revisionView: 'rejected'` as accepted-view mutation.
681
+ 9. **Contract decision (resolved, not deferred):** restoration requires **explicit caller intent** — a dedicated restore operation or an explicit restoration option. A `redline` operation with an empty accepted-view target and non-empty modified text remains fail-closed under WP08a. Automatic conversion is rejected because the same request shape is indistinguishable from an ordinary "insert text into an empty paragraph", and silently choosing restoration would move the caller's content into a different paragraph than the one they targeted.
682
+
683
+ #### Structural Anchors (Normative)
684
+
685
+ Reviewer B's paragraph MUST NOT clone `w:bookmarkStart`/`w:bookmarkEnd` or `w:commentRangeStart`/`w:commentRangeEnd`/`w:commentReference`. Bookmark names are document-unique, and duplicating a comment range attaches one comment to two disjoint locations. Anchors stay on Reviewer A's paragraph, where they remain valid until that deletion is resolved. Every anchor not carried over is reported in the receipt as a structured warning naming the bookmark or comment ID, so the caller can re-anchor deliberately.
686
+
687
+ #### Fail-Closed Refusals (Distinct Codes)
688
+
689
+ A single `UNSAFE_PARAGRAPH_BOUNDARY` code conflates unrelated conditions and reads as a near-collision with the existing `PAIRING_SKIPPED_STRUCTURAL_BOUNDARY`. Enumerate:
690
+
691
+ | Condition | Code |
692
+ |:--|:--|
693
+ | Resurrection attempted without explicit restore intent (WP08a gate) | `FOREIGN_PARAGRAPH_MARK_DELETION` |
694
+ | Source paragraph inside a row deleted via `w:trPr/w:del` — a sibling paragraph cannot survive the row | `UNSAFE_DELETED_TABLE_ROW` |
695
+ | Source paragraph is part of a move (`w:moveFrom` / `w:moveFromRangeStart`) | `UNSUPPORTED_MOVE_REVISION` |
696
+ | Source paragraph's `pPr` carries `w:sectPr` — mirrors the existing deletion refusal in `core/paragraph-targeting.js` | `SECTION_BREAK_PARAGRAPH` |
697
+ | Source paragraph is the final paragraph of the body, so no safe sibling placement exists | `UNSAFE_PARAGRAPH_PLACEMENT` |
698
+
699
+ All refusals return the original document unchanged under atomic mode.
700
+
701
+ #### Lifecycle Invariants
702
+
703
+ For the paragraph pair `[ins(B), del(A)]` in document order:
704
+
705
+ | Resolution | Expected Result |
706
+ |:--|:--|
707
+ | Current view | Reviewer B's restored/adjusted paragraph appears exactly once; Reviewer A's paragraph is invisible. |
708
+ | Accept All | Reviewer B's paragraph and mark become baseline; Reviewer A's content deletion resolves and A's mark merges A forward into its **original** successor (not into B). Net: Reviewer B's paragraph remains exactly once. |
709
+ | Reject Reviewer B | Reviewer B's content is removed and B's inserted mark is rejected, merging the now-empty B forward into A. Net: the document returns to its pre-restoration text with Reviewer A's deletion still pending. |
710
+ | Reject Reviewer A | Reviewer A's paragraph and its text return; Reviewer B's insertion remains independently pending. Both marks remain attributable and structurally valid. |
711
+ | Accept Reviewer A only | Reviewer A's content deletion resolves and A merges forward into its original successor; Reviewer B's inserted paragraph remains pending and visible. |
712
+ | Accept Reviewer B only | Reviewer B's paragraph and mark become baseline; Reviewer A's deleted paragraph remains pending and invisible in the current view. |
713
+
714
+ No lifecycle path may silently discard Reviewer B's restoration, produce invalid nested revisions, orphan comments/bookmarks, or leave duplicate visible text after all revisions are resolved.
715
+
716
+ #### Verification Oracle (Normative)
717
+
718
+ Package validation is not an oracle for this feature; neither is a paragraph-local text comparison. Accepting a paragraph-mark deletion **crosses the paragraph boundary**, so a paragraph-scoped round-trip check cannot observe the merge.
719
+
720
+ Every WP08b fixture must therefore:
721
+
722
+ 1. Reconstruct **body-scoped** (or at minimum a window of source paragraph ± 2) canonical text for **both the accepted view and the rejected view**, and compare each exactly against expectation. The rejected view is where duplicate-text regressions surface; the accepted view alone would pass the reported bug.
723
+ 2. Assert the merge target of each paragraph-mark resolution explicitly, not inferred from resulting text.
724
+ 3. Fail closed with a structured mismatch code and byte-exact rollback on divergence, matching the established `PATCH_ROUNDTRIP_MISMATCH` pattern.
725
+
726
+ #### Planned Implementation Areas
727
+
728
+ - `services/document-operation-applier.js`: route explicit restoration intent; retain the rejected-view mutation guard for unsupported generic mutations.
729
+ - `services/document-operation-mutations.js`: add the paragraph-level restoration mutation and sibling placement; extend the inserted-paragraph builders to emit inserted paragraph marks and sanitized `pPr` (see Paragraph-Mark Semantics item 3).
730
+ - `core/paragraph-targeting.js`: resolve the deleted paragraph identity consistently across accepted and rejected metadata without allowing stale descriptors; reuse the existing `w:sectPr` refusal.
731
+ - Revision allocator and receipt collector: report every paragraph-mark and content revision ID allocated by the restoration, plus dropped-anchor warnings.
732
+ - `core/redline-validation.js`: add the narrowed resurrection-state warning (see Validation Predicate).
733
+
734
+ #### Required Test Matrix
735
+
736
+ 1. Plain whole-paragraph deletion restored verbatim.
737
+ 2. Restored paragraph adjusted while being restored.
738
+ 3. Bold, italic, underline, and mixed-run formatting preservation.
739
+ 4. Numbered and bulleted paragraph restoration without list-label drift.
740
+ 5. Paragraph immediately before `w:sectPr`, **and** a paragraph whose own `pPr` carries `w:sectPr` (refusal).
741
+ 6. Paragraph inside a table cell; paragraph inside a row deleted via `w:trPr/w:del` (refusal).
742
+ 7. Deleted paragraph containing bookmarks or comment anchors: assert anchor counts are **unchanged**, that no name or comment ID appears twice, and that each dropped anchor is reported in the receipt.
743
+ 8. Multiple adjacent deleted paragraphs restored independently and as a range, asserting one inserted sibling per source paragraph and preserved order.
744
+ 9. Same-author deletion behavior remains governed by `merge-same-author` and is not routed through cross-author restoration.
745
+ 10. Each non-triggering row of the trigger taxonomy routes to its stated path and not to WP08b.
746
+ 11. Third-author follow-up edits to Reviewer B's restored paragraph continue to use ordinary cross-author slicing.
747
+ 12. Repeat application of the same restoration is idempotent — no duplicate Reviewer B paragraph.
748
+ 13. Source paragraph retains unresolved content (partial deletion, or a third author's pending `w:ins`) — proves the merge target is A's original successor and that surviving content does not land inside Reviewer B's paragraph.
749
+ 14. `w:moveFrom` source paragraph (refusal); final-paragraph-of-body source (refusal).
750
+ 15. Atomic runner rollback and progressive batch receipts.
751
+ 16. Strict targeting by paragraph ID, index, fingerprint, and `revisionView: 'rejected'` diagnostics — including an assertion that Reviewer A's and Reviewer B's paragraphs carry distinct `w14:paraId` values.
752
+
753
+ Every successful fixture must assert exact current text, both-view body-scoped round-trip equality, structural validation, unique revision IDs, receipt reconciliation, paragraph ordering, and the six lifecycle outcomes above.
754
+
755
+ #### Implementation Record
756
+
757
+ * **Public Contract**: Added a dedicated `restore` document operation. A single restoration accepts a non-empty `modified` string; a contiguous range accepts one string per source paragraph. `generateRedlines: false` is rejected because restoration necessarily creates both a tracked content insertion and an inserted paragraph mark. Generic `redline` operations remain protected by WP08a.
758
+ * **Paragraph Model**: Each counterproposal is inserted immediately before the foreign-deleted source paragraph (or, for a range, as one contiguous inserted block before the source block). The source paragraph is not modified. The new paragraph receives two allocator-issued revisions, a fresh `w14:paraId`, no copied `w14:textId`/`w:rsid*`, and only allowlisted paragraph properties.
759
+ * **Lifecycle Oracle**: Before commit, restoration validates the authored OOXML and compares body-scoped paragraph text vectors for current view, Accept All, and Reject All against independently constructed expected documents. Any mismatch returns `PATCH_ROUNDTRIP_MISMATCH`; the operation savepoint supplies byte-exact rollback.
760
+ * **Idempotency**: An identical adjacent same-author restoration is a no-op. A changed same-author reapplication replaces the prior counterproposal rather than adding a duplicate paragraph.
761
+ * **Files Touched**:
762
+ - `core/paragraph-revision-safety.js`
763
+ - `core/paragraph-targeting.js`
764
+ - `services/document-operation-contract.js`
765
+ - `services/document-operation-applier.js`
766
+ - `services/document-operation-mutations.js`
767
+ - `services/operation-preflight.js`
768
+ - `services/standalone-operation-runner.d.ts`
769
+ - `docs/schemas/document-operations.schema.json`
770
+ - `index.d.ts`
771
+ - `tests/paragraph_level_cross_author_restoration_tests.mjs` (NEW)
772
+ - `tests/types/usage.ts`
773
+ - `README.md`
774
+ - `AGENTS.md`
775
+ - `CHANGELOG.md`
776
+ - `docs/plans/2026-09-08-cross-author-revision-slicing.md`
777
+ * **Functions and Types Touched**:
778
+ - `getParagraphRestorationRefusal` and move-range/content-state helpers (NEW): distinguish deleted-row, move-from, section-break, and unsafe-placement refusals while preserving the narrow trigger taxonomy.
779
+ - `resolveTargetParagraph` (MODIFIED): supports strict fingerprint-only descriptors, bringing runtime targeting into alignment with the published schema.
780
+ - `getCanonicalOperationType`, `normalizeDocumentOperation`, and `validateDocumentOperation` (MODIFIED): normalize and validate explicit single/range `restore` operations and retain full `targetEnd` descriptors.
781
+ - `applyOperationToDocumentXml` (MODIFIED): routes restoration separately and keeps rejected-view mutation read-only for both range endpoints.
782
+ - `restoreDeletedParagraphByExactText` (NEW): resolves the source block, enforces trigger/safety rules, reconstructs rejected-view content, inserts tracked siblings, handles idempotency, and runs the lifecycle oracle.
783
+ - `createSanitizedRestorationPPr`, `buildRejectedRestorationTemplate`, `editRestorationTemplate`, `allocateFreshParagraphId`, `trackRestoredParagraph`, and lifecycle/anchor helpers (NEW): implement property sanitization, formatting preservation, fresh identity, dropped-anchor diagnostics, and exact round-trip checks.
784
+ - `wrapParagraphContentInInsertion`, `buildFallbackInsertedPlainParagraph`, and `buildInsertedPlainParagraph` (MODIFIED): optionally emit inserted paragraph marks, use typed insertion receipt metadata, sanitize restoration properties, and accept fresh paragraph identity.
785
+ - `preflightOperations` (MODIFIED): recognizes restoration state, range cardinality, and structural refusals without mutating the document.
786
+ - `RestoreDocumentOperation` and `RedlineError` (MODIFIED/NEW): publish the operation shape and structured refusal/oracle metadata.
787
+ - WP08b fixture helpers and assertions (NEW): exercise six lifecycle outcomes, strict descriptors, independent and range restoration, formatting/list preservation, anchor warnings, table cells/deleted rows, section/move/placement refusals, taxonomy exclusions, progressive and atomic batches, idempotency, and third-author follow-up slicing.
788
+ * **Verification**:
789
+ - `node tests/paragraph_level_cross_author_restoration_tests.mjs` — PASS.
790
+ - Focused list and insertion-affinity regressions — PASS.
791
+ - `$env:DOCX_TEST_CONCURRENCY='1'; npm test` — PASS, 97 test files passed and 0 failed. The serial final run was used after concurrent attempts hit unrelated per-file timeouts under host contention; each timed-out suite also passed directly.
792
+ - `npm run lint` — PASS.
793
+ - `npm run check:types` — PASS; all 123 runtime exports have declarations.
794
+ - `npm run build` — PASS.
795
+ - `git diff --check` — PASS (line-ending conversion notices only; no whitespace errors).
796
+
797
+ ---
798
+
799
+ ### WP09 — Real-Document Mutation Reliability and Agent-Safe Failure Reporting [WP09a-e COMPLETE]
800
+
801
+ #### Planning and characterization update (2026-09-08)
802
+
803
+ - Updated `docs/plans/2026-09-08-cross-author-revision-slicing.md`: added the three failure classes, the Word Desktop structural oracle, WP09a-e requirements, ordered implementation handoff, sanitized operation examples, and the planned file/function map. Removed private party, person, corpus, URL, clause, and commercial wording from permanent examples.
804
+ - Added `tests/word_deleted_section_edit_oracle_tests.mjs`: introduced local fixture/test helpers `localName`, `descendants`, `elementChildren`, `revisionElements`, `parse`, `plainText`, `assertUniqueRevisionIds`, and `assertValidOracle`; added sanitized inline deletion-carrier and post-source paragraph-restoration OOXML fixtures; asserted direct-child order, authorship, paragraph-mark behavior, global revision-ID uniqueness, validation, and selective/all-author lifecycle outcomes.
805
+ - No production mutation function has been changed for WP09 yet. The new test is a characterization oracle for the structures that the later public-runner and package-facade implementation tests must generate.
806
+ - Verification: focused oracle test passed; serial `$env:DOCX_TEST_CONCURRENCY='1'; npm test` passed **98/98 test files** with the new suite included; `npm run lint` passed; `git diff --check` reported no whitespace errors.
807
+
808
+ #### WP09a-b implementation update (completed 2026-09-09)
809
+
810
+ WP09a and WP09b are implemented. WP09c, WP09d, and WP09e remain planned and are not implied by this completion record.
811
+
812
+ **Production files and functions changed:**
813
+
814
+ - `services/document-operation-mutations.js`
815
+ - `applyToParagraphByExactText`: sends the resolved paragraph's exact accepted-view text to the engine for every text-bearing single-paragraph edit. The legacy caller-text fallback remains only for format-only field-code paragraphs whose canonical accepted view has no extractable text spans.
816
+ - `resolveTargetParagraph`: attaches bounded `targetTextMatch` metadata to the resolved target captured in operation results and receipts.
817
+ - Added internal `escapeInvisibleText`, `codePointLabel`, and `describeTargetTextMatch` helpers. They distinguish `exact`, one-to-one ordinary-space/NBSP `space_equivalent`, and broader `normalized` matches and report at most eight code-point differences.
818
+ - `pipeline/diff-engine.js`
819
+ - Added `computeCharacterDiffs`, a character-local diff without semantic cleanup for refining whitespace substitutions that word tokenization grouped with unchanged content.
820
+ - `engine/surgical-mode.js`
821
+ - `applySurgicalMode`: refines adjacent delete/insert hunks that differ only by ordinary spaces and NBSPs, preserving unchanged hyperlink runs rather than deleting and reconstructing their visible URL text.
822
+ - Plain-text edit groups now apply from right to left. Each group rebuilds the live surgical span index, so an earlier run split/removal cannot leave a stale DOM anchor for a later hunk.
823
+ - Added internal `refineSpaceEquivalentReplacements`, `collectTextEditOperations`, and `codePointAtOffset` helpers. `PATCH_ROUNDTRIP_MISMATCH` now includes `expectedCodePoint` and `actualCodePoint` at the first difference while still returning the exact input OOXML.
824
+ - `node/cli.js`
825
+ - Raised `CLI_CONTRACT_VERSION` to 3 and added the `compact-mutation-results` capability.
826
+ - `executeCli` now passes `apply`, `accept`, `reject`, and `delete-comments` results through `compactMutationResult`.
827
+ - Added `boundedText`, `compactError`, `compactResolvedTarget`, `compactReceipt`, `compactOperationResult`, `summarizeIssues`, and `compactMutationResult`.
828
+ - Normal mutation stdout omits `documentXml`, `oxml`, comments/numbering XML, inspection payloads, raw package buffers, and full issue arrays. It retains per-operation errors/receipts, `written`, `outputPath`, compact issue counts, and a derived `completion` flag. Resolved target text is removed while bounded match/code-point diagnostics remain.
829
+ - `services/standalone-operation-runner.d.ts`
830
+ - Extended `ResolvedDocumentTarget` with the typed `targetTextMatch` diagnostic contract.
831
+
832
+ **Tests changed:**
833
+
834
+ - `tests/cross_author_slicing_hyperlink_roundtrip_tests.mjs`
835
+ - Replaced the identifying policy sample with a synthetic Service Policy fixture.
836
+ - Added an ASCII-space target against an NBSP source, two code-point assertions, hyperlink relationship/history preservation, exact Accept-All output, and exact Reject-current-author restoration of the NBSP-bearing source.
837
+ - `tests/agent_cli_tests.mjs`
838
+ - Added a complete DOCX/CLI ASCII-space-target versus NBSP-source regression using strict paragraph identity.
839
+ - Asserts exact written text, `space_equivalent` diagnostics, source restoration after Reject, compact validation summaries, omitted resolved clause/XML text, and truthful `completion`.
840
+ - `tests/agent_cli_edge_tests.mjs`
841
+ - Added failed-apply assertions for `written: false`, `outputPath: null`, `completion: false`, summarized validation, no `documentXml`, bounded stdout, no unrelated body text, and the actionable per-operation error.
842
+ - `tests/plugin_wrapper_compatibility_tests.mjs`
843
+ - Sanitized the comment-author fixture and verified compact CLI errors retain bounded comment author/text details required to resolve `COMMENTED_CONTENT_DELETE`.
844
+
845
+ The first full serial compatibility run exposed two retained-contract requirements and was not treated as final: compact errors initially omitted protected-comment details, and the strict source-truth change initially removed the established caller-text fallback for format-only field-code paragraphs with no canonical text spans. `compactError` now retains bounded comment records, and `applyToParagraphByExactText` preserves that non-text fallback. Both formerly failing suites pass directly; the final serial result is recorded below after rerun.
846
+
847
+ **Documentation changed:** `README.md`, `CHANGELOG.md`, `ARCHITECTURE.md`, `AGENTS.md`, and this plan now describe source-truth mutation alignment, invisible-character diagnostics, reverse live-span application, CLI contract version 3, compact validation summaries, and completion semantics.
848
+
849
+ **Final WP09a-b verification:**
850
+
851
+ - `node tests/cross_author_slicing_hyperlink_roundtrip_tests.mjs` — PASS.
852
+ - `node tests/agent_cli_tests.mjs` — PASS.
853
+ - `node tests/agent_cli_edge_tests.mjs` — PASS.
854
+ - `node tests/plugin_wrapper_compatibility_tests.mjs` — PASS.
855
+ - `node tests/standalone_operation_runner_tests.mjs` — PASS.
856
+ - `$env:DOCX_TEST_CONCURRENCY='1'; npm test` — PASS, **98/98 test files** and 0 failed.
857
+ - `npm run lint` — PASS.
858
+ - `npm run check:types` — PASS; all 123 runtime exports have declarations.
859
+ - `npm run build` — PASS.
860
+ - `git diff --check` — PASS (line-ending conversion notices only; no whitespace errors).
861
+
862
+ #### WP09c-e implementation update (completed 2026-09-09)
863
+
864
+ WP09c, WP09d, and WP09e are implemented. The implementation keeps generic
865
+ accepted-view mutation fail-closed and adds only the explicit rejected-view
866
+ operation described below.
867
+
868
+ **Production files and functions changed:**
869
+
870
+ - `core/validation-delta.js` (new)
871
+ - Added internal `issueKey`, exported `subtractValidationIssueMultiset`, and
872
+ exported `validationErrors`. Validation differences retain multiplicity and
873
+ include source/severity/code/message in the stable signature.
874
+ - `core/revision-cloning.js`
875
+ - Added `clonePropertiesWithoutRevisionHistory`. New paragraphs and runs may
876
+ inherit effective properties, but cloned `ins`, `del`, move, `pPrChange`,
877
+ `rPrChange`, table/row/cell property-change, and section-property history is
878
+ removed rather than duplicated with stale identities.
879
+ - `core/paragraph-revision-safety.js`
880
+ - `getParagraphRestorationRefusal` now accepts
881
+ `requireFollowingParagraph: false` for an inline deletion-carrier edit that
882
+ creates no paragraph. All section, move, and deleted-table-row refusals stay
883
+ active; paragraph restoration still requires a following sibling.
884
+ - `core/redline-validation.js`
885
+ - `validateRedlineOoxml` accepts an already-parsed document DOM for internal
886
+ operation checks, avoiding an extra full-source parse while retaining the
887
+ public string input. All existing validation rules are unchanged.
888
+ - `engine/surgical-spans.js`
889
+ - `getRunChildText` and `isTextLikeRunChild` recognize `w:delText`, allowing
890
+ the shared run-piece splitter to address rejected-view deletion text.
891
+ - `engine/surgical-run-splitting.js`
892
+ - `splitTrackChangeCarrier` now supports `w:del` as well as `w:ins`, emits
893
+ `w:delText` on split deletion runs, preserves the leading carrier identity,
894
+ allocates the trailing carrier identity, refreshes duplicated
895
+ `w:rPrChange` IDs, and records the allocation in the active receipt.
896
+ - `cloneRunPiece` preserves tab, break, soft-hyphen, and non-breaking-hyphen
897
+ elements while slicing deletion carriers instead of flattening those
898
+ controls into `w:delText`.
899
+ - `services/document-operation-contract.js`
900
+ - `getCanonicalOperationType` maps only `type: "insert"` plus a rejected-view
901
+ target to `rejected-insert`.
902
+ - `normalizeDocumentOperation` normalizes `anchor.exactText`, `occurrence`,
903
+ and `offset` while retaining whether occurrence was supplied explicitly.
904
+ - `validateDocumentOperation` requires non-empty inserted text, an in-range
905
+ anchor-relative offset, and `existingRevisions: "slice-cross-author"`.
906
+ - `services/document-operation-mutations.js`
907
+ - Added `insertIntoRejectedDeletedText` and its exact occurrence, deletion
908
+ carrier, formatting-clone, and unsupported-boundary helpers. It resolves a
909
+ strict rejected-view paragraph, requires a wholly foreign-deleted state,
910
+ refuses repeated anchors without an explicit occurrence, splits the direct
911
+ deletion carrier, emits the new author's sibling `w:ins`, and verifies that
912
+ the rejected view is unchanged while the accepted view exposes the inserted
913
+ text. Comments, bookmarks, fields, hyperlinks, moves, and other non-text
914
+ split markup fail closed with `UNSAFE_REVISION_BOUNDARY`.
915
+ - `buildInsertedListParagraph`, `buildEmptyParagraphTemplateFromAnchor`, and
916
+ `wrapParagraphContentInInsertion` use
917
+ `clonePropertiesWithoutRevisionHistory` for effective property inheritance.
918
+ - `verifyParagraphRestorationLifecycle` validates baseline/output issue
919
+ multisets, separately validates each inserted mutation envelope, returns
920
+ `GENERATED_OOXML_INVALID` for generated markup defects, and preserves the
921
+ exact current/Accept-All/Reject-All paragraph-vector checks.
922
+ - `followingParagraphBlock`, `buildExpectedRestorationDocument`, and
923
+ `restoreDeletedParagraphByExactText` now detect, verify, replace, and emit
924
+ restoration blocks immediately after the complete deleted source range.
925
+ - `services/document-operation-applier.js`
926
+ - `applyOperationToDocumentXml` dispatches `rejected-insert` and permits a
927
+ rejected target only for explicit `rejected-insert` and `restore`
928
+ operations. Before marking any changed operation committed, it validates
929
+ the entire live document against its DOM savepoint by issue multiset. A
930
+ generated error restores the savepoint and returns
931
+ `GENERATED_OOXML_INVALID` with a refused receipt.
932
+ - `services/operation-preflight.js`
933
+ - Same-target conflict grouping includes `rejected-insert`, so it cannot evade
934
+ overlap diagnostics merely because it uses a distinct canonical kind.
935
+ - `node/docx-document.js`
936
+ - `DocxDocument.applyOperations` now applies the same multiset baseline-delta
937
+ classification to document and package validation. Unchanged legacy defects
938
+ remain in `validation.originalIssues`; introduced errors block the write and
939
+ are returned as generated issues.
940
+ - `docs/schemas/document-operations.schema.json`
941
+ - Added `rejectedTextInsertionAnchor` and the optional `anchor` field on the
942
+ compatible `insert` shape; runtime validation makes it mandatory for a
943
+ rejected-view target.
944
+ - `services/standalone-operation-runner.d.ts`
945
+ - Added `RejectedTextInsertionAnchor` and exposed `anchor` on insert/redline
946
+ operation declarations.
947
+
948
+ **Tests changed or added:**
949
+
950
+ - `tests/paragraph_level_cross_author_restoration_tests.mjs`
951
+ - Updated current-view, selective-author, range, idempotency, progressive,
952
+ table-cell, and follow-up edit assertions for post-source restoration.
953
+ - `tests/cross_author_carrier_splitting_tests.mjs`
954
+ - Extended the allocator receipt assertion so a refreshed cloned
955
+ `w:rPrChange` and the trailing carrier are both recorded in allocation order.
956
+ - `tests/docx_package_facade_tests.mjs`
957
+ - Updated the intentionally malformed-package fixture to prove an unchanged
958
+ package defect stays in `originalIssues` while a safe document mutation may
959
+ still be written with zero generated issues.
960
+ - `tests/merge_same_author_tests.mjs`
961
+ - Corrected an intermittent false-positive assertion that searched all OOXML
962
+ for the bare string `45` and therefore failed whenever a legitimate revision
963
+ ID reached 45. It now checks the intended intermediate phrase `45 days`.
964
+ - `tests/wp09c_e_validation_identity_rejected_insert_tests.mjs` (new)
965
+ - Proves restoration succeeds over an unrelated duplicate-ID baseline;
966
+ paragraph expansion does not clone `pPrChange`/`rPrChange` history; rejected
967
+ insertion produces `pPr / del(A) / ins(B) / del(A)` with globally unique
968
+ IDs; rejected text remains exact; selective/all-author lifecycle outcomes
969
+ match the minimized Word shape; missing policy/anchor inputs fail schema
970
+ validation; and both dirty-baseline restoration and rejected-view insertion
971
+ write successfully through `openDocx(...).applyOperations` with zero
972
+ generated package issues. Oracle reinforcement covers deletion start/end,
973
+ multi-run and formatted-run boundaries, tab/break preservation,
974
+ repeated-anchor disambiguation, comment/hyperlink/field refusal, explicit
975
+ rejected-view range restoration, all seven lifecycle views, and a combined
976
+ inline-insertion plus post-source range-restoration forward-merge case.
977
+ - `tests/word_deleted_section_edit_oracle_tests.mjs`
978
+ - Remains the sanitized Word-authored structural characterization oracle for
979
+ inline deletion slicing and post-source range restoration.
980
+
981
+ **Documentation changed:** `README.md`, `CHANGELOG.md`, `ARCHITECTURE.md`,
982
+ `AGENTS.md`, the operation JSON Schema, declarations, and this plan describe
983
+ baseline-delta validation, effective-property clone sanitation, post-source
984
+ restoration, and the explicit rejected-view insertion contract.
985
+
986
+ **Final WP09c-e verification:**
987
+
988
+ - `node tests/paragraph_level_cross_author_restoration_tests.mjs` — PASS.
989
+ - `node tests/wp09c_e_validation_identity_rejected_insert_tests.mjs` — PASS.
990
+ - `node tests/word_deleted_section_edit_oracle_tests.mjs` — PASS.
991
+ - `node tests/cross_author_carrier_splitting_tests.mjs` — PASS.
992
+ - `node tests/docx_package_facade_tests.mjs` — PASS.
993
+ - `node tests/performance_phase1_session_tests.mjs` — PASS; the full source is
994
+ still parsed once and the live document serialized once.
995
+ - 30 consecutive isolated `merge_same_author_tests.mjs` runs — PASS after
996
+ correcting the bare-revision-ID false positive.
997
+ - `$env:DOCX_TEST_CONCURRENCY='1'; npm test` — PASS, **99/99 test files** and 0 failed.
998
+ - `npm run lint` — PASS.
999
+ - `npm run check:types` — PASS; all 123 runtime exports have declarations.
1000
+ - `npm run build` — PASS.
1001
+ - `git diff --check` — PASS (line-ending conversion notices only; no whitespace errors).
1002
+
1003
+ #### Motivation and First Bug Report
1004
+
1005
+ The first post-WP08 report is not a paragraph-restoration case and does not involve a foreign paragraph-mark deletion. It is an ordinary single-paragraph replacement in a paragraph with two external hyperlinks and non-breaking spaces (`U+00A0`) immediately before both URLs and after the Service Policy URL.
1006
+
1007
+ The requested edit adds an execution-date qualifier around the second URL while leaving the Processing Schedule URL alone. The operation supplied ordinary spaces (`U+0020`) in its `target.exactText` and `modified` strings. Strict descriptor resolution still selected a uniquely identified paragraph by paragraph ID/fingerprint because paragraph targeting treats ordinary spaces and NBSPs as equivalent. The mutation then failed closed:
1008
+
1009
+ ```text
1010
+ PATCH_ROUNDTRIP_MISMATCH at offset 934
1011
+ expected: "Service Policy available at example.invalid/policy/ that ..."
1012
+ actual: "Service Policy available at\u00a0 example.invalid/policy/ that ..."
1013
+ ```
1014
+
1015
+ Adding another ordinary space on retry made the actual sequence `NBSP + two ordinary spaces`; it did not consume the source NBSP. A later `extract` exposed the hidden NBSPs, and the agent constructed a third operation with them, but the transcript contains no third `apply` command or successful result. It nevertheless reported the document as complete. No output path was produced by either recorded application.
1016
+
1017
+ The failed CLI response also serialized the entire large `documentXml` payload and repeated 21 pre-existing `MISSING_SPACE_PRESERVE` issues plus one pre-existing commentsExtended content-type issue. `generatedIssues` was empty. This noise obscured the actionable operation error and contributed to an unreliable agent recovery loop.
1018
+
1019
+ The exact source paragraph was recovered from the failed result. It contains no open revisions itself; its important shape is:
1020
+
1021
+ ```xml
1022
+ <w:r><w:t>... Processing Schedule available at&#xA0;</w:t></w:r>
1023
+ <w:hyperlink r:id="rId7"><w:r><w:t>example.invalid/schedule</w:t></w:r></w:hyperlink>
1024
+ <w:r><w:t>... Service Policy available at&#xA0;</w:t></w:r>
1025
+ <w:hyperlink r:id="rId8"><w:r><w:t>example.invalid/policy/</w:t></w:r></w:hyperlink>
1026
+ <w:r><w:t>&#xA0;(the “</w:t></w:r>
1027
+ ```
1028
+
1029
+ This is therefore a distinct gap in replacement alignment and failure ergonomics. WP08a/b remain unchanged.
1030
+
1031
+ #### Second Bug Report — Restore Blocked by Pre-Existing Validation Errors
1032
+
1033
+ The second report exercises the explicit WP08b `restore` operation against a paragraph wholly deleted by another reviewer. Target resolution succeeds by paragraph ID in the accepted/current view, where the paragraph text is empty; the rejected view exposes the deleted restrictions paragraph used to draft the adjusted restoration. The operation then fails before commit with:
1034
+
1035
+ ```text
1036
+ PATCH_ROUNDTRIP_MISMATCH
1037
+ stage: "validation"
1038
+ message: repeated MISSING_SPACE_PRESERVE errors
1039
+ written: false
1040
+ ```
1041
+
1042
+ Those `MISSING_SPACE_PRESERVE` errors already appear in `validation.originalIssues`. They occur elsewhere in the source document and were not created by the restoration. The package facade knows the baseline is imperfect, but `verifyParagraphRestorationLifecycle` calls `validateRedlineOoxml(outputXml)` and rejects the complete output whenever *any* error exists. It never validates `beforeXml`, subtracts the baseline, or scopes the errors to the inserted restoration paragraphs. This makes WP08b unusable on a real document that contains an unrelated legacy validation defect, even when the proposed restoration itself is valid.
1043
+
1044
+ The recovery then abandons `restore` and considers inserting the paragraph after a different surviving paragraph. That is not an equivalent fallback: it can change clause order, list numbering, paragraph-mark lifecycle behavior, and the relationship between the restored counterproposal and the original deleted paragraph. The transcript says the insertion worked but contains neither its apply result nor a validated output record. WP09 must prohibit silent operation-type fallback and make successful completion independently provable.
1045
+
1046
+ #### Third Bug Report — Paragraph Expansion Duplicates Revision IDs
1047
+
1048
+ The third report asks to restore the adjacent `8.4 Changes; No Waiver` heading and body, while modifying the body so an email satisfies the signature/writing requirement. It reconfirms the WP09c dirty-baseline failure: even a trivial explicit restore of `"Synthetic restoration text"` is rejected because the same 21 pre-existing `MISSING_SPACE_PRESERVE` issues are treated as generated validation failures.
1049
+
1050
+ It also exposes a separate structural mutation defect. The agent tried to append a new paragraph to an existing clause with both `"\n\n"` and `"\n"`. In both cases the operation returned `results[0].status: "applied"`, but the package facade subsequently rejected the output:
1051
+
1052
+ ```text
1053
+ status: error
1054
+ written: false
1055
+ error: PACKAGE_OPERATION_FAILED
1056
+ message: Applied operations introduced invalid revision markup (DUPLICATE_REVISION_ID)
1057
+ results[0]: applied
1058
+ ```
1059
+
1060
+ The duplicate appears only after expanding one paragraph into multiple tracked paragraphs. The likely implementation surface is the plain-adjacency/structured paragraph builder: `buildEmptyParagraphTemplateFromAnchor` clones the anchor's `pPr` and first-run `rPr` verbatim, while `wrapParagraphContentInInsertion` clones those properties again. Existing `w:pPrChange` or `w:rPrChange` descendants can therefore carry their old `w:id` into a newly inserted paragraph even though the new content and paragraph mark use the live document allocator. This hypothesis must be proven by recording the duplicated ID and both owning elements before choosing whether inherited change-history elements should be stripped or deliberately cloned with fresh IDs.
1061
+
1062
+ The run also demonstrates why structural validity alone is insufficient. An earlier workaround replaced the text of a surviving `3.1 Restricted Use` paragraph with restored `3.2` language; the generated redline was internally coherent but semantically targeted the wrong clause. For 8.4, another workaround inserted only the heading into a preceding amendment sentence, producing same-author deletions of that sentence and an insertion of the heading. A final baseline validation reported no *structural* issues, yet the requested standalone 8.4 heading/body was delivered inline after an earlier subsection instead. WP09 must require a document-shape oracle—paragraph count, order, identity, and exact neighboring text—for structural operations and restorations.
1063
+
1064
+ #### Microsoft Word Desktop Oracle — Edits Inside a Deleted Section
1065
+
1066
+ The user supplied a complete `word/document.xml` after making two edits directly in Microsoft Word Desktop. This is a first-party structural oracle, not an engine-generated hypothesis. The full source XML must remain an external/private acceptance artifact; implementation must reduce the relevant paragraphs to minimal checked-in fixtures.
1067
+
1068
+ **Oracle A — inline insertion inside a deleted subsection body.** A synthetic `8.1 Standard Charges` body has a paragraph-mark deletion by `Reviewer A` and all original text is deleted. Word inserts `REVIEWER B INSERTION` at a rejected-view offset by splitting Reviewer A's deletion carrier:
1069
+
1070
+ ```xml
1071
+ <w:p>
1072
+ <w:pPr><w:rPr><w:del w:id="710" w:author="Reviewer A"/></w:rPr></w:pPr>
1073
+ <w:del w:id="711" w:author="Reviewer A">The account holder must pa</w:del>
1074
+ <w:ins w:id="712" w:author="Reviewer B">REVIEWER B INSERTION</w:ins>
1075
+ <w:del w:id="713" w:author="Reviewer A">y each undisputed invoice.</w:del>
1076
+ </w:p>
1077
+ ```
1078
+
1079
+ Word preserves the leading carrier's ID/metadata, gives the trailing split carrier a fresh ID while retaining Reviewer A's author/date, and emits Reviewer B's insertion as a sibling—never nested inside `w:del`. It does not add an inserted paragraph mark because no new paragraph was created. This proves that foreign paragraph-mark deletion + all original content deleted + a foreign `w:ins` is not intrinsically invalid. The missing discriminator in WP08 is **intent and rejected-view anchoring**: a generic current-view insertion into an empty deleted paragraph remains ambiguous and fail-closed, while an explicit insertion at a uniquely resolved offset inside the foreign deletion is Word-native deletion-carrier slicing.
1080
+
1081
+ This operation is lifecycle-dependent by design. Rejecting Reviewer B removes only `REVIEWER B INSERTION`; rejecting Reviewer A restores the original sentence with Reviewer B's insertion at the exact `pa|y` offset. Accepting Reviewer A removes the deleted text and resolves its paragraph mark, so Reviewer B's surviving text follows Word's forward-merge semantics. WP09 must compare all selective Accept/Reject outcomes with Word Desktop, not assume the insertion remains an independent standalone paragraph.
1082
+
1083
+ **Oracle B — two new paragraphs between wholly deleted subsections.** Word leaves a deleted `8.2 Usage Adjustments` heading and body byte-for-byte intact, then inserts two new sibling paragraphs after that source block and before deleted `8.3 Collection Costs`:
1084
+
1085
+ ```text
1086
+ [del(A): 8.2 heading]
1087
+ [del(A): 8.2 body]
1088
+ [ins(B) paragraph mark + ins(B) content: 8.2 Usage Adjustments]
1089
+ [ins(B) paragraph mark + ins(B) content: Usage above the stated threshold may be billed at the next tier.]
1090
+ [del(A): 8.3 heading]
1091
+ ```
1092
+
1093
+ The heading paragraph uses five distinct synthetic IDs (720–724) for its paragraph-mark insertion, Word-authored property-change metadata, nested historical paragraph-mark snapshot, content insertion, and run-property change. The body uses IDs 725 and 726 for paragraph mark and content. All IDs are globally unique. The extra `w:rPrChange` nodes are Word UI artifacts and need not be reproduced byte-for-byte if the engine preserves equivalent effective bold formatting and lifecycle behavior; if emitted, however, each must receive a unique allocator ID.
1094
+
1095
+ Oracle B supersedes WP08b's pre-source placement decision for a fully deleted source range. The target behavior is now: insert the counterproposal block immediately **after the source range and before its original next paragraph**, matching Word Desktop. The source range remains untouched. Combined cases—such as Oracle A immediately before Oracle B—must be tested because accepting paragraph-mark deletions can cascade surviving inline content forward into the next inserted paragraph.
1096
+
1097
+ #### Diagnosis to Prove Before Mutation Changes
1098
+
1099
+ WP09 must first reduce the recovered paragraph and operation to a checked-in fixture and record the surgical diff/mutation trace. The implementation must determine which of these boundaries is wrong rather than patching the final string:
1100
+
1101
+ 1. `computeWordDiffs` may align the source NBSP as unchanged while treating the requested ordinary space as an insertion after it when the surrounding phrase is also replaced.
1102
+ 2. The diff may be correct, but `processDelete` may fail to consume the NBSP at a run/hyperlink boundary.
1103
+ 3. `processInsert` may consume a stale or ambiguous replacement anchor and place the ordinary space beside the still-live NBSP.
1104
+ 4. The document runner may pass a space-equivalent caller target as the edit-coordinate baseline instead of the exact accepted-view text recovered from the resolved paragraph.
1105
+
1106
+ The regression must expose the diff tuples, original offsets, live DOM anchors, and final accepted text sufficiently to identify the failing layer. A fix is not accepted if it merely special-cases URLs, policy wording, or one observed offset.
1107
+
1108
+ #### Normative Text Contract
1109
+
1110
+ 1. **Target selection and edit coordinates are separate concerns.** Space/NBSP equivalence may select a uniquely identified paragraph, but mutations MUST use the resolved paragraph's exact canonical accepted-view text as their source coordinate system.
1111
+ 2. **`modified` remains exact.** The engine must reconstruct the caller's requested `modified` string byte-for-byte at the JavaScript string level, including every `U+0020`, `U+00A0`, tab, and line break. WP09 must not make replacement text globally whitespace-normalized.
1112
+ 3. **Whitespace substitutions are real edits.** When exact source has NBSP and `modified` has an ordinary space, the output must track deletion/replacement of the NBSP; it must never retain the NBSP and append the ordinary space beside it.
1113
+ 4. **Unchanged hyperlink containers survive.** Both `rId7` and `rId8`, their `w:history` attributes, and their run formatting must be preserved. Text immediately outside a hyperlink must not be moved inside it, and URL text must not be reconstructed as a plain run.
1114
+ 5. **Fail closed remains mandatory.** `PATCH_ROUNDTRIP_MISMATCH` is doing the right thing by refusing incorrect OOXML. WP09 fixes the false mismatch; it must not weaken, normalize, or remove the exact accepted-view oracle.
1115
+ 6. **The runner must report how matching occurred.** If target resolution used space equivalence, the result/receipt should expose that fact and provide escaped source/caller excerpts or differing code points. Invisible characters must be diagnosable without a second ad hoc script.
1116
+
1117
+ #### WP09a — Source-Truth Replacement Alignment
1118
+
1119
+ 1. Capture the recovered paragraph as a minimal fixture with the two hyperlinks, all three NBSP boundaries, bold/underline formatting on the defined term, and the reported replacement.
1120
+ 2. Make the exact resolved accepted-view text authoritative from target resolution through surgical span construction. A normalized target string may validate identity, but must never supply mutation offsets.
1121
+ 3. Normalize replacement hunks/anchors so an NBSP-to-space substitution adjacent to a retained or shifted hyperlink becomes one deletion plus one insertion at the same logical boundary.
1122
+ 4. Rebuild live span/anchor state after any deletion that detaches a run. Multiple replacement hunks in the same paragraph must not reuse stale pre-mutation nodes.
1123
+ 5. Preserve existing insertion-only behavior, foreign-carrier slicing, explicit insertion affinity, formatting, comments, bookmarks, fields, and hyperlink relationships.
1124
+ 6. Run the exact round-trip oracle after the complete paragraph mutation and return the byte-exact input OOXML on any mismatch.
1125
+
1126
+ #### WP09b — Compact, Actionable CLI Failures
1127
+
1128
+ 1. Do not include full `documentXml` in normal CLI stdout for `apply`, `accept`, `reject`, or `delete-comments`. It remains available from library APIs where it is the actual programmatic result, but the CLI already communicates durability through the written DOCX and `outputPath`.
1129
+ 2. On a failed mutation, lead with the per-operation error and retain `written: false`, `outputPath: null`, `status`, `results`, and receipts. Include `mismatchOffset`, escaped excerpts, and code-point details for whitespace mismatches.
1130
+ 3. Summarize pre-existing validation issues by code/count in the normal mutation response. Keep `generatedIssues` explicit and provide full issue arrays only through `validate` or an explicit verbose diagnostics option if one is added.
1131
+ 4. A failed or partial result must be mechanically unmistakable. Add a compact `completion`/`success` signal only if it is derived from `written === true`, a non-error top-level status, and zero failed result entries; do not introduce a second contradictory status model.
1132
+ 5. CLI tests must prove that a failed application cannot emit an output path, cannot write a destination, and produces bounded stdout that does not contain `<w:document>` or contract body text.
1133
+
1134
+ The library cannot prevent an external agent from making a false narrative claim, but its default output must make the recorded mistake difficult: there must be no huge XML payload between the failure code and `written: false`, and no ambiguous success-looking field.
1135
+
1136
+ #### WP09c — Baseline-Delta Validation for Restoration and Package Mutation
1137
+
1138
+ 1. `verifyParagraphRestorationLifecycle` must validate both `beforeXml` and `outputXml`. Pre-existing errors are baseline diagnostics, not generated-output failures.
1139
+ 2. Compare issues as a **multiset**, not a `Set`: code/message duplicates occur many times in real Word documents. An additional occurrence after mutation is generated even when its code/message matches a baseline issue.
1140
+ 3. Validate every newly inserted or modified restoration paragraph independently. A generated `<w:t>` or `<w:delText>` with missing `xml:space="preserve"` must fail even if the source already contains the same error elsewhere.
1141
+ 4. Full-document validation must still catch document-scoped failures such as duplicate revision IDs, invalid revision nesting, unsafe paragraph restoration state, or duplicated structural anchors. Baseline subtraction must not become a blanket bypass.
1142
+ 5. Apply the same baseline-delta semantics at the package facade. Pre-existing package defects must remain visible in `originalIssues`; newly introduced defects belong in `generatedIssues` and fail the write. If a package defect cannot be safely classified, fail closed with a package-validation code rather than mislabeling it as a text round-trip mismatch.
1143
+ 6. Reserve `PATCH_ROUNDTRIP_MISMATCH` for current/Accept-All/Reject-All text-or-lifecycle divergence. Validation failures should return a distinct structured code such as `GENERATED_OOXML_INVALID`, with `stage: "validation"`, generated issue details, and the original document unchanged.
1144
+ 7. A failed `restore` must never be auto-converted to `redline`, `replace`, or an insertion beside a convenient surviving paragraph. Recovery requires either correcting the reported cause and retrying the same restoration or explicit caller authorization for a semantically different operation.
1145
+ 8. Successful restoration through the package facade must prove `written: true`, a non-null `outputPath` at the CLI layer, one applied result with a committed receipt, zero generated validation issues, and exact lifecycle oracle results.
1146
+
1147
+ #### WP09d — Revision Identity and Shape Oracles for Paragraph Expansion
1148
+
1149
+ 1. Every revision-bearing element introduced into the live document—including `w:ins`, `w:del`, paragraph-mark revisions, `w:rPrChange`, `w:pPrChange`, table/row revisions, and deliberately preserved cloned revisions—must have a document-unique allocator-issued `w:id`.
1150
+ 2. New paragraphs may inherit effective paragraph/run formatting, but MUST NOT blindly inherit the anchor paragraph's revision history. Define a shared clone policy:
1151
+ - strip `w:pPrChange`, `w:rPrChange`, and other historical `*Change` descendants when only effective formatting is needed; or
1152
+ - when revision history is intentionally preserved, deep-clone it and refresh every revision ID through the live document allocator.
1153
+ 3. `buildEmptyParagraphTemplateFromAnchor`, `buildInsertedPlainParagraph`, `buildFallbackInsertedPlainParagraph`, `wrapParagraphContentInInsertion`, and list/structured paragraph builders must use that shared policy. No builder may call `cloneNode(true)` on revision-bearing properties without explicit sanitization or ID refresh.
1154
+ 4. Perform an operation-level global revision-ID uniqueness check before an operation is reported as applied. A duplicate introduced by the mutation must roll back the operation savepoint, mark its receipt refused/uncommitted, and return a per-operation generated-markup error. It must not appear as `results[i].status: "applied"` followed by only a top-level package error.
1155
+ 5. Preserve the package-level duplicate-ID check as defense in depth. Operation-level and package-level checks must agree on the offending ID and element kinds.
1156
+ 6. Structural paragraph operations require shape postconditions in addition to accepted text: exact paragraph count delta, sibling order, fresh `w14:paraId`, unchanged anchor/source text where the operation is insertion-only, and correct paragraph-mark ownership.
1157
+ 7. Accept All and Reject Current Author must be checked at paragraph-vector scope. Rejecting a paragraph expansion must restore the original paragraph vector without empty stubs; accepting must retain the intended standalone paragraphs in order.
1158
+ 8. Restoring an adjacent deleted heading/body pair should use one explicit range `restore` operation with two `modified` strings, not a sequence that targets the newly created heading or a multiline replacement of an unrelated surviving clause. The body may be adjusted during restoration to add the email-sufficiency sentence.
1159
+
1160
+ #### WP09e — Word-Native Deleted-Section Editing
1161
+
1162
+ 1. Add an explicit, unambiguous contract for inserting at a rejected-view offset inside foreign deleted content. Reuse `type: "insert"` only if it can require a strict deleted-text anchor/offset and `revisionView: "rejected"`; otherwise add a dedicated operation shape. Do not reinterpret an ordinary empty accepted-view `redline` as this intent.
1163
+ 2. Resolve the deleted carrier by paragraph identity plus an exact, unique rejected-view anchor (or explicit offset tied to a fingerprint). Stale fingerprints, repeated anchors, move revisions, comments crossing the split, and ambiguous offsets fail closed.
1164
+ 3. Split a foreign `w:del` carrier at the exact run-piece offset. Preserve the leading carrier identity, allocate a fresh ID for each trailing carrier, preserve original author/date on split carriers, convert/retain `w:delText` correctly, and preserve formatting, tabs, breaks, rendered page breaks, fields, bookmarks, and hyperlinks.
1165
+ 4. Insert Reviewer B's `w:ins` as a sibling between deletion carriers. Never nest `w:ins` inside `w:del`; never add a paragraph-mark insertion unless the operation actually creates a paragraph.
1166
+ 5. Narrow `inspectForeignDeletedParagraphTarget` and `findForeignDeletedParagraphResurrections`: explicit, structurally anchored deletion-carrier slicing is allowed and must not produce `FOREIGN_PARAGRAPH_MARK_DELETION`; ambiguous generic resurrection stays protected by WP08a.
1167
+ 6. Update range restoration placement to insert Reviewer B's sibling block immediately **after** the complete foreign-deleted source range and before the range's original next paragraph, matching Oracle B. Idempotency detection and changed-restoration replacement must recognize the new side of the source block.
1168
+ 7. Preserve WP08b property sanitization and fresh identity requirements. Word's incidental `rsid`, `textId`, and property-change history are not required output, but effective heading/body formatting, separate paragraph/content revisions, and global ID uniqueness are required.
1169
+ 8. Extend lifecycle oracles to the entire affected section and compare engine resolution with Word Desktop for Current, Accept All, Reject All, Accept A, Reject A, Accept B, and Reject B. Explicitly test forward-merge cascades across adjacent deleted paragraphs and inserted blocks.
1170
+ 9. The validator must accept the minimized Word-authored Oracle A shape. It may warn about lifecycle dependency, but must not label a first-party Word structure as an unsafe resurrection solely from the presence of a foreign insertion in an otherwise deleted paragraph.
1171
+
1172
+ #### Required Test Matrix
1173
+
1174
+ 1. Exact synthetic two-hyperlink Service Policy fixture: ASCII-space operation against NBSP source; current view and Accept All equal `modified` exactly.
1175
+ 2. The same fixture through `applyOperationsToDocumentXml`, `openDocx(...).applyOperations`, and CLI `apply` with strict paragraph ID/fingerprint targeting.
1176
+ 3. Qualifier inserted before the second hyperlink, after it, and on both sides; each permutation with `U+0020 -> U+00A0`, `U+00A0 -> U+0020`, and unchanged NBSP.
1177
+ 4. Two hyperlinks in one paragraph where only the second surrounding clause changes; assert both relationship IDs and hyperlink attributes survive.
1178
+ 5. Repeated URL text and repeated surrounding prose so placement cannot rely on the first string occurrence.
1179
+ 6. NBSP as its own run, at the end of the run before a hyperlink, at the start of the run after a hyperlink, and inside a foreign `w:ins` carrier.
1180
+ 7. Multiple replacement hunks in one paragraph, including one earlier whitespace substitution and the later reported qualifier replacement.
1181
+ 8. Leading/trailing spaces, consecutive ordinary spaces, tabs, narrow NBSP (`U+202F`), word joiner (`U+2060`), and non-breaking hyphen (`U+2011`) remain distinct unless the contract explicitly declares equivalence.
1182
+ 9. Explicit insertion affinity at both hyperlink boundaries remains authoritative.
1183
+ 10. Reject-current-author restores the exact pre-operation source, including NBSPs; selective Accept/Reject of foreign authors retains valid lifecycle behavior.
1184
+ 11. Structural validation, unique revision IDs, receipt reconciliation, hyperlink preservation, and exact current/Accept-All/Reject-current views for every successful fixture.
1185
+ 12. Forced round-trip mismatch still returns `PATCH_ROUNDTRIP_MISMATCH`, byte-exact OOXML rollback, and bounded escaped diagnostics.
1186
+ 13. CLI failure over a large document omits `documentXml`, does not echo clause text, stays below a fixed response-size ceiling, and reports `written: false`/`outputPath: null` adjacent to the actionable error.
1187
+ 14. Pre-existing validation defects are summarized separately from generated defects; zero `generatedIssues` must remain obvious.
1188
+ 15. Progressive batch with one success and one failure reports `status: "partial"` and is never marked complete; atomic mode writes nothing and rolls back exactly.
1189
+ 16. Explicit restoration in a document containing pre-existing `MISSING_SPACE_PRESERVE` errors succeeds when the restoration introduces no new issues; the original issue counts remain reported.
1190
+ 17. Restoration that itself emits one missing `xml:space="preserve"` fails with `GENERATED_OOXML_INVALID` even when identical baseline errors already exist.
1191
+ 18. Duplicate baseline issues are compared by multiplicity: N baseline occurrences plus one generated occurrence yields exactly one generated issue.
1192
+ 19. Baseline issue removed in one location and reintroduced in a newly authored paragraph is still detected by mutation-envelope validation rather than hidden by equal aggregate counts.
1193
+ 20. Pre-existing document-level revision warnings remain visible but do not block a structurally valid restoration; new duplicate IDs, nested revisions, duplicated anchors, and unsafe restoration shapes still fail.
1194
+ 21. Package-facade fixture with a pre-existing commentsExtended content-type defect either repairs that defect through normal packaging or preserves it as a baseline issue without attributing it to the restoration; any new package defect fails.
1195
+ 22. A synthetic deleted restrictions paragraph restores at its original structural location with adjusted `(i)`–`(vi)` text, one new sibling paragraph, distinct paragraph identity, content/paragraph-mark revisions, exact six-way lifecycle behavior, and no numbering drift.
1196
+ 23. Tests assert that restore failure never invokes or reports a fallback insertion after another paragraph. A success narrative is supported only by an applied result, committed receipt, `written: true`, and a real output path.
1197
+ 24. Exact reported single- and double-newline paragraph expansion fixtures reproduce `DUPLICATE_REVISION_ID` from an anchor containing `w:pPrChange` and/or `w:rPrChange`, then prove all generated IDs are unique.
1198
+ 25. Property-clone matrix: clean `pPr`/`rPr`, `pPrChange` only, `rPrChange` only, both, nested formatting changes, and anchor content already inside a same-author or foreign `w:ins`.
1199
+ 26. Insert one, two, and three adjacent plain paragraphs; assert unique content and paragraph-mark revision IDs, fresh paragraph IDs, committed receipt reconciliation, and exact paragraph order.
1200
+ 27. Repeat the expansion through structured Markdown heading, plain adjacency, list adjacency, explicit range, low-level runner, package facade, and CLI routes that share paragraph builders.
1201
+ 28. A deliberately injected cloned revision ID is caught before commit: per-operation status is `error`, receipt is uncommitted, atomic output is byte-exact, and no destination is written.
1202
+ 29. Package validation remains a backstop and reports the same offending ID/kinds if the operation-level guard is deliberately bypassed in a test harness.
1203
+ 30. Shape oracle catches a syntactically valid operation that replaces anchor text instead of inserting siblings, even when accepted text contains all requested words.
1204
+ 31. Shape oracle catches the `8.4 Changes; No Waiver` heading/body being appended inline to an earlier subsection rather than restored as two standalone paragraphs.
1205
+ 32. Exact 8.4 range restoration: heading remains `8.4 Changes; No Waiver`; body retains its original substance plus an email-sufficiency adjustment; both follow their foreign-deleted sources per WP09e; paragraph identities and six lifecycle outcomes are exact.
1206
+ 33. Exact 3.1/3.2 neighborhood regression: restoring 3.2 cannot replace, delete, concatenate with, or otherwise mutate the surviving `3.1 Restricted Use` paragraph.
1207
+ 34. Minimized Word Oracle A: split deletion IDs/authors/dates, sibling insertion placement, no nested revision, no inserted paragraph mark, exact `pa|REVIEWER B INSERTION|y` rejected-author view.
1208
+ 35. Oracle A at deletion start, end, run boundary, formatted-run boundary, and across multiple runs; repeated anchor text must require an occurrence/offset discriminator.
1209
+ 36. Oracle A preserves `w:lastRenderedPageBreak`, bold/underline runs, NBSPs, tabs, hyperlinks, and field boundaries or fails closed with a specific unsupported-boundary code.
1210
+ 37. Oracle A six-way lifecycle matrix is compared with a Word Desktop accepted/rejected oracle; Reject B reconstructs the original split deletion semantically and Reject A restores original text with B at the exact offset.
1211
+ 38. Generic non-explicit insertion into an empty foreign-deleted paragraph remains refused, proving WP09e does not weaken WP08a's ambiguity guard.
1212
+ 39. `validateRedlineOoxml` accepts the minimized Word-authored Oracle A structure without `FOREIGN_PARAGRAPH_MARK_DELETION` as an error; any informational warning must identify it as dependent inline content, not corruption.
1213
+ 40. Minimized Word Oracle B: two inserted paragraphs appear after the untouched deleted 8.2 heading/body and before deleted 8.3, with fresh paragraph IDs and distinct paragraph/content revision IDs.
1214
+ 41. Oracle B heading preserves effective bold formatting without requiring Word's incidental `rPrChange` history; if property changes are emitted, synthetic IDs 720–726 are modeled as seven distinct revision identities.
1215
+ 42. Combined Oracle A + B fixture checks forward-merge destination and exact section paragraph vectors under all selective lifecycle resolutions, including whether accepted inline text joins the next surviving paragraph exactly as Word does.
1216
+ 43. Restoration idempotency and changed reapplication operate on the post-source block; they neither prepend a second block nor mistake the original deleted paragraphs for the restoration.
1217
+ 44. Existing pre-source v0.5.3 restoration output is detected deliberately: migrate/reapply only under explicit policy, otherwise return a structured placement-version diagnostic rather than duplicating it.
1218
+
1219
+ #### Planned Implementation Areas
1220
+
1221
+ * `core/paragraph-targeting.js`
1222
+ - Extend `resolveTargetParagraph` results with exact canonical source text and an explicit resolution/match mode without changing strict identity checks.
1223
+ - Keep `normalizeWhitespaceForTargeting` confined to candidate comparison; do not reuse its output as mutation text.
1224
+ - Add strict rejected-view deleted-text anchor/offset resolution for Word-native deletion-carrier insertion.
1225
+ * `core/paragraph-revision-safety.js`
1226
+ - Separate ambiguous same-paragraph resurrection from explicitly anchored inline insertion inside a foreign deletion carrier.
1227
+ * `services/document-operation-mutations.js`
1228
+ - Update `applyToParagraphByExactText` to pass exact resolved source text and space-equivalence diagnostics into the engine and receipt path.
1229
+ - Update `verifyParagraphRestorationLifecycle` and `restoreDeletedParagraphByExactText` to use baseline-delta plus mutation-envelope validation while preserving the current/Accept-All/Reject-All oracle and rollback.
1230
+ * `core/validation-delta.js` (NEW, or an equivalent shared validation helper)
1231
+ - Centralize stable issue signatures, multiset subtraction, issue summaries, and generated-versus-baseline classification so restoration, package, and CLI paths cannot drift.
1232
+ * `core/revision-cloning.js`
1233
+ - Generalize revision-ID refresh/sanitization beyond `w:rPrChange`, or provide separate effective-property clone helpers that deliberately remove historical change elements.
1234
+ * `core/redline-validation.js`
1235
+ - Expose duplicate-ID details sufficient to identify the repeated ID and owning element kinds for operation-level diagnostics.
1236
+ * `pipeline/diff-engine.js`
1237
+ - Add or adjust deterministic replacement-hunk normalization for exact whitespace substitutions and repeated-token alignment.
1238
+ * `engine/surgical-mode.js`
1239
+ - Add a pre-mutation diff replay assertion against exact source/modified text and rebuild live spans between mutation-dependent replacement hunks where required.
1240
+ * `engine/surgical-diff-application.js`
1241
+ - Correct deletion/insertion anchors at run and hyperlink boundaries; make anchor consumption single-use and connected-node checked.
1242
+ * `engine/surgical-run-splitting.js`
1243
+ - Extend carrier splitting to `w:del` with fresh trailing IDs and preserved foreign metadata/run pieces.
1244
+ * `engine/oxml-engine.js`
1245
+ - Preserve the exact final accepted-view oracle and enrich whitespace mismatch metadata without leaking the whole payload.
1246
+ * `services/receipt-collector.js` and operation result types
1247
+ - Report space-equivalent resolution and exact source-character diagnostics in a stable structured form if the data belongs in durable receipts.
1248
+ - Reconcile every revision-bearing element emitted by paragraph expansion and refuse duplicate/unreported cloned IDs before commit.
1249
+ * `services/document-operation-contract.js`, `docs/schemas/document-operations.schema.json`, and operation declarations
1250
+ - Publish the explicit rejected-view insertion anchor/offset contract and reject ambiguous combinations.
1251
+ * `node/cli.js`
1252
+ - Add a CLI projection that removes `documentXml` and other large internal payloads, summarizes baseline issues, and preserves decisive write/error fields.
1253
+ * `node/docx-document.js`
1254
+ - Enforce baseline-versus-generated issue separation for both revision OOXML and package validation; never reject unchanged legacy defects as newly authored output.
1255
+ * `index.d.ts`, `services/standalone-operation-runner.d.ts`, and `node/index.d.ts`
1256
+ - Publish any new match-mode, mismatch-code-point, validation-summary, or completion fields.
1257
+ * `tests/cross_author_slicing_whitespace_alignment_tests.mjs` (NEW)
1258
+ - Own the recovered fixture, exact low-level/runner/facade lifecycle assertions, and the boundary matrix.
1259
+ * `tests/paragraph_level_cross_author_restoration_tests.mjs`
1260
+ - Add dirty-baseline restoration, generated-issue, multiplicity, package-facade, original-placement, synthetic 8.4 range restoration, 3.1/3.2 neighborhood, and no-fallback coverage from the second and third reports.
1261
+ * `tests/paragraph_expansion_revision_identity_tests.mjs` (NEW)
1262
+ - Own newline/Markdown/list expansion, cloned property-history, global uniqueness, receipts, rollback, lifecycle vectors, and shape-oracle coverage.
1263
+ * `tests/word_deleted_section_edit_oracle_tests.mjs` (NEW)
1264
+ - Own minimized synthetic 8.1 deletion-carrier slicing and 8.2 post-source paragraph restoration fixtures, Word lifecycle differentials, validation routing, and combined forward-merge behavior.
1265
+ * `tests/agent_cli_tests.mjs`
1266
+ - Add bounded failure-output, validation-summary, committed-success-proof, and no-operation-fallback assertions.
1267
+ * `README.md`, `AGENTS.md`, `CHANGELOG.md`, and this plan
1268
+ - Document invisible-whitespace diagnostics, CLI output guarantees, and final files/functions changed during implementation.
1269
+
1270
+ #### Standalone implementation handoff
1271
+
1272
+ This section is the implementation brief for an agent that has none of the preceding conversation. The user-supplied documents and transcripts are evidence only: do not check them in, quote their parties, people, commercial terms, paragraph IDs, URLs, or exact clause language. Every permanent fixture must use the synthetic Reviewer A/Reviewer B examples below.
1273
+
1274
+ Implement WP09 in this order because each stage creates the safety net needed by the next:
1275
+
1276
+ 1. **WP09c — baseline-delta validation.** Capture the source validation inventory before mutation; compare the result by stable issue signature; permit unchanged pre-existing issues outside the mutation envelope; reject any new or worsened issue. Keep exact current-view, Accept-All, and Reject-All text checks. This unblocks safe restoration in imperfect real documents.
1277
+ 2. **WP09d — identity sanitation.** Centralize cloning of revision-bearing `pPr`/`rPr`; either remove stale history that is not semantically part of the new paragraph or reallocate every cloned `w:id`. Scan the entire document, not only the target paragraph, before committing. Receipts must enumerate all newly allocated content, paragraph-mark, and property-change revision IDs.
1278
+ 3. **WP09e — explicit rejected-view operations.** Add a distinct operation contract for editing content whose current/accepted view is empty. Do not relax generic current-view targeting. Slice a foreign deletion carrier for inline insertion and place restored paragraph ranges immediately after the deleted source block, matching the Word Desktop oracle below.
1279
+ 4. **WP09a — whitespace-aware alignment.** Preserve exact replacement text, but allow ordinary-space/NBSP equivalence only while resolving unchanged source anchors. Keep hyperlink elements and relationship IDs in place. Require exact requested accepted text after mutation.
1280
+ 5. **WP09b — compact CLI evidence.** Suppress full document XML from normal CLI JSON. Report per-operation status/error, `written`, `outputPath`, committed receipts, validation summary, and bounded diagnostics.
1281
+
1282
+ Trace `executeCli` -> `openDocx(...).applyOperations` -> `applyOperationsToDocumentXml` -> `applyOperationToDocumentXml` -> paragraph/range mutation helpers -> `applyRedlineToOxml` -> `applySurgicalMode` -> `processDelete`/`processInsert`. Preserve the live-DOM savepoint and allocator rollback at every operation boundary.
1283
+
1284
+ Use these sanitized operation contracts as the target behavior (field names may be adjusted once in the schema, but must remain explicit and schema-validated):
1285
+
1286
+ ```json
1287
+ {
1288
+ "type": "insert",
1289
+ "target": {
1290
+ "exactText": "The account holder must pay each undisputed invoice.",
1291
+ "paragraphId": "A1B2C3D4",
1292
+ "revisionView": "rejected"
1293
+ },
1294
+ "anchor": { "exactText": "pay", "occurrence": 1, "offset": 2 },
1295
+ "modified": "REVIEWER B INSERTION",
1296
+ "author": "Reviewer B",
1297
+ "existingRevisions": "slice-cross-author"
1298
+ }
1299
+ ```
1300
+
1301
+ This must convert Reviewer A's single deletion carrier into sibling `del(A prefix)`, `ins(B text)`, `del(A suffix)` nodes. The trailing Reviewer A carrier gets a fresh revision ID; the insertion must not inherit a deleted paragraph mark and must not be nested inside `w:del`. Rejecting Reviewer B restores Reviewer A's original deletion-carrier text; rejecting Reviewer A exposes the original sentence plus Reviewer B's pending insertion; Accept-All retains Reviewer B's inserted text at the deletion boundary with Word-compatible paragraph merging.
1302
+
1303
+ ```json
1304
+ {
1305
+ "type": "restore",
1306
+ "target": {
1307
+ "exactText": "8.2 Usage Adjustments",
1308
+ "paragraphId": "B1C2D3E4",
1309
+ "revisionView": "rejected"
1310
+ },
1311
+ "targetEnd": {
1312
+ "exactText": "Usage above the stated threshold may be billed at the next tier.",
1313
+ "paragraphId": "B1C2D3E5",
1314
+ "revisionView": "rejected"
1315
+ },
1316
+ "modified": "8.2 Usage Adjustments\n\nUsage above the stated threshold may be billed at the next tier.",
1317
+ "author": "Reviewer B"
1318
+ }
1319
+ ```
1320
+
1321
+ The restored heading and body must be newly inserted sibling paragraphs immediately **after** the two-paragraph Reviewer A deletion block and before the following source paragraph. Each inserted paragraph needs a fresh paragraph identity, a Reviewer B paragraph-mark insertion, a Reviewer B content insertion, sanitized properties, and globally unique IDs. The original deleted block remains unchanged outside allocator-neutral serialization. A repeated request is an idempotent no-op or a specific duplicate-restoration error; it must never add a second copy.
1322
+
1323
+ Required red/green fixtures are: (a) two hyperlinks separated by NBSP/ordinary spaces with a small insertion; (b) a source document containing an unrelated missing-`xml:space` warning plus a safe restoration that adds no issue; (c) newline expansion from a paragraph whose `pPr` and first run contain prior property-change IDs; (d) the inline deletion-carrier oracle above; and (e) the two-paragraph post-source restoration oracle above. For each, assert structural order, authorship, global ID uniqueness, exact accepted and rejected paragraph vectors, selective Accept/Reject outcomes, receipt reconciliation, atomic rollback, and package validation. Expected failures must use a specific code such as `AMBIGUOUS_TARGET`, `UNSAFE_REVISION_NESTING`, `UNSAFE_PARAGRAPH_BOUNDARY`, or a new rejected-view anchor error—not a generic exception.
1324
+
1325
+ The checked-in characterization suite `tests/word_deleted_section_edit_oracle_tests.mjs` records the Word-authored shapes without private source text. It is an oracle scaffold, not proof that WP09e mutation support already exists; implementation tests must additionally create those shapes through the public runner and package facade.
1326
+
1327
+ #### Exit Criteria
1328
+
1329
+ WP09 is complete only when all reported workflows and both sanitized Word Desktop oracles succeed through the real package facade: the Service Policy edit must require no manual NBSP discovery, produce exact requested accepted text, preserve both hyperlinks, and reject back to the exact source; explicit paragraph restorations must tolerate unrelated baseline defects, introduce zero new validation issues, follow Word's post-source placement, and satisfy all selective lifecycle outcomes; rejected-view insertion inside a foreign deletion must split the carrier exactly like Word; and multiline paragraph expansion must allocate globally unique revision IDs and satisfy exact paragraph-shape oracles. The synthetic 8.4 pair must exist as standalone paragraphs in its original location, while the neighboring 3.1 paragraph remains unchanged. The CLI must provide a compact success or failure record that an agent can verify from `status`, every `results[i].status`, committed receipts, `written`, and `outputPath` without receiving full document XML.
1330
+
1331
+ Final implementation notes must replace the planned file/function list above with the actual touched files and functions, record any deliberately deferred cases, and include focused tests, full serial `npm test`, lint, type declarations, build, schema parsing if changed, and `git diff --check`.
1332
+
512
1333
  ---
513
1334
 
514
1335
  ## 6. Comprehensive Verification Plan (Synthetic & Real Test Series)
@@ -517,7 +1338,7 @@ To prove correctness across all layers of the stack, this plan defines two compr
517
1338
  1. **Synthetic Unit & Boundary Suites** (`tests/cross_author_slicing_synthetic_tests.mjs`): Isolated OOXML fixtures testing edge cases, boundary alignments, and lifecycle mechanics.
518
1339
  2. **Checked-In Word Package Differential Suite** (`tests/cross_author_slicing_real_tests.mjs`): End-to-end strict-facade replay against actual DOCX packages created by Microsoft Word Desktop, including package validation and lifecycle comparison with Word-generated oracles.
519
1340
 
520
- The synthetic matrix below is fully automated. The checked-in package suite is also fully automated as PKG-01 through PKG-06. REAL-01 through REAL-05 remain an environment/input-dependent acceptance matrix for the named private/corpus documents and live Word COM/visual checks.
1341
+ The synthetic matrix below is fully automated. The checked-in package suite is also fully automated as PKG-01 through PKG-06. REAL-01 through REAL-05 remain an environment/input-dependent acceptance matrix using sanitized documents and live Word COM/visual checks.
521
1342
 
522
1343
  ---
523
1344
 
@@ -525,21 +1346,21 @@ The synthetic matrix below is fully automated. The checked-in package suite is a
525
1346
 
526
1347
  | ID | Test Case Name | Input Structure | Operation (Author B) | Expected OOXML Structure | Invariant Assertions |
527
1348
  |:---|:---|:---|:---|:---|:---|
528
- | **SYN-01** | Pure Interior Insertion | `<w:ins author="Barry">amended by this Agreement</w:ins>` | Insert `"MASTER "` before `"Agreement"` | `[ins(Barry): "amended by this "][ins(Anson): "MASTER "][ins(Barry): "Agreement"]` | 3 sibling `<w:ins>` nodes; no `NESTED_REVISION`; unique IDs allocated for `ins(Anson)` and trailing `ins(Barry)`. |
529
- | **SYN-02** | Pure Interior Deletion | `<w:ins author="Barry">The Services will process the Input to generate outputs</w:ins>` | Delete `"generate "` | `<w:ins author="Barry">...<w:del author="Anson">generate </w:del>...</w:ins>` | One Barry carrier remains; nested `w:del` uses `<w:delText>` and is authored by Anson. |
530
- | **SYN-03** | Boundary Deletion at Insertion Start | `<w:ins author="Barry">Notwithstanding the foregoing, the NDA remains</w:ins>` | Delete `"Notwithstanding the foregoing, "` | `<w:ins author="Barry"><w:del author="Anson">Notwithstanding...</w:del>the NDA remains</w:ins>` | Nested deletion is the carrier's first content node; Barry metadata remains intact. |
531
- | **SYN-04** | Boundary Deletion at Insertion End | `<w:ins author="Barry">subject to Section 2.8 and applicable law</w:ins>` | Delete `" and applicable law"` | `<w:ins author="Barry">subject...<w:del author="Anson"> and applicable law</w:del></w:ins>` | Nested deletion is the carrier's final content node. |
532
- | **SYN-05** | Complete Deletion of Pending Insertion Text | `<w:ins author="Barry">Obsolete clause insertion.</w:ins>` | Delete entire string `"Obsolete clause insertion."` | `<w:ins author="Barry"><w:del author="Anson">Obsolete clause insertion.</w:del></w:ins>` | Barry's carrier remains so rejecting Barry still cascades away Anson's dependent deletion. |
533
- | **SYN-06** | Straddle Deletion (Baseline to Insertion) | `<w:r><w:t>Baseline start </w:t></w:r><w:ins author="Barry">inserted finish</w:ins>` | Delete `"start inserted"` | `[r: "Baseline "][del(Anson): "start "][ins(Barry): [del(Anson): "inserted"] " finish"]` | Top-level and nested deletion portions remain structurally separate, matching Word Desktop. |
534
- | **SYN-07** | Straddle Deletion (Insertion to Baseline) | `<w:ins author="Barry">Inserted start</w:ins><w:r><w:t> baseline finish</w:t></w:r>` | Delete `"start baseline"` | `[ins(Barry): "Inserted " [del(Anson): "start"]][del(Anson): " baseline"][r: " finish"]` | Nested and top-level deletion portions preserve their respective carrier contexts. |
535
- | **SYN-08** | Multi-Insertion Straddle (Author A to Author C) | `<w:ins author="Barry">Barry text </w:ins><w:ins author="Carl">Carl text</w:ins>` | Author B deletes `"text Carl"` | `[ins(Barry): "Barry " [del(Anson): "text "]][ins(Carl): [del(Anson): "Carl"] " text"]` | Each foreign carrier owns its nested deletion portion; neither carrier is sliced for deletion. |
536
- | **SYN-09** | Multi-Run Formatting Preservation | `<w:ins author="Barry"><w:r><w:rPr><w:b/></w:rPr><w:t>Bold text </w:t></w:r><w:r><w:t>plain text</w:t></w:r></w:ins>` | Delete `"text plain"` | Barry's `<w:ins>` remains intact around a nested `<w:del>` containing a bold run for `"text "` and a plain run for `"plain"`. | Exact run-level formatting is preserved inside `<w:delText>` and unaffected insertion runs. |
537
- | **SYN-10** | Paired Replacement Event inside Insertion | `<w:ins author="Barry">process the Input to generate outputs</w:ins>` | Replace `"generate"` with `"synthesize"` (`pairReplacements: true`) | `[ins(Barry): prefix + nested del(Anson)][ins(Anson): "synthesize"][ins(Barry): suffix]` | Deletion and insertion share timestamp; only the insertion requires carrier splitting/hoisting. |
538
- | **SYN-11** | 3-Author Stacked Deletions | Output of **SYN-02** | Author C ("Davis, Chris") deletes `"process"` in Barry's carrier | `<w:ins author="Barry">...<w:del author="Davis">process</w:del>...<w:del author="Anson">generate</w:del>...</w:ins>` | Multiple distinct reviewer deletions coexist safely inside the same foreign insertion. |
539
- | **SYN-12a** | Lifecycle Oracle: Accept All | Output of **SYN-02** | `acceptTrackedChanges({ allAuthors: true })` | Clean baseline string: `"The Services will process the Input to outputs"` | All `<w:del>` removed, all `<w:ins>` unwrapped; zero revision tags remaining. |
540
- | **SYN-12b** | Lifecycle Oracle: Accept Author A Only | Output of **SYN-02** | `acceptTrackedChanges({ author: 'Barry' })` | Barry's text becomes baseline; Anson's `<w:del>` remains pending against the baseline. | Anson's `<w:del>` remains intact and reviewable. |
541
- | **SYN-12c** | Lifecycle Oracle: Reject Author A Only | Output of **SYN-02** | `rejectTrackedChanges({ author: 'Barry' })` | Barry's insertion is deleted from the document. Anson's internal `<w:del>` is cascaded and pruned. | Prevents orphaned deletion of text that was rejected from ever existing. |
542
- | **SYN-12d** | Lifecycle Oracle: Reject Author B Only | Output of **SYN-02** | `rejectTrackedChanges({ author: 'Anson' })` | Anson's nested `<w:del>` is unwrapped back into regular runs within Barry's `<w:ins>`. | Full restoration of Barry's original insertion. |
1349
+ | **SYN-01** | Pure Interior Insertion | `<w:ins author="Reviewer A">amended by this agreement</w:ins>` | Reviewer B inserts `"MASTER "` before `"agreement"` | `[ins(A): "amended by this "][ins(B): "MASTER "][ins(A): "agreement"]` | 3 sibling `<w:ins>` nodes; no `NESTED_REVISION`; unique IDs for Reviewer B and the trailing Reviewer A carrier. |
1350
+ | **SYN-02** | Pure Interior Deletion | `<w:ins author="Reviewer A">The service processes input to produce output</w:ins>` | Reviewer B deletes `"produce "` | `<w:ins author="Reviewer A">...<w:del author="Reviewer B">produce </w:del>...</w:ins>` | One Reviewer A carrier remains; nested `w:del` uses `<w:delText>` and is authored by Reviewer B. |
1351
+ | **SYN-03** | Boundary Deletion at Insertion Start | `<w:ins author="Reviewer A">Subject to the exception, the policy remains</w:ins>` | Delete `"Subject to the exception, "` | `<w:ins author="Reviewer A"><w:del author="Reviewer B">Subject...</w:del>the policy remains</w:ins>` | Nested deletion is the carrier's first content node; Reviewer A metadata remains intact. |
1352
+ | **SYN-04** | Boundary Deletion at Insertion End | `<w:ins author="Reviewer A">subject to Section 2 and applicable rules</w:ins>` | Delete `" and applicable rules"` | `<w:ins author="Reviewer A">subject...<w:del author="Reviewer B"> and applicable rules</w:del></w:ins>` | Nested deletion is the carrier's final content node. |
1353
+ | **SYN-05** | Complete Deletion of Pending Insertion Text | `<w:ins author="Reviewer A">Obsolete inserted clause.</w:ins>` | Delete the entire inserted string | `<w:ins author="Reviewer A"><w:del author="Reviewer B">Obsolete inserted clause.</w:del></w:ins>` | Reviewer A's carrier remains so rejecting A still cascades away B's dependent deletion. |
1354
+ | **SYN-06** | Straddle Deletion (Baseline to Insertion) | `<w:r><w:t>Baseline start </w:t></w:r><w:ins author="Reviewer A">inserted finish</w:ins>` | Delete `"start inserted"` | `[r: "Baseline "][del(B): "start "][ins(A): [del(B): "inserted"] " finish"]` | Top-level and nested deletion portions remain structurally separate. |
1355
+ | **SYN-07** | Straddle Deletion (Insertion to Baseline) | `<w:ins author="Reviewer A">Inserted start</w:ins><w:r><w:t> baseline finish</w:t></w:r>` | Delete `"start baseline"` | `[ins(A): "Inserted " [del(B): "start"]][del(B): " baseline"][r: " finish"]` | Nested and top-level deletion portions preserve their respective carrier contexts. |
1356
+ | **SYN-08** | Multi-Insertion Straddle | `<w:ins author="Reviewer A">Alpha text </w:ins><w:ins author="Reviewer C">Gamma text</w:ins>` | Reviewer B deletes `"text Gamma"` | `[ins(A): "Alpha " [del(B): "text "]][ins(C): [del(B): "Gamma"] " text"]` | Each foreign carrier owns its nested deletion portion. |
1357
+ | **SYN-09** | Multi-Run Formatting Preservation | `<w:ins author="Reviewer A"><w:r><w:rPr><w:b/></w:rPr><w:t>Bold text </w:t></w:r><w:r><w:t>plain text</w:t></w:r></w:ins>` | Delete `"text plain"` | Reviewer A's `<w:ins>` remains intact around Reviewer B's nested deletion with bold and plain runs. | Exact run-level formatting is preserved inside `<w:delText>` and unaffected insertion runs. |
1358
+ | **SYN-10** | Paired Replacement Event inside Insertion | `<w:ins author="Reviewer A">process input to produce output</w:ins>` | Replace `"produce"` with `"create"` | `[ins(A): prefix + nested del(B)][ins(B): "create"][ins(A): suffix]` | Deletion and insertion share a timestamp; only the insertion requires carrier splitting/hoisting. |
1359
+ | **SYN-11** | 3-Author Stacked Deletions | Output of **SYN-02** | Reviewer C deletes `"processes"` in Reviewer A's carrier | `<w:ins author="Reviewer A">...<w:del author="Reviewer C">processes</w:del>...<w:del author="Reviewer B">produce</w:del>...</w:ins>` | Multiple reviewer deletions coexist safely inside the same foreign insertion. |
1360
+ | **SYN-12a** | Lifecycle Oracle: Accept All | Output of **SYN-02** | `acceptTrackedChanges({ allAuthors: true })` | Clean baseline string: `"The service processes input to output"` | All `<w:del>` removed, all `<w:ins>` unwrapped; zero revision tags remaining. |
1361
+ | **SYN-12b** | Lifecycle Oracle: Accept Reviewer A Only | Output of **SYN-02** | `acceptTrackedChanges({ author: 'Reviewer A' })` | Reviewer A's text becomes baseline; Reviewer B's `<w:del>` remains pending. | Reviewer B's deletion remains intact and reviewable. |
1362
+ | **SYN-12c** | Lifecycle Oracle: Reject Reviewer A Only | Output of **SYN-02** | `rejectTrackedChanges({ author: 'Reviewer A' })` | Reviewer A's insertion is removed with Reviewer B's dependent deletion. | Prevents an orphaned deletion of text that never became baseline. |
1363
+ | **SYN-12d** | Lifecycle Oracle: Reject Reviewer B Only | Output of **SYN-02** | `rejectTrackedChanges({ author: 'Reviewer B' })` | Reviewer B's nested deletion is unwrapped inside Reviewer A's carrier. | Full restoration of Reviewer A's original insertion. |
543
1364
 
544
1365
  ---
545
1366
 
@@ -547,29 +1368,32 @@ The synthetic matrix below is fully automated. The checked-in package suite is a
547
1368
 
548
1369
  | ID | Scenario & Source Document | Workflow & Operations | Expected Real-World Behavior | Verification Oracle |
549
1370
  |:---|:---|:---|:---|:---|
550
- | **REAL-01** | **Salary.com Agreement: The Motivating AI Terms Deletion**<br>Source: `agreement.docx` (Section 2.1 Customer Data, `P40`) | 1. Document contains Barry Lai's pending insertion (`P40`, ID `45`).<br>2. Anson Lai runs operation to delete `"generate"` from `"to generate outputs"`.<br>3. `--existing-revisions slice-cross-author`. | Batch commits with `status: "ok"`, `written: true`.<br>Barry's insertion remains intact and contains Anson's visible, attributed nested deletion.<br>No `COMMENTED_CONTENT_DELETE` (since comment 144 is on Section 2.8, not 2.1). | Output file validated via `validateDocxPackage`. Revisions inspectable via `docx-redline inspect`. |
551
- | **REAL-02** | **Salary.com Agreement: §14.1 NDA Carve-Out**<br>Source: `agreement.docx` (Section 14.1 Entire Agreement, `P131`) | 1. Barry has pending insertion of the amendment sentence.<br>2. Anson inserts August 25, 2026 NDA carve-out in the middle.<br>3. `--existing-revisions slice-cross-author`. | Barry's insertion remains visibly attributed to Barry (not baked into baseline).<br>Anson's carve-out sits as an adjacent/spliced insertion attributed to Anson. | `extract` and `inspect` show both `Lai, Barry` and `Lai, Anson` in `revisionAuthors`. |
552
- | **REAL-03** | **SuperDoc Corpus: Interagency Multi-Counsel Negotiation**<br>Source: Corpus ID `c5bb43ede5...` (Joint Communications Protocol) | Round 1: BCHD Lead Agency Counsel applies insertions to Sections 3.2 and 4.1.<br>Round 2: MOHS Counterparty Counsel edits directly inside BCHD's insertions.<br>Round 3: Third Reviewer applies further modifications. | 3 distinct institutional authors with overlapping and sliced edits commit across rounds without merge corruption or loss of attribution. | Document hash checks; zero schema errors across all 3 rounds. |
553
- | **REAL-04** | **Desktop Word 365 COM Automation Oracle**<br>Execution on Windows runner via native Word | Open the output DOCX files from **REAL-01**, **REAL-02**, and **REAL-03** via Windows COM automation (`word-client.mjs`). | 1. Word opens each file with **0 repair prompts** / corruption dialogs.<br>2. `Document.Revisions.Count` matches exact receipt counts.<br>3. `Document.Revisions.AcceptAll()` in Word matches engine `acceptAll()` bit-for-bit.<br>4. `Document.Revisions.RejectAll()` in Word matches engine `rejectAll()` bit-for-bit. | COM automation script asserts identical string contents after Word native Accept/Reject. |
554
- | **REAL-05** | **Word Visual Rendering & PDF Export Proof**<br>Visual Evidence Pipeline | Export pages of **REAL-01** and **REAL-02** to PDF via Word COM `ExportAsFixedFormat`. Convert PDF pages to PNG. | 1. Deletions show strikethrough in Anson's reviewer color.<br>2. Insertions show underline in Barry's reviewer color.<br>3. Word Reviewing Pane displays balloons for both authors correctly without overlap or misaligned leader lines. | Visual evidence artifact generated in `tests/visual-evidence/` for human review sign-off. |
1371
+ | **REAL-01** | **Synthetic Service Agreement: Pending Feature Clause**<br>Source: sanitized Word-authored package | 1. Reviewer A has a pending insertion.<br>2. Reviewer B deletes one interior verb from it.<br>3. Apply with `--existing-revisions slice-cross-author`. | Batch commits with `status: "ok"`, `written: true`.<br>Reviewer A's insertion remains intact and contains Reviewer B's visible, attributed nested deletion. | Output validates via `validateDocxPackage`; `inspect` reports both generic reviewers. |
1372
+ | **REAL-02** | **Synthetic Master Agreement: Policy Carve-Out**<br>Source: sanitized Word-authored package | 1. Reviewer A has a pending amendment sentence.<br>2. Reviewer B inserts a policy carve-out in its middle.<br>3. Apply with `--existing-revisions slice-cross-author`. | Reviewer A's insertion remains attributed to Reviewer A rather than becoming baseline; Reviewer B's carve-out is an adjacent/spliced insertion. | `extract` and `inspect` report Reviewer A and Reviewer B without private source metadata. |
1373
+ | **REAL-03** | **Synthetic Three-Reviewer Negotiation**<br>Source: sanitized multi-round corpus package | Round 1: Reviewer A inserts text in two clauses.<br>Round 2: Reviewer B edits inside those insertions.<br>Round 3: Reviewer C applies further modifications. | Three authors' overlapping and sliced edits commit without merge corruption or lost attribution. | Document hash checks; zero new schema errors across all rounds. |
1374
+ | **REAL-04** | **Desktop Word COM Automation Oracle**<br>Execution on a Windows runner with Word | Open the sanitized outputs from **REAL-01**, **REAL-02**, and **REAL-03** through the native Word automation client. | 1. Word opens each file with no repair prompt.<br>2. `Document.Revisions.Count` matches committed receipt counts.<br>3. Word Accept-All and Reject-All text matches the engine lifecycle results. | Automation asserts identical paragraph vectors after native and engine lifecycle operations. |
1375
+ | **REAL-05** | **Word Visual Rendering and PDF Proof**<br>Sanitized evidence pipeline | Export **REAL-01** and **REAL-02** to PDF through Word, then render the affected pages. | Reviewer B's deletions and Reviewer A's insertions have distinct reviewer colors; the Reviewing Pane attributes both correctly without overlapping or misplaced balloons. | Sanitized visual artifacts are generated outside the repository unless explicitly approved for check-in. |
555
1376
 
556
1377
  ---
557
1378
 
558
1379
  ### 6.3 Test Execution Matrix
559
1380
 
560
1381
  ```bash
561
- # 1. Run synthetic unit & boundary suite
1382
+ # 1. Run the sanitized Word deleted-section characterization oracle
1383
+ node tests/word_deleted_section_edit_oracle_tests.mjs
1384
+
1385
+ # 2. Run synthetic unit & boundary suite
562
1386
  node tests/cross_author_slicing_synthetic_tests.mjs
563
1387
 
564
- # 2. Run checked-in Word DOCX package differential suite (PKG-01..06)
1388
+ # 3. Run checked-in Word DOCX package differential suite (PKG-01..06)
565
1389
  node tests/cross_author_slicing_real_tests.mjs
566
1390
 
567
- # 3. Optional external acceptance: Word Desktop COM differential oracle (Windows desktop)
1391
+ # 4. Optional external acceptance: Word Desktop COM differential oracle (Windows desktop)
568
1392
  npm run test:word
569
1393
 
570
- # 4. Verify existing mode matrix remains 100% backward compatible
1394
+ # 5. Verify existing mode matrix remains 100% backward compatible
571
1395
  node tests/existing_revisions_modes_matrix_tests.mjs
572
1396
 
573
- # 5. Full regression check
1397
+ # 6. Full regression check
574
1398
  npm test
575
1399
  ```