@ansonlai/docx-redline-js 0.5.3 → 0.6.0
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.
- package/AGENTS.md +82 -667
- package/ARCHITECTURE.md +51 -4
- package/CHANGELOG.md +11 -0
- package/README.md +176 -39
- package/core/paragraph-revision-safety.js +10 -8
- package/core/paragraph-targeting.js +14 -2
- package/core/redline-validation.js +7 -4
- package/core/revision-cloning.js +21 -0
- package/core/validation-delta.js +23 -0
- package/dist/docx-redline-js.esm.js +275 -45
- package/dist/docx-redline-js.esm.js.map +3 -3
- package/dist/docx-redline-js.esm.min.js +82 -82
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/docs/AGENT_FAST_START.md +59 -0
- package/docs/AGENT_KNOWLEDGE_BASE.md +868 -0
- package/docs/TESTING.md +20 -1
- package/docs/schemas/document-operations.schema.json +16 -2
- package/docs/validation-reports/2026-09-12-agent-protocol-rollout.md +82 -0
- package/engine/oxml-engine.js +80 -13
- package/engine/run-builders.js +5 -15
- package/engine/surgical-mode.js +148 -3
- package/engine/surgical-run-splitting.js +19 -7
- package/engine/surgical-spans.js +2 -1
- package/index.d.ts +17 -1
- package/node/cli.js +235 -36
- package/node/docx-document.js +137 -83
- package/node/index.d.ts +6 -2
- package/package.json +10 -3
- package/pipeline/diff-engine.js +15 -0
- package/scripts/generate-cross-author-slicing-fixtures.ps1 +25 -25
- package/services/batch-operation-orchestrator.js +215 -120
- package/services/document-inspection.js +5 -3
- package/services/document-operation-applier.js +99 -36
- package/services/document-operation-contract.js +50 -6
- package/services/document-operation-mutations.js +404 -41
- package/services/document-operation-session.js +4 -0
- package/services/error-recovery.js +174 -0
- package/services/operation-batch-compiler.js +394 -0
- package/services/operation-preflight.js +91 -72
- package/services/standalone-operation-runner.d.ts +35 -1
- package/docs/plans/2026-09-05-structural-revisions-and-fidelity-oracles.md +0 -1669
- package/docs/plans/2026-09-08-cross-author-revision-slicing.md +0 -856
- package/docs/plans/completed/2026-03-01-release-0.1.4-design.md +0 -33
- package/docs/plans/completed/2026-03-01-release-0.1.4.md +0 -110
- package/docs/plans/completed/2026-05-31-architectural changes.md +0 -593
- package/docs/plans/completed/2026-08-02-reliability-improvements.md +0 -1155
- package/docs/plans/completed/2026-08-30-reliability-testing-improvements.md +0 -488
- package/docs/plans/completed/2026-09-01-performance-and-complexity-reduction.md +0 -669
- package/docs/plans/completed/2026-09-03-agent-friendly-document-workflows.md +0 -427
- package/docs/plans/completed/2026-09-04-comment-anchor-and-cli-reliability.md +0 -519
- package/docs/plans/completed/PERFORMANCE-CONSOLIDATION.md +0 -69
- package/docs/plans/completed/structural-revision-capability-matrix.md +0 -115
- package/docs/test-comparison-dashboard.html +0 -4338
- package/docs/validation-reports/2026-08-30-phase-1-word-visual-preflight.md +0 -22
- package/docs/validation-reports/2026-08-30-phase-2-word-visual-preflight.md +0 -24
- package/docs/validation-reports/2026-08-30-phase-3-coverage.md +0 -73
- package/docs/validation-reports/2026-09-02-multilevel-bullets-visual-review.md +0 -82
- package/docs/validation-reports/2026-09-02-multimodal-visual-samples.md +0 -114
- package/docs/validation-reports/2026-09-02-visual-failures-preflight.md +0 -79
|
@@ -1,856 +0,0 @@
|
|
|
1
|
-
# Cross-Author Revision Slicing: Nested Revisions via Carrier Splitting
|
|
2
|
-
|
|
3
|
-
**Status:** Completed — WP-01 through WP-07 implemented; final automated verification complete
|
|
4
|
-
**Date:** 2026-09-08
|
|
5
|
-
**Target releases:** v0.6.0–v1.0.0
|
|
6
|
-
**Priority:** Document fidelity, multi-author contract negotiation accuracy, and strict OOXML schema compliance over single-pass simplicity.
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## 1. Executive Summary & Findings
|
|
11
|
-
|
|
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.
|
|
14
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
20
|
-
### The Underlying OOXML Mechanics
|
|
21
|
-
Empirical inspection of Microsoft Word Desktop 365 (via automated COM fixture generation in WP-01) reveals a vital asymmetry between how Word handles cross-author insertions versus deletions:
|
|
22
|
-
|
|
23
|
-
1. **Cross-Author Insertions (`insert-interior`) — Sibling Splitting**:
|
|
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
|
-
```xml
|
|
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">
|
|
28
|
-
<w:r><w:t xml:space="preserve">amended by this </w:t></w:r>
|
|
29
|
-
</w:ins>
|
|
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">
|
|
32
|
-
<w:r><w:t xml:space="preserve">MASTER </w:t></w:r>
|
|
33
|
-
</w:ins>
|
|
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">
|
|
36
|
-
<w:r><w:t>Agreement.</w:t></w:r>
|
|
37
|
-
</w:ins>
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
2. **Cross-Author Deletions (`delete-interior`) — Direct `<w:del>` Nesting inside `<w:ins>`**:
|
|
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
|
-
```xml
|
|
43
|
-
<w:ins w:id="0" w:author="Barry Lai" w:date="2026-09-08T09:29:00Z">
|
|
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">
|
|
46
|
-
<w:r w:rsidDel="00F70C09"><w:delText xml:space="preserve">generate </w:delText></w:r>
|
|
47
|
-
</w:del>
|
|
48
|
-
<w:r><w:t>outputs for Customer.</w:t></w:r>
|
|
49
|
-
</w:ins>
|
|
50
|
-
```
|
|
51
|
-
This nested structure ensures that:
|
|
52
|
-
- If Author A's insertion is rejected, the entire container—including Author B's deletion of that unaccepted text—is discarded cleanly.
|
|
53
|
-
- If Author A's insertion is accepted, Author A's text is unwrapped into the baseline while Author B's `<w:del>` remains pending against the baseline.
|
|
54
|
-
|
|
55
|
-
### Why `docx-redline-js` Currently Blocks This
|
|
56
|
-
`docx-redline-js` currently guards against this with `EXISTING_REVISIONS` / `UNSAFE_REVISION_NESTING` because:
|
|
57
|
-
1. **Container Splitting Is Unimplemented for Insertions**: In `engine/surgical-diff-application.js`, `processInsert` inserts newly created revision elements into `span.runElement.parentNode`. When that parent is `<w:ins>`, inserting `<w:ins>` directly into it produces schema-invalid nested `<w:ins><w:ins>...</w:ins></w:ins>`.
|
|
58
|
-
2. **Overzealous Validation Rule**: In `core/redline-validation.js`, an existing check unconditionally flagged any `<w:del>` inside `<w:ins>` as `NESTED_REVISION`, contradicting ECMA-376 and Microsoft Word Desktop's native behavior.
|
|
59
|
-
3. **Same-Author vs. Cross-Author Asymmetry**: The engine already has `merge-same-author`, but that strategy works by *reverting* prior revisions back to the pre-revision baseline and re-diffing against the baseline into a single revision. Applying that to another author would erase Author A's attribution.
|
|
60
|
-
|
|
61
|
-
This plan defines the architecture, mutation mechanics, lifecycle rules, and test strategy to implement **Cross-Author Revision Slicing** natively.
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## 2. Hard Invariants
|
|
66
|
-
|
|
67
|
-
1. **Strict OOXML Schema Compliance (Word-Native Hierarchy)**:
|
|
68
|
-
- `<w:del>` INSIDE `<w:ins>` IS allowed (and expected by Word Desktop and ECMA-376 `CT_RunTrackChange`).
|
|
69
|
-
- `<w:ins>` must NEVER contain `<w:ins>`, `<w:moveFrom>`, or `<w:moveTo>`.
|
|
70
|
-
- `<w:del>` must NEVER contain `<w:ins>`, `<w:del>`, `<w:moveFrom>`, or `<w:moveTo>`.
|
|
71
|
-
- All revision wrappers must be immediate children of `<w:p>`, `<w:hyperlink>`, or (for `<w:del>`) `<w:ins>`.
|
|
72
|
-
|
|
73
|
-
2. **Durable ID Allocation & Metadata Preservation**:
|
|
74
|
-
- Split fragments of Author A's `<w:ins>` MUST preserve Author A's exact author string, date, and `dateUtc`.
|
|
75
|
-
- The first fragment may retain the original `w:id` if non-conflicting, but subsequent split fragments MUST be allocated globally unique `w:id` values via the active `RevisionIdAllocator`.
|
|
76
|
-
- Author B's new revision element (`<w:del>` or `<w:ins>`) MUST be allocated its own fresh `w:id` under Author B's name and current timestamp.
|
|
77
|
-
|
|
78
|
-
3. **Multi-Author Revision Lifecycle Consistency**:
|
|
79
|
-
- **Accept All**: Removing `<w:del>` and unwrapping all `<w:ins>` must produce the identical text string as Word Desktop.
|
|
80
|
-
- **Accept Author A Only**: Author A's split `<w:ins>` elements unwrap into baseline text. Author B's `<w:del>` and `<w:ins>` remain pending against that now-baseline text.
|
|
81
|
-
- **Reject Author A Only**: Author A's `<w:ins>` elements are removed. Any `<w:del>` by Author B situated inside Author A's insertion MUST be removed as well (you cannot delete text that was rejected from ever entering the document). Any `<w:ins>` by Author B remains as a pending insertion in the surrounding baseline.
|
|
82
|
-
- **Accept Author B Only**: Author B's `<w:del>` is removed; Author B's `<w:ins>` unwraps into text inside Author A's pending insertion. Author A's insertion fragments remain pending.
|
|
83
|
-
- **Reject Author B Only**: Author B's `<w:del>` is converted back into regular runs and coalesced back into Author A's `<w:ins>`. Author B's `<w:ins>` is removed.
|
|
84
|
-
|
|
85
|
-
4. **Zero Empty Containers**:
|
|
86
|
-
- If an edit touches the exact boundary of Author A's `<w:ins>`, no empty `<w:ins>` (container with 0 text characters) may be emitted.
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## 3. Architecture & Technical Design
|
|
91
|
-
|
|
92
|
-
```
|
|
93
|
-
Target Paragraph OOXML
|
|
94
|
-
|
|
|
95
|
-
v
|
|
96
|
-
Build Surgical Text Spans (Surgical Spans)
|
|
97
|
-
[Identifies text, offsets, and carrier revision element]
|
|
98
|
-
|
|
|
99
|
-
v
|
|
100
|
-
Target Range Overlaps Foreign Author <w:ins>
|
|
101
|
-
|
|
|
102
|
-
+------------------+------------------+
|
|
103
|
-
| |
|
|
104
|
-
v v
|
|
105
|
-
[Deletion Edit] [Insertion Edit]
|
|
106
|
-
1. Keep carrier <w:ins> intact 1. Split carrier <w:ins>
|
|
107
|
-
2. Extract deleted text at insertion offset
|
|
108
|
-
into <w:delText> 2. Create Author B's
|
|
109
|
-
3. Create Author B's <w:ins> with new ID
|
|
110
|
-
<w:del> with new ID 3. Splice at carrier level:
|
|
111
|
-
4. Nest <w:del> directly inside [Author A Left <w:ins>]
|
|
112
|
-
Author A's <w:ins> [Author B New <w:ins>]
|
|
113
|
-
[Author A Right <w:ins>]
|
|
114
|
-
|
|
|
115
|
-
v
|
|
116
|
-
Output Reconciliation Oracle
|
|
117
|
-
(Assert valid schema, unique IDs, only permitted nesting)
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### 3.1 Container Splitting Primitive: `splitTrackChangeCarrier`
|
|
121
|
-
A new utility in `engine/surgical-run-splitting.js`:
|
|
122
|
-
```js
|
|
123
|
-
export function splitTrackChangeCarrier(xmlDoc, carrierElement, splitOffset, allocator) {
|
|
124
|
-
// 1. Validate the <w:ins> carrier and preserve all carrier metadata.
|
|
125
|
-
// 2. Locate run and character offset corresponding to splitOffset.
|
|
126
|
-
// 3. Clone carrierElement into leftCarrier and rightCarrier.
|
|
127
|
-
// 4. Distribute child runs before splitOffset to leftCarrier, after to rightCarrier.
|
|
128
|
-
// 5. Allocate new unique revision ID for rightCarrier.
|
|
129
|
-
// 6. Return { leftCarrier, rightCarrier }.
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### 3.2 Splicing in `processDelete`
|
|
134
|
-
When deleting a range `[startPos, endPos]`:
|
|
135
|
-
1. Check if the affected spans belong to a `<w:ins>` container.
|
|
136
|
-
2. If `span.runElement.parentNode` is `<w:ins>`:
|
|
137
|
-
- Identify whether the author of `<w:ins>` differs from the mutation `author`.
|
|
138
|
-
- Under cross-author slicing mode, keep the foreign `<w:ins>` intact.
|
|
139
|
-
- Create `<w:del w:author="AuthorB">` containing `<w:r><w:delText>deleted text</w:delText></w:r>`.
|
|
140
|
-
- Insert the `<w:del>` inside the carrier at the deleted run position, preserving unaffected runs on either side.
|
|
141
|
-
|
|
142
|
-
### 3.3 Splicing in `processInsert`
|
|
143
|
-
When inserting at position `pos`:
|
|
144
|
-
1. Check if `pos` falls inside an existing `<w:ins>`.
|
|
145
|
-
2. If `targetSpan.runElement.parentNode` is `<w:ins>`:
|
|
146
|
-
- Split carrier `<w:ins>` into `left_ins` and `right_ins` at `pos`.
|
|
147
|
-
- Create `new_ins` for Author B with Author B's text runs.
|
|
148
|
-
- Insert `[left_ins, new_ins, right_ins]` into the common `<w:p>`.
|
|
149
|
-
- Remove original carrier `<w:ins>`.
|
|
150
|
-
|
|
151
|
-
### 3.4 Straddle Deletions (Boundary Spanning)
|
|
152
|
-
When Author B's deletion starts in baseline text and ends inside Author A's `<w:ins>` (or vice-versa):
|
|
153
|
-
* The deletion must be partitioned:
|
|
154
|
-
1. The baseline portion emits a normal `<w:del>` child of `<w:p>`.
|
|
155
|
-
2. The insertion portion remains inside Author A's `<w:ins>` and emits a nested `<w:del>` there.
|
|
156
|
-
3. The baseline and insertion portions cannot be coalesced across the carrier boundary; preserve a top-level `<w:del>` plus a nested `<w:ins><w:del>...</w:del></w:ins>`, matching the Word Desktop fixture.
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## 4. Policy Configuration & Migration
|
|
161
|
-
|
|
162
|
-
We introduce a dedicated policy setting on `existingRevisions`:
|
|
163
|
-
|
|
164
|
-
```ts
|
|
165
|
-
type ExistingRevisionsPolicy =
|
|
166
|
-
| 'merge-same-author' // Default in v0.5.x: merges same author, fails cross-author
|
|
167
|
-
| 'slice-cross-author' // NEW: merges same author, slices cross-author insertions
|
|
168
|
-
| 'accept-all-first' // Normalizes all prior revisions to baseline
|
|
169
|
-
| 'reject-input'; // Refuses any paragraph with revisions
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
### CLI Flag
|
|
173
|
-
* `--existing-revisions slice-cross-author`
|
|
174
|
-
* Future migration in v1.0.0: make `slice-cross-author` the default behavior of `merge-same-author` or replace it once verified across stress tests.
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
|
|
178
|
-
## 5. Work Packages
|
|
179
|
-
|
|
180
|
-
### WP-01: Baseline Word Desktop Fixture Generation [COMPLETED 2026-09-08]
|
|
181
|
-
* **Goal**: Produce reference DOCX files created directly in Microsoft Word Desktop 365 / 2021 covering:
|
|
182
|
-
1. Cross-author insertion in the middle of a pending insertion (`insert-interior`).
|
|
183
|
-
2. Cross-author deletion in the middle of a pending insertion (`delete-interior`).
|
|
184
|
-
3. Cross-author deletion at the start of a pending insertion (`delete-boundary-start`).
|
|
185
|
-
4. Cross-author deletion at the end of a pending insertion (`delete-boundary-end`).
|
|
186
|
-
5. Cross-author deletion straddling baseline text and an insertion (`delete-straddle-baseline-insertion`).
|
|
187
|
-
6. Multi-author stacked edits (`multi-author-stacked` - Author C editing Author B's edit inside Author A's edit).
|
|
188
|
-
* **Deliverables Produced**:
|
|
189
|
-
1. `scripts/generate-cross-author-slicing-fixtures.ps1`: Windows PowerShell script automating Microsoft Word Desktop 365 via COM (`Word.Application`) to author the 6 scenarios, export pending/accepted/rejected `.docx` files, and extract `word/document.xml`.
|
|
190
|
-
2. `tests/fixtures/cross-author-slicing/`: 36 golden fixture files (18 `.docx` and 18 UTF-8 BOM-clean `.xml` files) covering all triples:
|
|
191
|
-
- `insert-interior-{pending,accepted,rejected}.{docx,xml}`
|
|
192
|
-
- `delete-interior-{pending,accepted,rejected}.{docx,xml}`
|
|
193
|
-
- `delete-boundary-start-{pending,accepted,rejected}.{docx,xml}`
|
|
194
|
-
- `delete-boundary-end-{pending,accepted,rejected}.{docx,xml}`
|
|
195
|
-
- `delete-straddle-baseline-insertion-{pending,accepted,rejected}.{docx,xml}`
|
|
196
|
-
- `multi-author-stacked-{pending,accepted,rejected}.{docx,xml}`
|
|
197
|
-
3. `tests/cross_author_slicing_fixtures_tests.mjs`: Comprehensive automated test suite asserting:
|
|
198
|
-
- All 36 files exist and load cleanly without BOM corruption.
|
|
199
|
-
- Word Desktop native OOXML structural patterns match assertions (sibling `<w:ins>` splitting vs. nested `<w:del>` inside `<w:ins>`).
|
|
200
|
-
- Lifecycle oracle equivalence between `docx-redline-js` (`acceptTrackedChangesInOoxml`, `rejectTrackedChangesInOoxml`) and Word Desktop's native `AcceptAll` / `RejectAll`.
|
|
201
|
-
- Selective author accept/reject lifecycle assertions on nested revisions.
|
|
202
|
-
* **Files Touched**:
|
|
203
|
-
- `.gitignore` (MODIFIED to retain this plan in version control)
|
|
204
|
-
- `scripts/generate-cross-author-slicing-fixtures.ps1` (NEW)
|
|
205
|
-
- `tests/fixtures/cross-author-slicing/*` (36 files: 18 `.docx`, 18 `.xml`) (NEW)
|
|
206
|
-
- `tests/cross_author_slicing_fixtures_tests.mjs` (NEW)
|
|
207
|
-
- `docs/plans/2026-09-08-cross-author-revision-slicing.md` (MODIFIED)
|
|
208
|
-
* **Functions & Modules Touched / Created**:
|
|
209
|
-
- `scripts/generate-cross-author-slicing-fixtures.ps1`:
|
|
210
|
-
- `Extract-DocumentXml`: Extracts UTF-8 `word/document.xml` from DOCX ZIP archive.
|
|
211
|
-
- `Save-Triple`: Saves pending DOCX, accepts all and saves accepted DOCX, reopens and rejects all and saves rejected DOCX.
|
|
212
|
-
- Scenario blocks 1–6 utilizing COM Word automation (`$doc.TrackRevisions`, `$doc.Range.Text`, `$delRange.Delete()`, etc.).
|
|
213
|
-
- `tests/cross_author_slicing_fixtures_tests.mjs`:
|
|
214
|
-
- `loadFixtureXml`: Safe XML loader with leading BOM stripping.
|
|
215
|
-
- `findChildrenByLocalName`, `findDescendantsByLocalName`: Namespace-resilient OOXML tree inspection.
|
|
216
|
-
- Fixture inventory checks, Word Desktop structural checks, lifecycle parity checks (`acceptTrackedChangesInOoxml`, `rejectTrackedChangesInOoxml`), selective author accept/reject tests.
|
|
217
|
-
* **Key Findings & Next Phase Guidance (Crucial for WP-02 – WP-04)**:
|
|
218
|
-
- **Insertion Slicing**: Word Desktop splits `<w:ins>` into siblings at `<w:p>` level: `[ins(A), ins(B), ins(A)]`.
|
|
219
|
-
- **Deletion Slicing**: Word Desktop nests `<w:del>` directly inside `<w:ins>`: `<w:ins><w:r>...</w:r><w:del><w:r><w:delText>...</w:delText></w:r></w:del><w:r>...</w:r></w:ins>`. This is valid ECMA-376 `CT_RunTrackChange`.
|
|
220
|
-
- **Validator Update Required in WP-04**: `core/redline-validation.js` currently flags any `<w:del>` inside `<w:ins>` as `NESTED_REVISION`. That rule must be updated to allow `<w:del>` inside `<w:ins>`, while maintaining the prohibition against `<w:ins>` inside `<w:ins>` or `<w:ins>` inside `<w:del>`.
|
|
221
|
-
* **WP-01 Review (2026-09-08)**:
|
|
222
|
-
- Ran `node tests/cross_author_slicing_fixtures_tests.mjs`: all inventory, structural, lifecycle parity, and selective-author assertions passed.
|
|
223
|
-
- Corrected the generator to derive the repository root from `$PSScriptRoot` instead of a machine-specific absolute path.
|
|
224
|
-
- Added `Find-RequiredText` and changed all six scenario mutations to fail explicitly when Word cannot locate the intended text, preventing false golden fixtures.
|
|
225
|
-
- Corrected stale deletion diagrams and synthetic expectations in this plan to match the native nested `<w:del>` evidence.
|
|
226
|
-
|
|
227
|
-
### WP-02: Carrier Splitting & Revision Allocator Integration [COMPLETED 2026-09-08]
|
|
228
|
-
* **Goal**: Implement `splitTrackChangeCarrier` in `engine/surgical-run-splitting.js`.
|
|
229
|
-
* **Deliverable**: Unit tests proving clean splitting of `<w:ins>` runs at character offsets, proper allocation of unique revision IDs via `RevisionIdAllocator`, and metadata preservation (`w:author`, `w:date`, `w16du:dateUtc`).
|
|
230
|
-
* **Implementation Summary**:
|
|
231
|
-
- `splitTrackChangeCarrier` validates a `<w:ins>` carrier and accepted-view character offset, returns detached left/right fragments, and never mutates the source carrier.
|
|
232
|
-
- Interior splits retain the original revision ID on the left fragment and allocate a fresh document-scoped ID for the right fragment. Exact-start and exact-end splits return `null` for the empty side and do not consume an ID.
|
|
233
|
-
- Carrier attributes are cloned unchanged, including `w:author`, `w:date`, and `w16du:dateUtc`.
|
|
234
|
-
- A run split preserves `w:rPr` and text-like run children (`w:t`, `w:tab`, `w:br`, and related supported nodes). If cloning duplicates a nested `w:rPrChange`, the right-hand copy receives a fresh ID.
|
|
235
|
-
- Newly allocated carrier IDs are reported to an attached receipt collector when present.
|
|
236
|
-
* **Files Touched**:
|
|
237
|
-
- `engine/surgical-run-splitting.js` (MODIFIED)
|
|
238
|
-
- `tests/cross_author_carrier_splitting_tests.mjs` (NEW)
|
|
239
|
-
- `scripts/generate-cross-author-slicing-fixtures.ps1` (MODIFIED during WP-01 review)
|
|
240
|
-
- `docs/plans/2026-09-08-cross-author-revision-slicing.md` (MODIFIED)
|
|
241
|
-
* **Functions Touched / Created**:
|
|
242
|
-
- `engine/surgical-run-splitting.js`:
|
|
243
|
-
- `splitTrackChangeCarrier` (NEW): non-mutating `<w:ins>` split primitive.
|
|
244
|
-
- `resolveAllocator` (NEW): resolves the explicit or document-scoped `RevisionIdAllocator`.
|
|
245
|
-
- `setWordAttribute` (NEW): writes namespace-correct Word attributes.
|
|
246
|
-
- `getLocalName` (NEW): namespace-resilient carrier validation.
|
|
247
|
-
- Reuses `getRunContentPieces`, `getRunTextLength`, `sliceRunPieces`, and `createRunFromPieces`; these existing functions were not behaviorally changed.
|
|
248
|
-
- `scripts/generate-cross-author-slicing-fixtures.ps1`:
|
|
249
|
-
- `Find-RequiredText` (NEW): fail-fast lookup used by all six fixture scenarios.
|
|
250
|
-
- Fixture directory initialization (MODIFIED): now repository-relative.
|
|
251
|
-
- `tests/cross_author_carrier_splitting_tests.mjs`:
|
|
252
|
-
- `parseCarrier`, `wordAttribute`, `text`, and `revisionIds` (NEW test helpers).
|
|
253
|
-
- Tests cover interior/multi-run splitting, metadata and formatting preservation, unique carrier/`w:rPrChange` IDs, special run children, zero-empty-container boundaries, source immutability, and invalid inputs.
|
|
254
|
-
* **Verification**:
|
|
255
|
-
- `node tests/cross_author_carrier_splitting_tests.mjs` — PASS.
|
|
256
|
-
- `npx eslint engine/surgical-run-splitting.js tests/cross_author_carrier_splitting_tests.mjs` — PASS.
|
|
257
|
-
- PowerShell parser check for `scripts/generate-cross-author-slicing-fixtures.ps1` — PASS (the COM fixtures were reviewed, not regenerated).
|
|
258
|
-
- `npm test` — PASS, 90 test files passed and 0 failed.
|
|
259
|
-
- `npm run lint` — PASS.
|
|
260
|
-
- `npm run check:types` — PASS; all 123 runtime exports have declarations.
|
|
261
|
-
* **Handoff to WP-03**:
|
|
262
|
-
- Import `splitTrackChangeCarrier` into `engine/surgical-diff-application.js` and use it only when a generated insertion lands inside a foreign-author `<w:ins>`.
|
|
263
|
-
- The returned fragments are detached. WP-03 must splice non-null fragments plus Author B's new `<w:ins>` into the original carrier's parent, then remove the original carrier.
|
|
264
|
-
- Pass the active document-scoped allocator so split-fragment IDs participate in the same operation receipt and rollback lifecycle.
|
|
265
|
-
|
|
266
|
-
### WP-03: Surgical Engine Cross-Author Insert Splicing [COMPLETED 2026-09-08]
|
|
267
|
-
* **Goal**: Update `processInsert` in `engine/surgical-diff-application.js` to split carrier `<w:ins>` when author differs and hoist Author B's `<w:ins>` to paragraph sibling level (`[ins(A), ins(B), ins(A)]`).
|
|
268
|
-
* **Deliverable**: Tests verifying cross-author insertion produces 3 sibling `<w:ins>` tags in valid schema without `NESTED_REVISION` errors.
|
|
269
|
-
* **Implementation Summary**:
|
|
270
|
-
- `processInsert` now detects a foreign-author `<w:ins>` in ordinary, insertion-affinity, boundary, and replacement-anchor paths when `existingRevisions: 'slice-cross-author'` is active.
|
|
271
|
-
- `spliceInsertionAtCarrierOffset` uses the WP-02 primitive and replaces the original carrier with `[left foreign ins, current-author ins, right foreign ins]`, omitting null boundary fragments.
|
|
272
|
-
- Inserted text inherits effective run formatting but drops cloned historical `w:rPrChange` markup so revision IDs are not duplicated or misattributed.
|
|
273
|
-
- `applyRedlineToOxml` retains foreign revisions and routes `slice-cross-author` text edits through surgical mode. Same-author input continues to use the established reject-to-baseline/re-diff merge behavior.
|
|
274
|
-
- Pending move revisions still fail closed with `UNSAFE_REVISION_NESTING`.
|
|
275
|
-
* **Files Touched**:
|
|
276
|
-
- `engine/surgical-diff-application.js`
|
|
277
|
-
- `engine/surgical-mode.js`
|
|
278
|
-
- `engine/oxml-engine.js`
|
|
279
|
-
- `tests/cross_author_slicing_synthetic_tests.mjs` (NEW)
|
|
280
|
-
- `tests/cross_author_slicing_fixtures_tests.mjs`
|
|
281
|
-
- `docs/plans/2026-09-08-cross-author-revision-slicing.md`
|
|
282
|
-
* **Functions Touched / Created**:
|
|
283
|
-
- `processInsert` (MODIFIED): policy-aware foreign carrier detection and replacement-anchor support.
|
|
284
|
-
- `spliceInsertionAtCarrierOffset` (NEW): carrier split/splice orchestration.
|
|
285
|
-
- `withoutRunPropertyChanges` (NEW): preserves effective formatting without copying historical formatting revisions.
|
|
286
|
-
- `getCarrierSplitOffset`, `getCarrierGlobalStart`, `isForeignInsertion`, `isConnected` (NEW): carrier targeting helpers.
|
|
287
|
-
- `applySurgicalMode` (MODIFIED): forwards the policy and permits paired replacements inside an insertion carrier under slicing mode.
|
|
288
|
-
- `checkSafeAdjacencyForPairing` (MODIFIED): conditionally treats a foreign insertion carrier as a supported replacement context.
|
|
289
|
-
- `applyRedlineToOxml` (MODIFIED): retains cross-author revisions, merges same-author revisions, rejects moves, and selects surgical routing.
|
|
290
|
-
|
|
291
|
-
### WP-04: Surgical Engine Cross-Author Delete Splicing & Validation Update [COMPLETED 2026-09-08]
|
|
292
|
-
* **Goal**: Update `processDelete` in `engine/surgical-diff-application.js` to nest Author B's `<w:del>` directly inside Author A's carrier `<w:ins>` (matching Word Desktop). Update `core/redline-validation.js` to permit `<w:del>` inside `<w:ins>`.
|
|
293
|
-
* **Deliverable**: Tests verifying cross-author deletion produces `<w:ins>...<w:del>...</w:del>...</w:ins>` matching Word Desktop fixtures without validation errors.
|
|
294
|
-
* **Implementation Summary**:
|
|
295
|
-
- `processDelete` now groups adjacent affected runs sharing the same parent into one deletion wrapper, preserving each run's formatting and converting its text-like children to `w:delText`.
|
|
296
|
-
- When the shared parent is a foreign `<w:ins>`, the deletion wrapper remains directly nested in that carrier; the carrier and its metadata stay intact at start, end, interior, and complete-content deletions.
|
|
297
|
-
- Separate structural contexts receive separate deletion IDs but preserve shared replacement metadata/timestamps where supplied.
|
|
298
|
-
- `validateRedlineOoxml` now accepts only direct `<w:ins><w:del>...</w:del></w:ins>` nesting. It continues to reject `ins/ins`, `del/del`, `ins` inside `del`, and deeper non-direct revision nesting.
|
|
299
|
-
* **Files Touched**:
|
|
300
|
-
- `engine/surgical-diff-application.js`
|
|
301
|
-
- `core/redline-validation.js`
|
|
302
|
-
- `tests/redline_validation_tests.mjs`
|
|
303
|
-
- `tests/cross_author_slicing_synthetic_tests.mjs` (NEW)
|
|
304
|
-
- `tests/cross_author_slicing_fixtures_tests.mjs`
|
|
305
|
-
- `docs/plans/2026-09-08-cross-author-revision-slicing.md`
|
|
306
|
-
* **Functions Touched / Created**:
|
|
307
|
-
- `processDelete` (MODIFIED): plans run mutations, groups adjacent runs, emits one schema-correct deletion per parent context, and records replacement anchors.
|
|
308
|
-
- `nextElementSibling` (NEW): determines whether affected runs can share a deletion wrapper without crossing structural markers.
|
|
309
|
-
- `validateRedlineOoxml` (MODIFIED): implements the Word-native nested-deletion exception and retains all other nesting prohibitions.
|
|
310
|
-
|
|
311
|
-
### WP-05: Straddle & Boundary Deletion Normalization [COMPLETED 2026-09-08]
|
|
312
|
-
* **Goal**: Support deletions that cross between baseline text and pending insertions.
|
|
313
|
-
* **Deliverable**: Regression suite ensuring each deletion portion remains in its schema-correct context (top-level for baseline text, nested for pending insertion text) while sharing coherent author/event metadata.
|
|
314
|
-
* **Implementation Summary**:
|
|
315
|
-
- Cross-boundary deletion records are partitioned by their actual DOM parent: baseline portions emit top-level `<w:del>` and insertion portions emit nested `<w:del>` without attempting schema-invalid coalescing.
|
|
316
|
-
- Forward and reverse baseline/insertion straddles and deletion across two distinct foreign insertion carriers are covered.
|
|
317
|
-
- `revisionInsertionAnchors` preserves the post-deletion carrier/offset/formatting location for the insertion half of a replacement after original span nodes have been removed.
|
|
318
|
-
- Paired replacement metadata remains paired inside a foreign insertion: the deletion stays in the left carrier, the new insertion is hoisted, and the surviving right carrier receives its own unique ID.
|
|
319
|
-
* **Files Touched**:
|
|
320
|
-
- `engine/surgical-diff-application.js`
|
|
321
|
-
- `engine/surgical-mode.js`
|
|
322
|
-
- `tests/cross_author_slicing_synthetic_tests.mjs` (NEW)
|
|
323
|
-
- `tests/cross_author_slicing_fixtures_tests.mjs`
|
|
324
|
-
- `docs/plans/2026-09-08-cross-author-revision-slicing.md`
|
|
325
|
-
* **Functions Touched / Created**:
|
|
326
|
-
- `processDelete` (MODIFIED): records per-carrier replacement anchors and keeps parent contexts separate.
|
|
327
|
-
- `processInsert` (MODIFIED): consumes a connected replacement anchor before consulting stale original spans.
|
|
328
|
-
- `applySurgicalMode` and `checkSafeAdjacencyForPairing` (MODIFIED): allow paired events in the newly supported carrier context.
|
|
329
|
-
* **Verification to Date**:
|
|
330
|
-
- `node tests/cross_author_slicing_synthetic_tests.mjs` — PASS.
|
|
331
|
-
- `node tests/cross_author_slicing_fixtures_tests.mjs` — PASS, including engine reproduction of the five Word Desktop WP03-WP05 fixtures and AcceptAll/RejectAll parity.
|
|
332
|
-
- `npm test` — PASS, 91 test files passed and 0 failed.
|
|
333
|
-
- `npm run lint` — PASS.
|
|
334
|
-
- `npm run check:types` — PASS; all 123 runtime exports have declarations.
|
|
335
|
-
- `git diff --check` — PASS.
|
|
336
|
-
* **Handoff Boundary**:
|
|
337
|
-
- `slice-cross-author` is implemented in the low-level `applyRedlineToOxml` engine used by these work packages.
|
|
338
|
-
- CLI validation, operation preflight, document-operation/facade gating, schema enum changes, and public type exposure remain intentionally deferred to WP-07.
|
|
339
|
-
- WP-06 can build on the already passing native-fixture lifecycle tests; no lifecycle service code was changed in WP03-WP05.
|
|
340
|
-
|
|
341
|
-
### WP-06: Lifecycle Oracles & Selective Accept/Reject Support [COMPLETED 2026-09-08]
|
|
342
|
-
* **Goal**: Update `services/revision-comment-management.js` (`acceptTrackedChangesInOoxml` and `rejectTrackedChangesInOoxml`) to handle sliced revisions correctly:
|
|
343
|
-
- Selective accept/reject by author.
|
|
344
|
-
- Cascading rejection (rejecting Author A cleans up internal deletions by Author B).
|
|
345
|
-
* **Deliverable**: Round-trip lifecycle oracle tests comparing against Word Desktop results.
|
|
346
|
-
* **Implementation Summary**:
|
|
347
|
-
- Confirmed the existing outer-first accept/reject traversal already provides correct dependency behavior for nested deletions: accepting Author A unwraps the carrier and leaves Author B's deletion pending; rejecting Author A removes the carrier and its dependent deletion; rejecting Author B restores `w:delText` as normal text inside Author A's carrier.
|
|
348
|
-
- Added post-rejection normalization for sliced insertions. When rejecting Author B removes the middle insertion, adjacent Author A `<w:ins>` fragments with identical metadata other than `w:id` are coalesced back into one carrier.
|
|
349
|
-
- Coalescing is deliberately metadata-safe: different authors, dates, `dateUtc` values, or other carrier attributes are never merged.
|
|
350
|
-
* **Files Touched**:
|
|
351
|
-
- `services/revision-comment-management.js`
|
|
352
|
-
- `tests/cross_author_slicing_synthetic_tests.mjs`
|
|
353
|
-
- `docs/plans/2026-09-08-cross-author-revision-slicing.md`
|
|
354
|
-
* **Functions Touched / Created**:
|
|
355
|
-
- `rejectTrackedChangesInOoxml` (MODIFIED): runs compatible insertion coalescing after selective rejection.
|
|
356
|
-
- `nextElementSibling` (NEW): finds adjacent revision carriers while tolerating formatting whitespace nodes.
|
|
357
|
-
- `revisionMetadataWithoutId` (NEW): creates a stable metadata comparison key excluding only the split-specific revision ID.
|
|
358
|
-
- `coalesceAdjacentCompatibleInsertions` (NEW): merges compatible same-author insertion fragments without changing lifecycle counts.
|
|
359
|
-
* **Lifecycle Coverage**:
|
|
360
|
-
- Selective accept/reject of Author A and Author B on generated sibling insertion slices.
|
|
361
|
-
- Fragment coalescing after rejecting Author B.
|
|
362
|
-
- Selective rejection of nested Author B deletion and restoration inside Author A's insertion.
|
|
363
|
-
- Selective acceptance of Author A with Author B's deletion remaining pending.
|
|
364
|
-
- Cascading removal of Author B's nested deletion when Author A is rejected.
|
|
365
|
-
- Existing Word Desktop AcceptAll/RejectAll and selective-author fixture oracles remain green.
|
|
366
|
-
|
|
367
|
-
### WP-07: CLI, Preflight, and Facade Exposure [COMPLETED 2026-09-08]
|
|
368
|
-
* **Goal**: Expose `slice-cross-author` in:
|
|
369
|
-
- `docx-redline` CLI (`--existing-revisions slice-cross-author`)
|
|
370
|
-
- `services/operation-preflight.js` (recognizes cross-author slicing as valid rather than `EXISTING_REVISIONS`)
|
|
371
|
-
- `openDocx` / `applyOperationsToDocumentXml` options
|
|
372
|
-
* **Deliverable**: Updated schema definitions and CLI test cases.
|
|
373
|
-
* **Implementation Summary**:
|
|
374
|
-
- Added one canonical runtime policy list and validation helper. Operation-level and batch-level invalid policies now return `INVALID_OPERATION` instead of falling through to unrelated revision errors.
|
|
375
|
-
- Preflight reports foreign insertion/deletion edits as `ready` under `slice-cross-author`, preserves same-author comment safeguards, and continues to fail closed on pending moves.
|
|
376
|
-
- The document mutation gate now permits slicing operations to reach `applyRedlineToOxml`; operation-level policy overrides continue to flow through `document-operation-applier`.
|
|
377
|
-
- `applyOperationsToDocumentXml`, `openDocx(...).applyOperations`, and CLI `apply`/`preflight` accept the batch policy. CLI version capabilities now advertise `cross-author-revision-slicing`.
|
|
378
|
-
- Public TypeScript types, JSON Schema, README, and `AGENTS.md` document the new policy.
|
|
379
|
-
* **Files Touched**:
|
|
380
|
-
- `services/document-operation-contract.js`
|
|
381
|
-
- `services/operation-preflight.js`
|
|
382
|
-
- `services/document-operation-mutations.js`
|
|
383
|
-
- `services/batch-operation-orchestrator.js`
|
|
384
|
-
- `node/cli.js`
|
|
385
|
-
- `index.d.ts`
|
|
386
|
-
- `docs/schemas/document-operations.schema.json`
|
|
387
|
-
- `README.md`
|
|
388
|
-
- `AGENTS.md`
|
|
389
|
-
- `tests/existing_revisions_modes_matrix_tests.mjs`
|
|
390
|
-
- `tests/agent_operation_contract_tests.mjs`
|
|
391
|
-
- `tests/agent_cli_tests.mjs`
|
|
392
|
-
- `docs/plans/2026-09-08-cross-author-revision-slicing.md`
|
|
393
|
-
* **Functions / Types Touched or Created**:
|
|
394
|
-
- `EXISTING_REVISIONS_POLICIES` and `isExistingRevisionsPolicy` (NEW): shared policy contract and validator.
|
|
395
|
-
- `validateDocumentOperation` (MODIFIED): validates operation-level `existingRevisions` values.
|
|
396
|
-
- `preflightOperations` (MODIFIED): validates batch policy and models same-author merge, foreign-author slicing, and unsupported moves.
|
|
397
|
-
- `applyOperationsToDocumentXml` (MODIFIED): validates batch policy before creating or mutating a session.
|
|
398
|
-
- Document redline mutation policy gate in `applyToParagraphByExactText` (MODIFIED): permits slicing while preserving comment and move safeguards.
|
|
399
|
-
- `executeCli` (MODIFIED): validates `--existing-revisions`; apply/preflight pass-through remains shared with facade options.
|
|
400
|
-
- `ExistingRevisionsPolicy` (MODIFIED): includes `'slice-cross-author'`.
|
|
401
|
-
* **End-to-End Coverage**:
|
|
402
|
-
- CLI `apply --existing-revisions slice-cross-author` writes a package that inspects with both original and current authors.
|
|
403
|
-
- CLI `preflight` accepts the same operation and reports the selected policy.
|
|
404
|
-
- CLI rejects an unknown policy with `INVALID_OPERATION`.
|
|
405
|
-
- Runtime operation validation accepts `slice-cross-author` and rejects misspellings.
|
|
406
|
-
- JSON Schema is parsed and asserted to contain the new enum value.
|
|
407
|
-
- Facade/package validation and output reconciliation run through the existing CLI application path.
|
|
408
|
-
* **Final Verification**:
|
|
409
|
-
- `npm test` — PASS, 91 test files passed and 0 failed.
|
|
410
|
-
- `npm run lint` — PASS.
|
|
411
|
-
- `npm run check:types` — PASS; all 123 runtime exports have declarations.
|
|
412
|
-
- `git diff --check` — PASS.
|
|
413
|
-
|
|
414
|
-
### Final Automated Test Completion [COMPLETED 2026-09-08]
|
|
415
|
-
* **Coverage Added**:
|
|
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.
|
|
419
|
-
* **Files Touched**:
|
|
420
|
-
- `tests/cross_author_slicing_synthetic_tests.mjs` (MODIFIED)
|
|
421
|
-
- `tests/cross_author_slicing_real_tests.mjs` (NEW)
|
|
422
|
-
- `docs/plans/2026-09-08-cross-author-revision-slicing.md` (MODIFIED)
|
|
423
|
-
* **Functions / Test Helpers Touched or Created**:
|
|
424
|
-
- `tests/cross_author_slicing_synthetic_tests.mjs`: added the SYN-02/SYN-11/SYN-12 generated lifecycle block; no production functions changed during final test completion.
|
|
425
|
-
- `tests/cross_author_slicing_real_tests.mjs`:
|
|
426
|
-
- `fixtureBuffer` (NEW): loads a checked-in Word Desktop DOCX oracle.
|
|
427
|
-
- `visibleText` (NEW): obtains stable main-document text through package inspection.
|
|
428
|
-
- `resolvedText` (NEW): resolves all revisions through the package facade, requires package validation, and asserts no revision authors remain.
|
|
429
|
-
- PKG-01 through PKG-05 (NEW): strict package reproduction and differential lifecycle tests.
|
|
430
|
-
- PKG-06 (NEW): generated three-reviewer, two-round package negotiation test.
|
|
431
|
-
* **Final Verification**:
|
|
432
|
-
- `node tests/cross_author_slicing_synthetic_tests.mjs` — PASS.
|
|
433
|
-
- `node tests/cross_author_slicing_real_tests.mjs` — PASS.
|
|
434
|
-
- `npm test` — PASS, 92 test files passed and 0 failed.
|
|
435
|
-
- `npm run lint` — PASS.
|
|
436
|
-
- `npm run check:types` — PASS; all 123 runtime exports have declarations.
|
|
437
|
-
- `git diff --check` — PASS (line-ending conversion notices only; no whitespace errors).
|
|
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.
|
|
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
|
-
|
|
442
|
-
### Bug Follow-Up: Hyperlink Boundary Round-Trip Mismatch [FIXED 2026-09-08]
|
|
443
|
-
* **Report Reproduced**: A slicing edit with repeated text, hyperlink runs, and NBSP-to-space substitutions could return `status: "ok"` even though its accepted-view text differed from `modified`.
|
|
444
|
-
* **Root Cause**: `applySurgicalMode` discarded whitespace-only insertion diff segments by checking `textWithoutNewlines.trim().length`. The corresponding NBSP deletion still committed, changing `located at\u00a0example.com` to `located atexample.com`. Structural replacement paths could also proceed after `PAIRING_SKIPPED_STRUCTURAL_BOUNDARY` without a final exact-text oracle.
|
|
445
|
-
* **Fix**:
|
|
446
|
-
- Whitespace-only insertions are now applied rather than silently skipped.
|
|
447
|
-
- Every `slice-cross-author` surgical result reconstructs canonical accepted-view text and compares it exactly with the requested clean modified text.
|
|
448
|
-
- A mismatch returns `PATCH_ROUNDTRIP_MISMATCH`, `hasChanges: false`, diagnostic excerpts and offset, and the exact original OOXML. The document runner/facade therefore treats the operation as unapplied and preserves transactional rollback.
|
|
449
|
-
* **Files Touched**:
|
|
450
|
-
- `engine/surgical-mode.js`
|
|
451
|
-
- `engine/oxml-engine.js`
|
|
452
|
-
- `tests/cross_author_slicing_hyperlink_roundtrip_tests.mjs` (NEW)
|
|
453
|
-
- `CHANGELOG.md`
|
|
454
|
-
- `README.md`
|
|
455
|
-
- `AGENTS.md`
|
|
456
|
-
- `docs/TESTING.md`
|
|
457
|
-
- `docs/plans/2026-09-08-cross-author-revision-slicing.md`
|
|
458
|
-
* **Functions Touched / Created**:
|
|
459
|
-
- `applySurgicalMode` (MODIFIED): retains whitespace-only insertions and enforces the slicing accepted-view postcondition.
|
|
460
|
-
- `firstMismatchOffset` (NEW): locates the first exact-text divergence.
|
|
461
|
-
- `excerptAt` (NEW): provides bounded expected/actual diagnostics without returning entire contract paragraphs.
|
|
462
|
-
- `applyRedlineToOxml` surgical result handling (MODIFIED): restores the exact input OOXML on `PATCH_ROUNDTRIP_MISMATCH`.
|
|
463
|
-
- Test helpers `run`, `insertion`, `hyperlink`, and `acceptedParagraphText` (NEW).
|
|
464
|
-
* **Regression Coverage**:
|
|
465
|
-
- Low-level reproduction with two hyperlink relationship containers, repeated `Widget Policy`, and three NBSP-to-space edits.
|
|
466
|
-
- Full `applyOperationsToDocumentXml` atomic runner reproduction matching the CLI execution path.
|
|
467
|
-
- Exact Accept-All equality with the submitted modified string.
|
|
468
|
-
- Explicit fail-closed test proving mismatch status, error code, mismatch offset, and byte-exact original OOXML rollback.
|
|
469
|
-
* **Verification**:
|
|
470
|
-
- `node tests/cross_author_slicing_replacement_anchor_tests.mjs` — PASS, 12 scenarios.
|
|
471
|
-
- `node tests/cross_author_slicing_hyperlink_roundtrip_tests.mjs` — PASS.
|
|
472
|
-
- `npm test` — PASS, 93 test files passed and 0 failed.
|
|
473
|
-
- `npm run lint` — PASS.
|
|
474
|
-
- `npm run check:types` — PASS; all 123 runtime exports have declarations.
|
|
475
|
-
- `git diff --check` — PASS (line-ending conversion notices only; no whitespace errors).
|
|
476
|
-
|
|
477
|
-
### Insertion Stress Follow-Up [COMPLETED 2026-09-08]
|
|
478
|
-
* **Motivation**: Real usage reported failures across a wider variety of insertions after the first hyperlink/NBSP bug. A generated matrix was added to exercise location, payload, structure, lifecycle, and repeated-review dimensions rather than relying on a few fixed examples.
|
|
479
|
-
* **Defects Exposed and Fixed**:
|
|
480
|
-
1. Leading/trailing spaces, tabs, and NBSP-only additions were classified as no-ops because slicing inherited trim-based text-change detection. `applyRedlineToOxml` now uses exact comparison for `slice-cross-author`.
|
|
481
|
-
2. Word-token semantic diff cleanup could relocate a pure insertion between repeated phrases, especially inside a hyperlink. `computeInsertionOnlyDiffs` now selects a character-local, no-deletion diff whenever the original is an exact subsequence of the modified text; replacements retain the established word diff and exact round-trip guard.
|
|
482
|
-
3. In a paragraph containing both current-author and foreign insertion carriers, inserting into the current-author carrier produced illegal nested `w:ins`. `processInsert` now adds a normal run to that existing carrier while continuing to split foreign carriers into siblings.
|
|
483
|
-
* **Coverage Added**:
|
|
484
|
-
- 76 deterministic scenarios spanning carrier start/end/interior positions; single-, multi-, and formatted runs; repeated tokens; double spaces, tabs, NBSP, XML-sensitive characters, emoji, ZWJ emoji, combining characters, citations, and punctuation.
|
|
485
|
-
- Hyperlink interiors and both hyperlink boundaries; bookmarks; comment anchors; nested prior deletions; adjacent foreign authors; mixed current/foreign authors; three-container edits; and consecutive second-/third-reviewer rounds.
|
|
486
|
-
- Exact accepted-view, Accept-All, Reject-Current, validation, unique metadata, hyperlink preservation, and zero-empty-insertion assertions.
|
|
487
|
-
- Twenty scenarios also execute through `applyOperationsToDocumentXml` with atomic and strict-target settings, matching the CLI runner path.
|
|
488
|
-
- Nested hyperlink/field structures are required either to produce exact valid output or fail closed without throwing.
|
|
489
|
-
* **Files Touched**:
|
|
490
|
-
- `pipeline/diff-engine.js`
|
|
491
|
-
- `engine/oxml-engine.js`
|
|
492
|
-
- `engine/surgical-mode.js`
|
|
493
|
-
- `engine/surgical-diff-application.js`
|
|
494
|
-
- `tests/cross_author_slicing_insertion_stress_tests.mjs` (NEW)
|
|
495
|
-
- `CHANGELOG.md`
|
|
496
|
-
- `README.md`
|
|
497
|
-
- `docs/TESTING.md`
|
|
498
|
-
- `docs/plans/2026-09-08-cross-author-revision-slicing.md`
|
|
499
|
-
* **Functions Touched / Created**:
|
|
500
|
-
- `computeInsertionOnlyDiffs` (NEW): detects insertion-only transforms and returns a character-local diff only when it contains no deletion.
|
|
501
|
-
- `applyRedlineToOxml` (MODIFIED): uses exact slicing change detection, including boundary whitespace.
|
|
502
|
-
- `applySurgicalMode` (MODIFIED): selects insertion-only versus word diff without changing replacement semantics.
|
|
503
|
-
- `processInsert` (MODIFIED): inserts directly into an existing same-author carrier in mixed-author paragraphs.
|
|
504
|
-
- `isSameAuthorInsertion` (NEW): namespace-safe author comparison for carrier coalescing.
|
|
505
|
-
- Stress helpers `escapeXml`, `run`, `insertion`, `paragraph`, `parsed`, `acceptedText`, `authorOf`, `assertValid`, and `assertInsertionRoundTrip` (NEW).
|
|
506
|
-
* **Verification**:
|
|
507
|
-
- `node tests/cross_author_slicing_insertion_stress_tests.mjs` — PASS, 76 scenarios.
|
|
508
|
-
- `npm test` — PASS, 95 test files passed and 0 failed.
|
|
509
|
-
- `npm run lint` — PASS.
|
|
510
|
-
- `npm run check:types` — PASS; all 123 runtime exports have declarations.
|
|
511
|
-
- `git diff --check` — PASS (line-ending conversion notices only; no whitespace errors).
|
|
512
|
-
|
|
513
|
-
### Hyperlink-Adjacent Replacement Follow-Up [COMPLETED 2026-09-08]
|
|
514
|
-
* **Bug Report**: Replacing the space immediately after a Privacy Policy hyperlink with a comma and execution-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
|
-
#### Paragraph Property Sanitization (Normative Allowlist)
|
|
635
|
-
|
|
636
|
-
When deriving Reviewer B's paragraph from the deleted source, copy only:
|
|
637
|
-
|
|
638
|
-
* `w:pStyle`, `w:numPr`, `w:ind`, `w:jc`, `w:spacing`, `w:tabs`, `w:keepNext`/`w:keepLines`, `w:outlineLvl`, `w:contextualSpacing`.
|
|
639
|
-
|
|
640
|
-
Strip unconditionally:
|
|
641
|
-
|
|
642
|
-
* `w:rPr/w:del` and `w:rPr/w:ins` (foreign mark revisions — replaced by Reviewer B's own inserted mark),
|
|
643
|
-
* `w:sectPr` (section identity must never be duplicated; see refusals),
|
|
644
|
-
* `w:pPrChange`, `w:rPrChange`, and every other `*Change` element (they describe a revision of the *source* paragraph and are meaningless on the clone).
|
|
645
|
-
|
|
646
|
-
#### Paragraph Identity (Normative)
|
|
647
|
-
|
|
648
|
-
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.
|
|
649
|
-
|
|
650
|
-
#### Trigger Taxonomy (Normative)
|
|
651
|
-
|
|
652
|
-
"Whole-paragraph deletion" is ambiguous across the four combinations of paragraph-mark state and content state. Only one routes to WP08b:
|
|
653
|
-
|
|
654
|
-
| Paragraph mark | Pre-existing content | Route |
|
|
655
|
-
|:--|:--|:--|
|
|
656
|
-
| Deleted by foreign author | All deleted | **WP08b sibling restoration** (the resurrection state) |
|
|
657
|
-
| 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 |
|
|
658
|
-
| Not deleted | All deleted | Ordinary cross-author insertion — no foreign mark owns the paragraph; MUST NOT route to WP08b |
|
|
659
|
-
| Deleted by current author | All deleted | `merge-same-author`; MUST NOT route to WP08b |
|
|
660
|
-
|
|
661
|
-
#### Validation Predicate (Normative)
|
|
662
|
-
|
|
663
|
-
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.
|
|
664
|
-
|
|
665
|
-
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).
|
|
666
|
-
|
|
667
|
-
#### Required Behavior
|
|
668
|
-
|
|
669
|
-
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.
|
|
670
|
-
2. Never append visible runs or `w:ins` content to the paragraph still owned by the foreign paragraph deletion.
|
|
671
|
-
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.
|
|
672
|
-
4. Track the new paragraph's content and its paragraph mark as Reviewer B insertions with document-scoped revision IDs.
|
|
673
|
-
5. Preserve document order and ensure `w:sectPr`, tables, list boundaries, comments, bookmarks, and other structural anchors are neither displaced nor duplicated.
|
|
674
|
-
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.
|
|
675
|
-
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.
|
|
676
|
-
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.
|
|
677
|
-
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.
|
|
678
|
-
|
|
679
|
-
#### Structural Anchors (Normative)
|
|
680
|
-
|
|
681
|
-
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.
|
|
682
|
-
|
|
683
|
-
#### Fail-Closed Refusals (Distinct Codes)
|
|
684
|
-
|
|
685
|
-
A single `UNSAFE_PARAGRAPH_BOUNDARY` code conflates unrelated conditions and reads as a near-collision with the existing `PAIRING_SKIPPED_STRUCTURAL_BOUNDARY`. Enumerate:
|
|
686
|
-
|
|
687
|
-
| Condition | Code |
|
|
688
|
-
|:--|:--|
|
|
689
|
-
| Resurrection attempted without explicit restore intent (WP08a gate) | `FOREIGN_PARAGRAPH_MARK_DELETION` |
|
|
690
|
-
| Source paragraph inside a row deleted via `w:trPr/w:del` — a sibling paragraph cannot survive the row | `UNSAFE_DELETED_TABLE_ROW` |
|
|
691
|
-
| Source paragraph is part of a move (`w:moveFrom` / `w:moveFromRangeStart`) | `UNSUPPORTED_MOVE_REVISION` |
|
|
692
|
-
| Source paragraph's `pPr` carries `w:sectPr` — mirrors the existing deletion refusal in `core/paragraph-targeting.js` | `SECTION_BREAK_PARAGRAPH` |
|
|
693
|
-
| Source paragraph is the final paragraph of the body, so no safe sibling placement exists | `UNSAFE_PARAGRAPH_PLACEMENT` |
|
|
694
|
-
|
|
695
|
-
All refusals return the original document unchanged under atomic mode.
|
|
696
|
-
|
|
697
|
-
#### Lifecycle Invariants
|
|
698
|
-
|
|
699
|
-
For the paragraph pair `[ins(B), del(A)]` in document order:
|
|
700
|
-
|
|
701
|
-
| Resolution | Expected Result |
|
|
702
|
-
|:--|:--|
|
|
703
|
-
| Current view | Reviewer B's restored/adjusted paragraph appears exactly once; Reviewer A's paragraph is invisible. |
|
|
704
|
-
| 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. |
|
|
705
|
-
| 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. |
|
|
706
|
-
| 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. |
|
|
707
|
-
| 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. |
|
|
708
|
-
| 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. |
|
|
709
|
-
|
|
710
|
-
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.
|
|
711
|
-
|
|
712
|
-
#### Verification Oracle (Normative)
|
|
713
|
-
|
|
714
|
-
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.
|
|
715
|
-
|
|
716
|
-
Every WP08b fixture must therefore:
|
|
717
|
-
|
|
718
|
-
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.
|
|
719
|
-
2. Assert the merge target of each paragraph-mark resolution explicitly, not inferred from resulting text.
|
|
720
|
-
3. Fail closed with a structured mismatch code and byte-exact rollback on divergence, matching the established `PATCH_ROUNDTRIP_MISMATCH` pattern.
|
|
721
|
-
|
|
722
|
-
#### Planned Implementation Areas
|
|
723
|
-
|
|
724
|
-
- `services/document-operation-applier.js`: route explicit restoration intent; retain the rejected-view mutation guard for unsupported generic mutations.
|
|
725
|
-
- `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).
|
|
726
|
-
- `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.
|
|
727
|
-
- Revision allocator and receipt collector: report every paragraph-mark and content revision ID allocated by the restoration, plus dropped-anchor warnings.
|
|
728
|
-
- `core/redline-validation.js`: add the narrowed resurrection-state warning (see Validation Predicate).
|
|
729
|
-
|
|
730
|
-
#### Required Test Matrix
|
|
731
|
-
|
|
732
|
-
1. Plain whole-paragraph deletion restored verbatim.
|
|
733
|
-
2. Restored paragraph adjusted while being restored.
|
|
734
|
-
3. Bold, italic, underline, and mixed-run formatting preservation.
|
|
735
|
-
4. Numbered and bulleted paragraph restoration without list-label drift.
|
|
736
|
-
5. Paragraph immediately before `w:sectPr`, **and** a paragraph whose own `pPr` carries `w:sectPr` (refusal).
|
|
737
|
-
6. Paragraph inside a table cell; paragraph inside a row deleted via `w:trPr/w:del` (refusal).
|
|
738
|
-
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.
|
|
739
|
-
8. Multiple adjacent deleted paragraphs restored independently and as a range, asserting one inserted sibling per source paragraph and preserved order.
|
|
740
|
-
9. Same-author deletion behavior remains governed by `merge-same-author` and is not routed through cross-author restoration.
|
|
741
|
-
10. Each non-triggering row of the trigger taxonomy routes to its stated path and not to WP08b.
|
|
742
|
-
11. Third-author follow-up edits to Reviewer B's restored paragraph continue to use ordinary cross-author slicing.
|
|
743
|
-
12. Repeat application of the same restoration is idempotent — no duplicate Reviewer B paragraph.
|
|
744
|
-
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.
|
|
745
|
-
14. `w:moveFrom` source paragraph (refusal); final-paragraph-of-body source (refusal).
|
|
746
|
-
15. Atomic runner rollback and progressive batch receipts.
|
|
747
|
-
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.
|
|
748
|
-
|
|
749
|
-
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.
|
|
750
|
-
|
|
751
|
-
#### Implementation Record
|
|
752
|
-
|
|
753
|
-
* **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.
|
|
754
|
-
* **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.
|
|
755
|
-
* **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.
|
|
756
|
-
* **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.
|
|
757
|
-
* **Files Touched**:
|
|
758
|
-
- `core/paragraph-revision-safety.js`
|
|
759
|
-
- `core/paragraph-targeting.js`
|
|
760
|
-
- `services/document-operation-contract.js`
|
|
761
|
-
- `services/document-operation-applier.js`
|
|
762
|
-
- `services/document-operation-mutations.js`
|
|
763
|
-
- `services/operation-preflight.js`
|
|
764
|
-
- `services/standalone-operation-runner.d.ts`
|
|
765
|
-
- `docs/schemas/document-operations.schema.json`
|
|
766
|
-
- `index.d.ts`
|
|
767
|
-
- `tests/paragraph_level_cross_author_restoration_tests.mjs` (NEW)
|
|
768
|
-
- `tests/types/usage.ts`
|
|
769
|
-
- `README.md`
|
|
770
|
-
- `AGENTS.md`
|
|
771
|
-
- `CHANGELOG.md`
|
|
772
|
-
- `docs/plans/2026-09-08-cross-author-revision-slicing.md`
|
|
773
|
-
* **Functions and Types Touched**:
|
|
774
|
-
- `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.
|
|
775
|
-
- `resolveTargetParagraph` (MODIFIED): supports strict fingerprint-only descriptors, bringing runtime targeting into alignment with the published schema.
|
|
776
|
-
- `getCanonicalOperationType`, `normalizeDocumentOperation`, and `validateDocumentOperation` (MODIFIED): normalize and validate explicit single/range `restore` operations and retain full `targetEnd` descriptors.
|
|
777
|
-
- `applyOperationToDocumentXml` (MODIFIED): routes restoration separately and keeps rejected-view mutation read-only for both range endpoints.
|
|
778
|
-
- `restoreDeletedParagraphByExactText` (NEW): resolves the source block, enforces trigger/safety rules, reconstructs rejected-view content, inserts tracked siblings, handles idempotency, and runs the lifecycle oracle.
|
|
779
|
-
- `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.
|
|
780
|
-
- `wrapParagraphContentInInsertion`, `buildFallbackInsertedPlainParagraph`, and `buildInsertedPlainParagraph` (MODIFIED): optionally emit inserted paragraph marks, use typed insertion receipt metadata, sanitize restoration properties, and accept fresh paragraph identity.
|
|
781
|
-
- `preflightOperations` (MODIFIED): recognizes restoration state, range cardinality, and structural refusals without mutating the document.
|
|
782
|
-
- `RestoreDocumentOperation` and `RedlineError` (MODIFIED/NEW): publish the operation shape and structured refusal/oracle metadata.
|
|
783
|
-
- 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.
|
|
784
|
-
* **Verification**:
|
|
785
|
-
- `node tests/paragraph_level_cross_author_restoration_tests.mjs` — PASS.
|
|
786
|
-
- Focused list and insertion-affinity regressions — PASS.
|
|
787
|
-
- `$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.
|
|
788
|
-
- `npm run lint` — PASS.
|
|
789
|
-
- `npm run check:types` — PASS; all 123 runtime exports have declarations.
|
|
790
|
-
- `npm run build` — PASS.
|
|
791
|
-
- `git diff --check` — PASS (line-ending conversion notices only; no whitespace errors).
|
|
792
|
-
|
|
793
|
-
---
|
|
794
|
-
|
|
795
|
-
## 6. Comprehensive Verification Plan (Synthetic & Real Test Series)
|
|
796
|
-
|
|
797
|
-
To prove correctness across all layers of the stack, this plan defines two comprehensive test suites:
|
|
798
|
-
1. **Synthetic Unit & Boundary Suites** (`tests/cross_author_slicing_synthetic_tests.mjs`): Isolated OOXML fixtures testing edge cases, boundary alignments, and lifecycle mechanics.
|
|
799
|
-
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.
|
|
800
|
-
|
|
801
|
-
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.
|
|
802
|
-
|
|
803
|
-
---
|
|
804
|
-
|
|
805
|
-
### 6.1 Synthetic Test Series (`tests/cross_author_slicing_synthetic_tests.mjs`)
|
|
806
|
-
|
|
807
|
-
| ID | Test Case Name | Input Structure | Operation (Author B) | Expected OOXML Structure | Invariant Assertions |
|
|
808
|
-
|:---|:---|:---|:---|:---|:---|
|
|
809
|
-
| **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)`. |
|
|
810
|
-
| **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. |
|
|
811
|
-
| **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. |
|
|
812
|
-
| **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. |
|
|
813
|
-
| **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. |
|
|
814
|
-
| **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. |
|
|
815
|
-
| **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. |
|
|
816
|
-
| **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. |
|
|
817
|
-
| **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. |
|
|
818
|
-
| **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. |
|
|
819
|
-
| **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. |
|
|
820
|
-
| **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. |
|
|
821
|
-
| **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. |
|
|
822
|
-
| **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. |
|
|
823
|
-
| **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. |
|
|
824
|
-
|
|
825
|
-
---
|
|
826
|
-
|
|
827
|
-
### 6.2 External Real-World Acceptance Series
|
|
828
|
-
|
|
829
|
-
| ID | Scenario & Source Document | Workflow & Operations | Expected Real-World Behavior | Verification Oracle |
|
|
830
|
-
|:---|:---|:---|:---|:---|
|
|
831
|
-
| **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`. |
|
|
832
|
-
| **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`. |
|
|
833
|
-
| **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. |
|
|
834
|
-
| **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. |
|
|
835
|
-
| **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. |
|
|
836
|
-
|
|
837
|
-
---
|
|
838
|
-
|
|
839
|
-
### 6.3 Test Execution Matrix
|
|
840
|
-
|
|
841
|
-
```bash
|
|
842
|
-
# 1. Run synthetic unit & boundary suite
|
|
843
|
-
node tests/cross_author_slicing_synthetic_tests.mjs
|
|
844
|
-
|
|
845
|
-
# 2. Run checked-in Word DOCX package differential suite (PKG-01..06)
|
|
846
|
-
node tests/cross_author_slicing_real_tests.mjs
|
|
847
|
-
|
|
848
|
-
# 3. Optional external acceptance: Word Desktop COM differential oracle (Windows desktop)
|
|
849
|
-
npm run test:word
|
|
850
|
-
|
|
851
|
-
# 4. Verify existing mode matrix remains 100% backward compatible
|
|
852
|
-
node tests/existing_revisions_modes_matrix_tests.mjs
|
|
853
|
-
|
|
854
|
-
# 5. Full regression check
|
|
855
|
-
npm test
|
|
856
|
-
```
|