@ansonlai/docx-redline-js 0.4.0 → 0.5.1
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 +646 -288
- package/ARCHITECTURE.md +215 -9
- package/CHANGELOG.md +319 -0
- package/README.md +604 -360
- package/adapters/config.js +45 -43
- package/bin/docx-redline.js +3 -0
- package/core/list-targeting.js +101 -110
- package/core/paragraph-targeting.js +501 -61
- package/core/paragraph-text.js +209 -0
- package/core/redline-validation.js +11 -5
- package/core/revision-cloning.js +38 -0
- package/core/types.js +64 -10
- package/core/word-xml.js +43 -15
- package/dist/docx-redline-js.esm.js +3145 -505
- package/dist/docx-redline-js.esm.js.map +4 -4
- package/dist/docx-redline-js.esm.min.js +88 -76
- package/dist/docx-redline-js.esm.min.js.map +4 -4
- package/docs/TESTING.md +342 -23
- package/docs/plans/2026-09-05-structural-revisions-and-fidelity-oracles.md +1669 -0
- package/docs/plans/2026-09-08-cross-author-revision-slicing.md +505 -0
- package/docs/plans/completed/2026-09-01-performance-and-complexity-reduction.md +669 -0
- package/docs/plans/completed/2026-09-03-agent-friendly-document-workflows.md +427 -0
- package/docs/plans/completed/2026-09-04-comment-anchor-and-cli-reliability.md +519 -0
- package/docs/plans/completed/PERFORMANCE-CONSOLIDATION.md +69 -0
- package/docs/plans/completed/structural-revision-capability-matrix.md +115 -0
- package/docs/schemas/document-operations.schema.json +109 -0
- package/docs/test-comparison-dashboard.html +4250 -7
- package/engine/formatting-removal.js +11 -2
- package/engine/oxml-engine.js +508 -336
- package/engine/reconstruction-mode.js +15 -14
- package/engine/reconstruction-writer.js +247 -142
- package/engine/route-selection.js +35 -0
- package/engine/rpr-helpers.js +334 -35
- package/engine/run-builders.js +239 -196
- package/engine/surgical-diff-application.js +407 -50
- package/engine/surgical-mode.js +142 -6
- package/engine/surgical-run-splitting.js +103 -0
- package/engine/surgical-spans.js +52 -1
- package/engine/table-cell-context.js +3 -6
- package/engine/table-mode.js +1 -1
- package/index.d.ts +234 -6
- package/index.js +24 -1
- package/node/cli.js +322 -0
- package/node/docx-document.js +302 -0
- package/node/index.d.ts +31 -0
- package/node/index.js +2 -0
- package/node/zip-archive.js +52 -0
- package/orchestration/list-markdown.js +10 -16
- package/orchestration/list-parsing.js +7 -12
- package/orchestration/list-structural-fallback.js +21 -10
- package/package.json +123 -102
- package/pipeline/content-analysis.js +12 -17
- package/pipeline/ingestion-export.js +3 -31
- package/pipeline/ingestion-paragraph.js +10 -5
- package/pipeline/list-generation.js +150 -55
- package/pipeline/list-markers.js +70 -3
- package/pipeline/serialization.js +4 -2
- package/pipeline/structured-content.js +160 -0
- package/scripts/apply_changes.mjs +27 -0
- package/scripts/benchmark-operation-session.mjs +137 -0
- package/scripts/benchmark-targeting-browser.html +74 -0
- package/scripts/benchmark-targeting-hot-paths.mjs +67 -0
- package/scripts/benchmark-test-runner.mjs +59 -0
- package/scripts/build-test-dashboard.mjs +23 -0
- package/scripts/export-lane1-fixtures.mjs +380 -0
- package/scripts/export-reredline-stress-fixtures.mjs +317 -0
- package/scripts/export-validation-fixtures.mjs +1 -1
- package/scripts/extract_text.mjs +7 -0
- package/scripts/generate-cross-author-slicing-fixtures.ps1 +256 -0
- package/scripts/generate-paragraph-boundary-fixtures.ps1 +215 -0
- package/scripts/generate-test-dashboard.mjs +362 -11
- package/scripts/lib/word-coverage-catalogue.mjs +6 -2
- package/scripts/profile-route-selection.mjs +19 -0
- package/scripts/render-agenda-multilevel.mjs +0 -5
- package/scripts/render-multilevel-cases.mjs +0 -1
- package/scripts/run-tests.mjs +107 -35
- package/scripts/word-com-corpus-suite.ps1 +3 -0
- package/scripts/word-com-differential.ps1 +64 -4
- package/scripts/word-com-suite.ps1 +3 -0
- package/services/batch-operation-orchestrator.js +513 -0
- package/services/capture-engine.js +226 -0
- package/services/comment-builders.js +23 -6
- package/services/comment-engine.js +108 -47
- package/services/comment-locator.js +187 -82
- package/services/comment-replies.js +95 -0
- package/services/document-inspection.js +258 -0
- package/services/document-operation-applier.js +372 -0
- package/services/document-operation-contract.js +345 -0
- package/services/document-operation-mutations.js +1749 -0
- package/services/document-operation-session.js +258 -0
- package/services/numbering-service.js +14 -5
- package/services/operation-heuristics.js +173 -0
- package/services/operation-preflight.js +390 -0
- package/services/receipt-collector.js +288 -0
- package/services/revision-comment-management.js +77 -5
- package/services/revision-token.js +290 -0
- package/services/standalone-docx-plumbing.js +123 -8
- package/services/standalone-operation-runner.d.ts +296 -0
- package/services/standalone-operation-runner.js +10 -1455
- package/services/table-reconciliation.js +15 -6
- package/docs/VALIDATION.md +0 -183
- package/docs/WORD-MANUAL-REVIEW.md +0 -138
- package/docs/plans/2026-09-01-performance-and-complexity-reduction.md +0 -210
- /package/docs/plans/{2026-08-30-reliability-testing-improvements.md → completed/2026-08-30-reliability-testing-improvements.md} +0 -0
|
@@ -0,0 +1,1669 @@
|
|
|
1
|
+
# Structural Revisions, Stable Addressing, and Fidelity Oracles
|
|
2
|
+
|
|
3
|
+
**Status:** Proposed — implementation-ready specification
|
|
4
|
+
**Date:** 2026-09-05
|
|
5
|
+
**Target releases:** v0.5.0–v1.0.0
|
|
6
|
+
**Priority:** Correct accepted/rejected results and document fidelity over throughput or review-pane cosmetics
|
|
7
|
+
|
|
8
|
+
## 1. Purpose
|
|
9
|
+
|
|
10
|
+
The package already performs surgical text redlining, paragraph-mark revision
|
|
11
|
+
generation, tracked run formatting, structured-content planning, strict
|
|
12
|
+
preflight, atomic live-DOM batches, and package validation. The next releases
|
|
13
|
+
should harden and extend those capabilities without creating a second mutation
|
|
14
|
+
model or weakening existing rollback and targeting guarantees.
|
|
15
|
+
|
|
16
|
+
This plan focuses on the remaining gaps:
|
|
17
|
+
|
|
18
|
+
1. Prove what is preserved before expanding mutation behavior.
|
|
19
|
+
2. Give callers precise revision-view addressing without pretending that All
|
|
20
|
+
Markup is one unambiguous string.
|
|
21
|
+
3. Make replacement metadata and insertion affinity explicit while preserving
|
|
22
|
+
structural containers.
|
|
23
|
+
4. Complete paragraph-boundary and formatting-revision semantics already
|
|
24
|
+
partially implemented.
|
|
25
|
+
5. Add package concurrency guards, dependent batch steps, and commit-aware
|
|
26
|
+
receipts.
|
|
27
|
+
6. Promote strict application targeting only through a documented major-version
|
|
28
|
+
migration.
|
|
29
|
+
|
|
30
|
+
The plan does not introduce an editor tree, layout engine, HTML round-trip, or
|
|
31
|
+
second document authority. The OOXML package and the current live DOM remain
|
|
32
|
+
authoritative.
|
|
33
|
+
|
|
34
|
+
## 2. Current Baseline
|
|
35
|
+
|
|
36
|
+
Before implementation, tests and documentation must acknowledge what exists.
|
|
37
|
+
Treat these as foundations to extend, not greenfield work:
|
|
38
|
+
|
|
39
|
+
- `engine/run-builders.js` creates `w:ins`, `w:del`, paragraph-mark revisions,
|
|
40
|
+
and `w:rPrChange` snapshots.
|
|
41
|
+
- `services/revision-comment-management.js` accepts and rejects text, move,
|
|
42
|
+
paragraph-mark, and property-change revisions.
|
|
43
|
+
- `core/paragraph-text.js` already extracts accepted and rejected revision
|
|
44
|
+
views.
|
|
45
|
+
- `services/operation-preflight.js` is strict by default and reports ambiguous
|
|
46
|
+
targets and anchors.
|
|
47
|
+
- The Node facade and CLI default to strict target application; the lower-level
|
|
48
|
+
application API remains permissive for compatibility.
|
|
49
|
+
- `DocumentOperationSession` owns one live DOM and one document-scoped
|
|
50
|
+
`RevisionIdAllocator`, with per-operation DOM and allocator savepoints.
|
|
51
|
+
- Batch execution uses an immutable start-of-batch target-reference snapshot,
|
|
52
|
+
comment-first scheduling, and one final serialization.
|
|
53
|
+
- `planStructuredReplacement` validates headings, paragraphs, lists, and tables
|
|
54
|
+
before a large structural insertion is applied.
|
|
55
|
+
- Word differential, visual, package, round-trip, and real-document tests are
|
|
56
|
+
already part of the verification stack.
|
|
57
|
+
|
|
58
|
+
### Required baseline audit
|
|
59
|
+
|
|
60
|
+
Create a short capability matrix before feature work begins. For each proposed
|
|
61
|
+
behavior, label it `implemented`, `partial`, `missing`, or `intentionally
|
|
62
|
+
unsupported`, and link the production code and regression test. At minimum the
|
|
63
|
+
matrix must cover:
|
|
64
|
+
|
|
65
|
+
- text insertion, deletion, and replacement;
|
|
66
|
+
- paragraph insertion, deletion, split, and boundary removal;
|
|
67
|
+
- run and paragraph property revisions;
|
|
68
|
+
- hyperlinks, bookmarks, comments, fields, SDTs, tabs, breaks, and notes;
|
|
69
|
+
- lists and table structural revisions;
|
|
70
|
+
- accepted/rejected text extraction;
|
|
71
|
+
- target resolution, batch savepoints, and package artifact wiring.
|
|
72
|
+
|
|
73
|
+
No phase may duplicate an implemented path under a new option without a written
|
|
74
|
+
migration and compatibility reason.
|
|
75
|
+
|
|
76
|
+
## 3. Non-Negotiable Invariants
|
|
77
|
+
|
|
78
|
+
### 3.1 Lifecycle correctness
|
|
79
|
+
|
|
80
|
+
For every generated revision:
|
|
81
|
+
|
|
82
|
+
- Accept All produces the requested final document.
|
|
83
|
+
- Reject All restores the intended source document.
|
|
84
|
+
- Selective author acceptance/rejection does not consume another author's work.
|
|
85
|
+
- Repeating acceptance or rejection is idempotent.
|
|
86
|
+
- Paragraph count, order, list membership, tables, comments, and section
|
|
87
|
+
ownership are checked in addition to plain text.
|
|
88
|
+
|
|
89
|
+
### 3.2 Preservation envelope
|
|
90
|
+
|
|
91
|
+
“Untouched” has three distinct meanings and must not be conflated:
|
|
92
|
+
|
|
93
|
+
1. **Untouched package entry:** compare the uncompressed entry payload
|
|
94
|
+
byte-for-byte. ZIP container bytes are not expected to match after repacking.
|
|
95
|
+
2. **Untouched subtree inside a modified XML part:** compare using a
|
|
96
|
+
namespace-aware canonical structural digest.
|
|
97
|
+
3. **Intentionally modified region:** validate against an operation-specific
|
|
98
|
+
mutation envelope and lifecycle oracle.
|
|
99
|
+
|
|
100
|
+
An operation must declare which XML nodes, ancestor containers, relationships,
|
|
101
|
+
content types, and companion parts it is permitted to change. Unexpected drift
|
|
102
|
+
outside that envelope fails the test.
|
|
103
|
+
|
|
104
|
+
### 3.3 Structural safety over presentation heuristics
|
|
105
|
+
|
|
106
|
+
Bookmarks, comment ranges, hyperlinks, fields, SDTs, proofing markers, drawings,
|
|
107
|
+
and revision boundaries must not be moved or deleted merely to influence how
|
|
108
|
+
Word groups a change in the Reviewing Pane.
|
|
109
|
+
|
|
110
|
+
### 3.4 Refusal over guessing
|
|
111
|
+
|
|
112
|
+
Ambiguous targets, anchors, capture selections, revision views, or structural
|
|
113
|
+
ownership must produce structured errors. Fuzzy or first-match fallback is a
|
|
114
|
+
legacy compatibility behavior, never an agent recommendation.
|
|
115
|
+
|
|
116
|
+
### 3.5 One mutation authority
|
|
117
|
+
|
|
118
|
+
All public package operations continue through the existing session, allocator,
|
|
119
|
+
savepoint, artifact, and validation paths. New functionality must not introduce
|
|
120
|
+
parallel ID allocation, string-only package mutation, or a second batch state.
|
|
121
|
+
|
|
122
|
+
## 4. Compatibility Matrix
|
|
123
|
+
|
|
124
|
+
| Initiative | Initial behavior | Classification | Default change |
|
|
125
|
+
|---|---|---|---|
|
|
126
|
+
| Fidelity and mutation-envelope oracles | Test-only | Non-breaking | None |
|
|
127
|
+
| Explicit accepted/rejected addressing | Additive option; omitted means current behavior | Non-breaking | None |
|
|
128
|
+
| Preserving existing revisions during addressed edits | New opt-in policy | Additive, high-risk | No automatic default |
|
|
129
|
+
| Paired replacement metadata | Opt-in serialization policy | Observable semantic change | Consider only after Word evidence |
|
|
130
|
+
| Explicit insertion affinity | Additive; omitted means legacy resolution | Non-breaking | No implicit `left` default |
|
|
131
|
+
| Completed paragraph-boundary handling | New support for previously refused/fallback cases | Internal semantic change | Route only proven cases |
|
|
132
|
+
| Author-aware formatting coalescing | Opt-in policy | Observable semantic change | Preserve current behavior initially |
|
|
133
|
+
| Package revision token | Additive precondition | Non-breaking | No check when omitted |
|
|
134
|
+
| Capture chaining | Additive batch feature | Non-breaking for independent batches | Dependency scheduling only when used |
|
|
135
|
+
| Mutation receipts | Additive result field | Non-breaking | Existing result fields remain authoritative |
|
|
136
|
+
| Strict lower-level application by default | Default behavior change | Breaking | v1.0.0 only |
|
|
137
|
+
|
|
138
|
+
“Non-breaking” means more than an unchanged TypeScript signature. Changes to
|
|
139
|
+
generated OOXML, formatting inheritance, accepted/rejected structure, revision
|
|
140
|
+
count, author attribution, warnings, or Word presentation are observable and
|
|
141
|
+
must be tested and documented.
|
|
142
|
+
|
|
143
|
+
## 5. Milestone 0 — Fidelity Oracle Foundation
|
|
144
|
+
|
|
145
|
+
**Target:** v0.5.0, before new mutation semantics
|
|
146
|
+
**Classification:** Non-breaking, test and validation infrastructure
|
|
147
|
+
|
|
148
|
+
### 5.1 Part inventory and mutation envelopes
|
|
149
|
+
|
|
150
|
+
Add a fixture helper that inventories every package entry and records:
|
|
151
|
+
|
|
152
|
+
- entry URI and SHA-256 of its uncompressed bytes;
|
|
153
|
+
- content type and relationship ownership;
|
|
154
|
+
- XML root namespace and canonical digest for XML parts;
|
|
155
|
+
- known volatile metadata that a test intentionally ignores.
|
|
156
|
+
|
|
157
|
+
Each operation family declares allowed effects. Examples:
|
|
158
|
+
|
|
159
|
+
- Text-only body edit: `word/document.xml` only.
|
|
160
|
+
- List creation: document XML, numbering XML, numbering relationship, and
|
|
161
|
+
content type when absent.
|
|
162
|
+
- Comment creation: document XML, comments XML, comments relationship, and
|
|
163
|
+
content type when absent.
|
|
164
|
+
- Header edit: selected header part only, plus artifacts explicitly required by
|
|
165
|
+
that edit.
|
|
166
|
+
|
|
167
|
+
The oracle must fail when an undeclared part changes, even if the resulting DOCX
|
|
168
|
+
opens successfully.
|
|
169
|
+
|
|
170
|
+
### 5.2 Canonical subtree comparison
|
|
171
|
+
|
|
172
|
+
Do not implement canonicalization by simple prefix replacement. Use a proven XML
|
|
173
|
+
canonicalization implementation where possible. If a local OOXML subset is
|
|
174
|
+
necessary, it must:
|
|
175
|
+
|
|
176
|
+
- compare expanded names, not literal prefixes;
|
|
177
|
+
- sort attributes by namespace URI and local name;
|
|
178
|
+
- retain child order;
|
|
179
|
+
- preserve text, tabs, breaks, and `xml:space` semantics;
|
|
180
|
+
- preserve or correctly rewrite QName-valued attributes such as
|
|
181
|
+
`mc:Ignorable`;
|
|
182
|
+
- distinguish absent properties from explicit off-values when Word does;
|
|
183
|
+
- normalize only syntax that is semantically equivalent.
|
|
184
|
+
|
|
185
|
+
Untouched subtree identity cannot rely solely on `w14:paraId`: IDs may be absent
|
|
186
|
+
or duplicated in real documents. Match nodes using a combination of stable IDs,
|
|
187
|
+
canonical fingerprints, ancestor context, and ordered-neighbor evidence. Exclude
|
|
188
|
+
the mutation target, its structurally affected ancestors, and any explicitly
|
|
189
|
+
declared sibling boundary from the untouched set.
|
|
190
|
+
|
|
191
|
+
### 5.3 Required oracles
|
|
192
|
+
|
|
193
|
+
Add tests for:
|
|
194
|
+
|
|
195
|
+
- accepted and rejected text and structure;
|
|
196
|
+
- revision ID uniqueness across every revision element supported by validation;
|
|
197
|
+
- comment and numbering artifact integrity;
|
|
198
|
+
- untouched entry byte equality;
|
|
199
|
+
- untouched subtree canonical equality;
|
|
200
|
+
- save-and-reopen semantic equality;
|
|
201
|
+
- no-op and atomic rollback returning the exact original XML/package bytes;
|
|
202
|
+
- Word open, Accept All, Reject All, and save/reopen behavior.
|
|
203
|
+
|
|
204
|
+
Reviewing Pane grouping is a visual compatibility observation, not a schema or
|
|
205
|
+
lifecycle oracle.
|
|
206
|
+
|
|
207
|
+
### Acceptance gate
|
|
208
|
+
|
|
209
|
+
No later phase begins until representative paragraph, list, table, comment,
|
|
210
|
+
header/footer, field, hyperlink, bookmark, drawing, and SDT fixtures pass the
|
|
211
|
+
new preservation envelope.
|
|
212
|
+
|
|
213
|
+
## 6. Milestone 1 — Revision-View Addressing
|
|
214
|
+
|
|
215
|
+
**Target:** v0.5.0
|
|
216
|
+
**Classification:** Additive, with mutation support gated separately
|
|
217
|
+
|
|
218
|
+
### 6.1 Use semantic view names
|
|
219
|
+
|
|
220
|
+
Expose revision addressing as:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
export type RevisionView = 'accepted' | 'rejected';
|
|
224
|
+
|
|
225
|
+
export interface ParagraphTargetDescriptor {
|
|
226
|
+
exactText?: string;
|
|
227
|
+
paragraphId?: string;
|
|
228
|
+
index?: number;
|
|
229
|
+
occurrence?: number;
|
|
230
|
+
fingerprint?: string;
|
|
231
|
+
revisionView?: RevisionView; // Defaults to accepted/current behavior.
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Do not call accepted text “No Markup or Original.” Original/rejected and
|
|
236
|
+
accepted/final are different views. Do not expose a single `tracked` string made
|
|
237
|
+
by concatenating deleted and inserted text; a replacement has two overlapping
|
|
238
|
+
histories, not one stable user-facing coordinate line.
|
|
239
|
+
|
|
240
|
+
For inspection that needs revision history, return segments instead:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
interface RevisionTextSegment {
|
|
244
|
+
text: string;
|
|
245
|
+
kind: 'baseline' | 'insertion' | 'deletion' | 'move_from' | 'move_to';
|
|
246
|
+
author?: string;
|
|
247
|
+
revisionId?: string;
|
|
248
|
+
acceptedStart: number | null;
|
|
249
|
+
rejectedStart: number | null;
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Offsets use JavaScript UTF-16 code units. DOM mapping must cover `w:t`,
|
|
254
|
+
`w:delText`, tabs, breaks, carriage returns, soft/no-break hyphens, entities,
|
|
255
|
+
and revision/move ancestry.
|
|
256
|
+
|
|
257
|
+
### 6.2 Separate inspection from mutation policy
|
|
258
|
+
|
|
259
|
+
Reading or locating text in a rejected view does not automatically authorize
|
|
260
|
+
editing historical deletion markup. Initial scope:
|
|
261
|
+
|
|
262
|
+
- Inspection and preflight support both views.
|
|
263
|
+
- Comments may target a supported revision range only after schema and Word
|
|
264
|
+
round-trip fixtures prove the anchor representation.
|
|
265
|
+
- Text mutation of another pending deletion or insertion is refused until an
|
|
266
|
+
explicit author-aware lifecycle policy exists.
|
|
267
|
+
- The existing `reject-input` and `accept-all-first` policies remain unchanged.
|
|
268
|
+
- If a future `preserve-input` policy is added, it must prohibit illegal nested
|
|
269
|
+
revisions and define same-author coalescing separately.
|
|
270
|
+
|
|
271
|
+
### Acceptance criteria
|
|
272
|
+
|
|
273
|
+
- The same descriptor resolves deterministically in accepted and rejected
|
|
274
|
+
views.
|
|
275
|
+
- Duplicate text remains ambiguous within the selected view.
|
|
276
|
+
- Emoji, entities, tabs, breaks, moves, and mixed insert/delete replacements map
|
|
277
|
+
to the correct DOM boundaries.
|
|
278
|
+
- Omitting `revisionView` produces byte-for-byte current behavior.
|
|
279
|
+
|
|
280
|
+
## 7. Milestone 2 — Replacement Pairing and Insertion Affinity
|
|
281
|
+
|
|
282
|
+
**Target:** v0.5.0 experimental; stabilize in v0.6.0
|
|
283
|
+
**Classification:** Opt-in observable serialization behavior
|
|
284
|
+
|
|
285
|
+
### 7.1 Paired replacement metadata
|
|
286
|
+
|
|
287
|
+
Model a delete-followed-by-insert diff as one internal replacement event when
|
|
288
|
+
both sides share a safe parent and no structural boundary must be crossed. The
|
|
289
|
+
event allocates distinct revision IDs but may share one author and timestamp.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
interface ReplacementRevisionEvent {
|
|
293
|
+
deletionId: string;
|
|
294
|
+
insertionId: string;
|
|
295
|
+
author: string;
|
|
296
|
+
date: string;
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Rules:
|
|
301
|
+
|
|
302
|
+
- `w:del` and `w:ins` IDs remain distinct and document-scoped.
|
|
303
|
+
- Shared metadata is allocated once for the event.
|
|
304
|
+
- Empty runs created solely by splitting are removed.
|
|
305
|
+
- `xml:space="preserve"` is based on emitted text boundaries; consecutive
|
|
306
|
+
interior spaces are also preserved defensively.
|
|
307
|
+
- Pairing never crosses a hyperlink, field, SDT, comment, bookmark, move, or
|
|
308
|
+
incompatible revision parent.
|
|
309
|
+
- Required structural markers may remain between changes. Fidelity wins over
|
|
310
|
+
visual grouping.
|
|
311
|
+
- Word may present a paired event as a combined replacement, but the public API
|
|
312
|
+
does not guarantee a particular balloon shape.
|
|
313
|
+
|
|
314
|
+
### 7.2 Separate formatting affinity from container affinity
|
|
315
|
+
|
|
316
|
+
One `left | right` flag is insufficient for every boundary. Define:
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
interface InsertionAffinity {
|
|
320
|
+
formatting?: 'left' | 'right' | 'none';
|
|
321
|
+
hyperlink?: 'inside' | 'outside' | 'preserve';
|
|
322
|
+
revision?: 'coalesce_same_author' | 'separate';
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Bookmarks and comments are ranges represented by markers, not formatting
|
|
327
|
+
wrappers. Their membership is determined by the insertion point relative to
|
|
328
|
+
start/end markers and must be tested independently.
|
|
329
|
+
|
|
330
|
+
When affinity is omitted, retain the existing boundary behavior. Do not claim
|
|
331
|
+
that a universal implicit `left` policy matches Word in every container.
|
|
332
|
+
|
|
333
|
+
### Acceptance criteria
|
|
334
|
+
|
|
335
|
+
- Accepted and rejected content is identical with pairing enabled or disabled.
|
|
336
|
+
- Pairing never removes or reorders structural markers.
|
|
337
|
+
- Every formatting/hyperlink/comment/bookmark boundary combination has an
|
|
338
|
+
explicit expected DOM position.
|
|
339
|
+
- Word-version visual fixtures record presentation differences without making
|
|
340
|
+
them correctness requirements.
|
|
341
|
+
|
|
342
|
+
## 8. Milestone 3 — Complete Paragraph-Boundary Semantics
|
|
343
|
+
|
|
344
|
+
**Target:** v0.6.0
|
|
345
|
+
**Classification:** Internal semantic change for newly supported cases
|
|
346
|
+
|
|
347
|
+
The paragraph-mark primitives already exist. This phase completes the operation
|
|
348
|
+
matrix and removes unsafe reconstruction only where native boundary revisions
|
|
349
|
+
are proven.
|
|
350
|
+
|
|
351
|
+
### 8.1 Boundary removal versus paragraph deletion
|
|
352
|
+
|
|
353
|
+
These operations must remain distinct:
|
|
354
|
+
|
|
355
|
+
1. **Remove the boundary between A and B while retaining both texts**
|
|
356
|
+
- Keep both physical paragraphs in the pending-revision document.
|
|
357
|
+
- Mark the paragraph mark ending A with `w:pPr/w:rPr/w:del`.
|
|
358
|
+
- Do not mark B's retained text as deleted.
|
|
359
|
+
- Accept joins the contents; Reject restores the boundary.
|
|
360
|
+
2. **Delete Paragraph B and its text**
|
|
361
|
+
- Track B's content deletion.
|
|
362
|
+
- Track the relevant paragraph mark so Reject does not leave a ghost
|
|
363
|
+
paragraph, bullet, or number.
|
|
364
|
+
- Accept removes B; Reject restores B exactly.
|
|
365
|
+
|
|
366
|
+
For boundary acceptance, do not assume that A's paragraph properties win. The
|
|
367
|
+
remaining paragraph mark normally determines final paragraph formatting. Build
|
|
368
|
+
native Word fixtures for conflicting A/B styles, numbering, spacing, and
|
|
369
|
+
section properties, then encode the observed ownership rule before implementing
|
|
370
|
+
the merge algorithm.
|
|
371
|
+
|
|
372
|
+
### 8.2 Paragraph split
|
|
373
|
+
|
|
374
|
+
When splitting A into A and B:
|
|
375
|
+
|
|
376
|
+
- Split at a structurally safe run boundary.
|
|
377
|
+
- Create B with the correct inherited paragraph properties.
|
|
378
|
+
- Mark the newly inserted boundary—the paragraph mark ending A—with
|
|
379
|
+
`w:pPr/w:rPr/w:ins`.
|
|
380
|
+
- Move retained trailing runs without incorrectly marking their text inserted.
|
|
381
|
+
- Accept keeps A and B; Reject moves B's retained content back into A and
|
|
382
|
+
removes the inserted boundary.
|
|
383
|
+
|
|
384
|
+
### 8.3 Unsafe cases
|
|
385
|
+
|
|
386
|
+
Refuse or retain the current proven fallback when the boundary owns or crosses:
|
|
387
|
+
|
|
388
|
+
- `w:sectPr` with uncertain ownership;
|
|
389
|
+
- table-cell terminal paragraphs;
|
|
390
|
+
- incompatible list definitions;
|
|
391
|
+
- overlapping bookmarks/comments;
|
|
392
|
+
- fields, SDTs, drawings, notes, or unsupported revision nesting.
|
|
393
|
+
|
|
394
|
+
### Acceptance criteria
|
|
395
|
+
|
|
396
|
+
- A full matrix covers split, join, delete, list-item insertion/deletion, and
|
|
397
|
+
boundaries adjacent to structural content.
|
|
398
|
+
- Word and internal Accept All/Reject All agree on paragraph order, text,
|
|
399
|
+
paragraph properties, numbering, and section ownership.
|
|
400
|
+
- Existing paragraph-mark regression tests remain valid and are expanded rather
|
|
401
|
+
than replaced.
|
|
402
|
+
|
|
403
|
+
## 9. Milestone 4 — Complete Tracked Formatting Semantics
|
|
404
|
+
|
|
405
|
+
**Target:** v0.6.0
|
|
406
|
+
**Classification:** Existing behavior plus opt-in author-aware coalescing
|
|
407
|
+
|
|
408
|
+
`w:rPrChange` generation and property-change lifecycle handling already exist.
|
|
409
|
+
This phase closes coverage gaps and defines a public formatting operation model.
|
|
410
|
+
|
|
411
|
+
### 9.1 Operation contracts
|
|
412
|
+
|
|
413
|
+
Distinguish character and paragraph formatting:
|
|
414
|
+
|
|
415
|
+
```ts
|
|
416
|
+
interface CharacterFormatOperation {
|
|
417
|
+
type: 'format';
|
|
418
|
+
target: ParagraphTargetDescriptor;
|
|
419
|
+
textToFormat: string;
|
|
420
|
+
properties: {
|
|
421
|
+
bold?: boolean;
|
|
422
|
+
italic?: boolean;
|
|
423
|
+
underline?: boolean;
|
|
424
|
+
strike?: boolean;
|
|
425
|
+
highlight?: string | null;
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
interface ParagraphFormatOperation {
|
|
430
|
+
type: 'paragraph-format';
|
|
431
|
+
target: ParagraphTargetDescriptor;
|
|
432
|
+
properties: {
|
|
433
|
+
alignment?: 'left' | 'center' | 'right' | 'both';
|
|
434
|
+
keepNext?: boolean;
|
|
435
|
+
keepLines?: boolean;
|
|
436
|
+
pageBreakBefore?: boolean;
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
The property set may expand only with prior-state snapshot and lifecycle tests.
|
|
442
|
+
The previous-state child of `w:rPrChange` or `w:pPrChange` contains the complete
|
|
443
|
+
direct property state required to reverse the change, excluding nested change
|
|
444
|
+
records.
|
|
445
|
+
|
|
446
|
+
### 9.2 Author-aware policy
|
|
447
|
+
|
|
448
|
+
Preserve current formatting revision behavior by default. Add an experimental
|
|
449
|
+
policy only after selective-author tests:
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
formattingRevisionPolicy?: 'always' | 'coalesce-own-insertion';
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
- `always`: current behavior; create the appropriate property revision.
|
|
456
|
+
- `coalesce-own-insertion`: formatting inside the active author's pending
|
|
457
|
+
insertion updates that insertion directly.
|
|
458
|
+
- Formatting baseline text or another author's insertion creates a separate
|
|
459
|
+
property revision attributed to the active author.
|
|
460
|
+
|
|
461
|
+
### Acceptance criteria
|
|
462
|
+
|
|
463
|
+
- Bold/italic/underline/strike/highlight on/off transitions round-trip.
|
|
464
|
+
- Paragraph-property changes round-trip independently of run properties.
|
|
465
|
+
- Same-author and different-author pending insertions are tested separately.
|
|
466
|
+
- Run splitting never duplicates an existing `w:rPrChange` ID.
|
|
467
|
+
- Omitting the new policy retains current serialized behavior.
|
|
468
|
+
|
|
469
|
+
## 10. Milestone 5 — Versioned Concurrency Tokens
|
|
470
|
+
|
|
471
|
+
**Target:** v0.7.0
|
|
472
|
+
**Classification:** Additive precondition
|
|
473
|
+
|
|
474
|
+
### 10.1 Two token scopes
|
|
475
|
+
|
|
476
|
+
Do not describe a three-part hash as a complete package revision.
|
|
477
|
+
|
|
478
|
+
```ts
|
|
479
|
+
interface RevisionToken {
|
|
480
|
+
algorithm: 'sha256';
|
|
481
|
+
version: 1;
|
|
482
|
+
scope: 'document-parts' | 'package';
|
|
483
|
+
value: string;
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
- `document-parts`: hash a documented, sorted list of provided part names and
|
|
488
|
+
exact strings/bytes. Suitable for lower-level APIs.
|
|
489
|
+
- `package`: hash every relevant ZIP entry name plus its uncompressed bytes.
|
|
490
|
+
Suitable for `openDocx` and CLI workflows.
|
|
491
|
+
|
|
492
|
+
Use length-prefixed binary framing so part boundaries cannot collide. Include
|
|
493
|
+
the token version and scope in the digest input. A package token necessarily
|
|
494
|
+
requires reading the ZIP directory and selected entries; it can still reject
|
|
495
|
+
before any mutation is staged.
|
|
496
|
+
|
|
497
|
+
### 10.2 Check location
|
|
498
|
+
|
|
499
|
+
- Inspection returns the token and the covered part list.
|
|
500
|
+
- Apply recomputes it from the exact incoming state before creating mutation
|
|
501
|
+
savepoints or package artifacts.
|
|
502
|
+
- Mismatch returns `REVISION_MISMATCH`, `hasChanges: false`, no artifacts, and
|
|
503
|
+
the original bytes.
|
|
504
|
+
- A token from one scope cannot satisfy another scope.
|
|
505
|
+
|
|
506
|
+
### Acceptance criteria
|
|
507
|
+
|
|
508
|
+
- Changes to document text, comments, numbering, relationships, headers,
|
|
509
|
+
footers, styles, notes, or media are detected by the package token.
|
|
510
|
+
- ZIP recompression without uncompressed entry changes does not invalidate a
|
|
511
|
+
package token.
|
|
512
|
+
- Omitted preconditions preserve current behavior.
|
|
513
|
+
|
|
514
|
+
## 11. Milestone 6 — Dependency-Aware Capture Chaining
|
|
515
|
+
|
|
516
|
+
**Target:** v0.7.0
|
|
517
|
+
**Classification:** Additive batch behavior when captures are used
|
|
518
|
+
|
|
519
|
+
### 11.1 Contract
|
|
520
|
+
|
|
521
|
+
```json
|
|
522
|
+
[
|
|
523
|
+
{
|
|
524
|
+
"operationId": "insert-privacy-section",
|
|
525
|
+
"type": "replace",
|
|
526
|
+
"target": { "exactText": "11. Term and Termination" },
|
|
527
|
+
"modified": "11. Term and Termination\n\n12. Data Privacy\nService Provider shall maintain the required controls.",
|
|
528
|
+
"structuredContent": true,
|
|
529
|
+
"captureKey": "privacy-section"
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
"operationId": "comment-controls",
|
|
533
|
+
"type": "comment",
|
|
534
|
+
"target": {
|
|
535
|
+
"captureRef": "privacy-section",
|
|
536
|
+
"select": "required controls"
|
|
537
|
+
},
|
|
538
|
+
"commentContent": "Confirm the evidence and audit cadence."
|
|
539
|
+
}
|
|
540
|
+
]
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### 11.2 Dependency scheduling
|
|
544
|
+
|
|
545
|
+
Build a dependency graph before execution:
|
|
546
|
+
|
|
547
|
+
- A `captureRef` depends on the operation exporting its `captureKey`.
|
|
548
|
+
- Reject duplicate keys, missing references, forward references not resolvable
|
|
549
|
+
by the graph, and cycles.
|
|
550
|
+
- Preserve source order among independent operations.
|
|
551
|
+
- Retain comment-first scheduling only when it does not violate dependencies.
|
|
552
|
+
- `executionOrder` reports actual topological order.
|
|
553
|
+
|
|
554
|
+
Static preflight reports capture-dependent targets as `deferred`, not missing.
|
|
555
|
+
An optional mutation preview may execute against an isolated clone to resolve
|
|
556
|
+
all dynamic selections without committing output.
|
|
557
|
+
|
|
558
|
+
### 11.3 Stable capture representation
|
|
559
|
+
|
|
560
|
+
Do not store raw DOM nodes as the durable capture value. Savepoint restoration
|
|
561
|
+
replaces the DOM tree and invalidates those references. A capture records a
|
|
562
|
+
serializable session-local identity:
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
interface CapturedEntity {
|
|
566
|
+
captureKey: string;
|
|
567
|
+
operationIndex: number;
|
|
568
|
+
kind: 'paragraph' | 'range' | 'table' | 'list';
|
|
569
|
+
generatedParagraphIds: string[];
|
|
570
|
+
fingerprints: string[];
|
|
571
|
+
expectedText: string[];
|
|
572
|
+
structuralPathHints: string[];
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
Resolution reacquires nodes from the current session index and verifies text and
|
|
577
|
+
fingerprint before mutation. The capture table participates in every savepoint:
|
|
578
|
+
failed/no-op steps restore its exact prior state, and atomic rollback discards it.
|
|
579
|
+
If a captured entity is replaced later, the executor must either update the
|
|
580
|
+
capture deterministically or return `CAPTURE_STALE`.
|
|
581
|
+
|
|
582
|
+
### 11.4 Structured insertion guidance
|
|
583
|
+
|
|
584
|
+
Large generated content must be analyzed before execution:
|
|
585
|
+
|
|
586
|
+
1. Run `planStructuredReplacement`.
|
|
587
|
+
2. Refuse malformed tables or ambiguous block boundaries.
|
|
588
|
+
3. Emit separate heading, paragraph, list, and table nodes.
|
|
589
|
+
4. Capture block identities, not a flattened combined string.
|
|
590
|
+
5. Apply dependent comments or formatting only after those nodes exist.
|
|
591
|
+
|
|
592
|
+
### Acceptance criteria
|
|
593
|
+
|
|
594
|
+
- Insert-then-comment and insert-then-format work in one atomic batch.
|
|
595
|
+
- Independent comments retain current comment-first safety.
|
|
596
|
+
- Savepoint restoration never leaves stale node references or captures.
|
|
597
|
+
- Failure of a dependent step rolls back the entire atomic batch and all
|
|
598
|
+
artifacts.
|
|
599
|
+
|
|
600
|
+
## 12. Milestone 7 — Commit-Aware Mutation Receipts
|
|
601
|
+
|
|
602
|
+
**Target:** v0.7.0
|
|
603
|
+
**Classification:** Additive result metadata
|
|
604
|
+
|
|
605
|
+
Receipts describe both what was attempted and what survived the transaction:
|
|
606
|
+
|
|
607
|
+
```ts
|
|
608
|
+
interface MutationReceipt {
|
|
609
|
+
operationIndex: number;
|
|
610
|
+
operationId?: string;
|
|
611
|
+
attemptedDisposition: 'applied' | 'no_change' | 'refused' | 'not_attempted';
|
|
612
|
+
finalDisposition: 'applied' | 'no_change' | 'refused' | 'rolled_back' | 'not_attempted';
|
|
613
|
+
committed: boolean;
|
|
614
|
+
authorUsed?: string;
|
|
615
|
+
revisionItems: Array<{
|
|
616
|
+
id: string;
|
|
617
|
+
kind: 'ins' | 'del' | 'move_from' | 'move_to' | 'rPrChange' | 'pPrChange' | 'structural';
|
|
618
|
+
partName: string;
|
|
619
|
+
}>;
|
|
620
|
+
commentIds: string[];
|
|
621
|
+
numberingIds: string[];
|
|
622
|
+
relationshipIds: string[];
|
|
623
|
+
affectedTargets: ResolvedDocumentTarget[];
|
|
624
|
+
warnings: string[];
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
Rules:
|
|
629
|
+
|
|
630
|
+
- Existing result fields and status strings remain unchanged.
|
|
631
|
+
- Collectors are scoped to the operation savepoint.
|
|
632
|
+
- IDs consumed by a failed/no-op step are absent after restore.
|
|
633
|
+
- A later atomic failure changes earlier provisional receipts to
|
|
634
|
+
`finalDisposition: 'rolled_back'` and `committed: false`.
|
|
635
|
+
- No IDs are presented as durable handles unless the transaction commits.
|
|
636
|
+
- Receipt generation must not add paragraph IDs or otherwise mutate the
|
|
637
|
+
document solely for telemetry.
|
|
638
|
+
|
|
639
|
+
### Acceptance criteria
|
|
640
|
+
|
|
641
|
+
- Receipts enumerate exact committed revision, comment, numbering, and
|
|
642
|
+
relationship allocations.
|
|
643
|
+
- No-op, refusal, stopped execution, per-step rollback, and whole-batch rollback
|
|
644
|
+
are distinguishable.
|
|
645
|
+
- Receipt contents agree with a fresh parse of the committed package.
|
|
646
|
+
|
|
647
|
+
## 13. Milestone 8 — Strict Application Migration
|
|
648
|
+
|
|
649
|
+
**Target:** warning cycle in v0.7.x; default change in v1.0.0
|
|
650
|
+
**Classification:** Breaking default change
|
|
651
|
+
|
|
652
|
+
Preflight is already strict by default, and the Node facade/CLI already prefer
|
|
653
|
+
strict application. The remaining migration concerns callers of permissive
|
|
654
|
+
lower-level application APIs.
|
|
655
|
+
|
|
656
|
+
### Migration
|
|
657
|
+
|
|
658
|
+
- v0.7.x: ambiguous permissive resolution succeeds only where legacy behavior
|
|
659
|
+
currently permits it and emits `AMBIGUOUS_TARGET_HEURISTIC_USED`.
|
|
660
|
+
- Documentation and generated agent examples always use strict application.
|
|
661
|
+
- v1.0.0: lower-level application defaults to strict.
|
|
662
|
+
- An explicit `strictTargets: false` compatibility escape hatch may remain for
|
|
663
|
+
one major release, with warnings.
|
|
664
|
+
|
|
665
|
+
Strict descriptors are conjunctive assertions, not a priority list: when both
|
|
666
|
+
`paragraphId` and `exactText` are supplied, both must describe the same current
|
|
667
|
+
target. An index or occurrence alone is not a concurrency guarantee.
|
|
668
|
+
|
|
669
|
+
### Acceptance criteria
|
|
670
|
+
|
|
671
|
+
- Zero, duplicate, descriptor-conflict, stale fingerprint, and anchor ambiguity
|
|
672
|
+
have distinct errors and candidate diagnostics.
|
|
673
|
+
- Preflight and strict application resolve the same target or both refuse.
|
|
674
|
+
- CLI and Node package workflows never silently choose candidate one.
|
|
675
|
+
|
|
676
|
+
## 14. Verification Matrix
|
|
677
|
+
|
|
678
|
+
Every milestone runs:
|
|
679
|
+
|
|
680
|
+
```bash
|
|
681
|
+
npm test
|
|
682
|
+
npm run test:isolation
|
|
683
|
+
npm run check:types
|
|
684
|
+
node scripts/export-validation-fixtures.mjs
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
Risk-specific gates:
|
|
688
|
+
|
|
689
|
+
| Change | Required additional verification |
|
|
690
|
+
|---|---|
|
|
691
|
+
| Canonical fidelity | New fidelity-oracle suite plus real package corpus |
|
|
692
|
+
| Revision addressing | Accepted/rejected view, entity, Unicode, move, and mixed-revision fixtures |
|
|
693
|
+
| Pairing/affinity | Hyperlink, field, SDT, bookmark, comment, and multi-author fixtures |
|
|
694
|
+
| Paragraph boundaries | Internal round trip plus desktop Word Accept/Reject and visual review |
|
|
695
|
+
| Formatting revisions | Selective-author Accept/Reject and complete previous-state snapshots |
|
|
696
|
+
| Concurrency tokens | Entry mutation/recompression/token-version matrix |
|
|
697
|
+
| Capture chaining | Dependency graph, savepoint, stale capture, and atomic rollback matrix |
|
|
698
|
+
| Receipts | Fresh-package audit of every reported committed ID |
|
|
699
|
+
|
|
700
|
+
Desktop Word automation proves open/save and accepted/rejected outcomes. Visual
|
|
701
|
+
inspection remains required for layout, paragraph marks, change bars, and
|
|
702
|
+
Reviewing Pane presentation because those UI details are not fully exposed as a
|
|
703
|
+
stable COM assertion surface.
|
|
704
|
+
|
|
705
|
+
## 15. Release Sequence
|
|
706
|
+
|
|
707
|
+
### v0.5.0 — Evidence and addressing
|
|
708
|
+
|
|
709
|
+
- Current-capability audit.
|
|
710
|
+
- Mutation envelopes and fidelity oracles.
|
|
711
|
+
- Accepted/rejected revision-view inspection and targeting.
|
|
712
|
+
- Experimental paired replacement metadata and explicit insertion affinity.
|
|
713
|
+
|
|
714
|
+
### v0.6.0 — Structural lifecycle completion
|
|
715
|
+
|
|
716
|
+
- Proven paragraph boundary join/split/delete cases.
|
|
717
|
+
- Complete character and paragraph formatting revision contracts.
|
|
718
|
+
- Expanded selective-author and native Word lifecycle fixtures.
|
|
719
|
+
|
|
720
|
+
### v0.7.0 — Transactional agent workflows
|
|
721
|
+
|
|
722
|
+
- Versioned document/package concurrency tokens.
|
|
723
|
+
- Dependency-aware capture chaining.
|
|
724
|
+
- Commit-aware mutation receipts.
|
|
725
|
+
- Permissive-target deprecation warnings.
|
|
726
|
+
|
|
727
|
+
### v1.0.0 — Strict defaults
|
|
728
|
+
|
|
729
|
+
- Strict lower-level application by default.
|
|
730
|
+
- Compatibility flags and deprecated helpers reviewed under normal semver rules.
|
|
731
|
+
- Full fidelity, lifecycle, package, and Word validation gates required for
|
|
732
|
+
release.
|
|
733
|
+
|
|
734
|
+
## 16. Exit Criteria
|
|
735
|
+
|
|
736
|
+
This program is complete when:
|
|
737
|
+
|
|
738
|
+
1. Every claimed existing and new capability has production-code and regression-
|
|
739
|
+
test evidence.
|
|
740
|
+
2. Every operation declares and stays within its mutation envelope.
|
|
741
|
+
3. Accepted and rejected results match internal and desktop Word outcomes for
|
|
742
|
+
text and structure.
|
|
743
|
+
4. Large structured insertions remain headings, paragraphs, lists, and tables
|
|
744
|
+
rather than flattened text.
|
|
745
|
+
5. Dependent batch steps use stable captures and preserve atomic rollback.
|
|
746
|
+
6. Receipts never report rolled-back IDs as committed.
|
|
747
|
+
7. Stale package mutations fail before document state is changed.
|
|
748
|
+
8. Ambiguous agent operations fail closed under all recommended public workflows.
|
|
749
|
+
|
|
750
|
+
## 17. Coding-Agent Execution Protocol
|
|
751
|
+
|
|
752
|
+
This section is normative. An implementation agent should follow it even when a
|
|
753
|
+
work package appears simple.
|
|
754
|
+
|
|
755
|
+
### 17.1 Unit of work
|
|
756
|
+
|
|
757
|
+
Implement exactly one work package from Section 18 at a time. Do not combine
|
|
758
|
+
unrelated packages, opportunistically refactor adjacent modules, or change a
|
|
759
|
+
default before the package that explicitly authorizes that change.
|
|
760
|
+
|
|
761
|
+
For each work package:
|
|
762
|
+
|
|
763
|
+
1. Read `AGENTS.md`, `ARCHITECTURE.md`, `docs/TESTING.md`, and every production
|
|
764
|
+
file listed under **Files to inspect**.
|
|
765
|
+
2. Run the listed baseline tests before editing. If they fail, record the
|
|
766
|
+
pre-existing failure and stop unless the task explicitly includes repairing
|
|
767
|
+
it.
|
|
768
|
+
3. Add the smallest failing regression test that expresses the requested
|
|
769
|
+
invariant.
|
|
770
|
+
4. Implement through existing helpers and session state. Do not create a second
|
|
771
|
+
parser, allocator, target resolver, or package writer.
|
|
772
|
+
5. Run the focused tests, then the shared gates in Section 17.5.
|
|
773
|
+
6. Update public declarations, schemas, README, architecture, testing guidance,
|
|
774
|
+
and changelog only when the work package changes those contracts.
|
|
775
|
+
7. Report files changed, tests run, compatibility impact, and any deferred
|
|
776
|
+
unsafe cases.
|
|
777
|
+
|
|
778
|
+
### 17.2 Mutation rules
|
|
779
|
+
|
|
780
|
+
- Create Word elements through `createWordElement`.
|
|
781
|
+
- Create revision metadata through the document-scoped allocator path already
|
|
782
|
+
used by `createRevisionMetadata` and `RevisionIdAllocator`.
|
|
783
|
+
- Never allocate revision IDs with module globals, timestamps, random numbers,
|
|
784
|
+
or a fresh per-operation allocator inside a batch.
|
|
785
|
+
- Mutate the live session DOM. String serialization is a boundary operation,
|
|
786
|
+
not a mutation technique.
|
|
787
|
+
- Retain per-operation DOM, allocator, runtime-context, artifact, capture, and
|
|
788
|
+
receipt savepoints.
|
|
789
|
+
- Never move or delete unknown OOXML merely because the current renderer ignores
|
|
790
|
+
it.
|
|
791
|
+
- Preserve source whitespace exactly. Replacement text is caller data and must
|
|
792
|
+
not be trimmed, collapsed, Markdown-normalized, or line-ending-normalized.
|
|
793
|
+
- Keep accepted/rejected text extraction centralized in
|
|
794
|
+
`core/paragraph-text.js`.
|
|
795
|
+
- Keep target selection centralized in `core/paragraph-targeting.js` and
|
|
796
|
+
`services/operation-preflight.js`.
|
|
797
|
+
- Keep package mutations in the Node/package layer. Browser-safe root imports
|
|
798
|
+
must not acquire Node built-ins.
|
|
799
|
+
|
|
800
|
+
### 17.3 Error behavior
|
|
801
|
+
|
|
802
|
+
Expected refusal is not an exception. Return the established error shape:
|
|
803
|
+
|
|
804
|
+
```ts
|
|
805
|
+
{
|
|
806
|
+
hasChanges: false,
|
|
807
|
+
status: 'error',
|
|
808
|
+
error: {
|
|
809
|
+
code: 'STABLE_MACHINE_CODE',
|
|
810
|
+
message: 'Actionable human-readable explanation.'
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
An atomic batch failure additionally returns the original document, empty
|
|
816
|
+
uncommitted artifacts, `rolledBack: true`, and per-operation attempt results.
|
|
817
|
+
Never convert a structured refusal into `no_change`.
|
|
818
|
+
|
|
819
|
+
New error codes introduced by this plan:
|
|
820
|
+
|
|
821
|
+
| Code | Meaning |
|
|
822
|
+
|---|---|
|
|
823
|
+
| `UNSUPPORTED_REVISION_VIEW_MUTATION` | Requested view can be inspected but not safely mutated |
|
|
824
|
+
| `UNSAFE_REVISION_NESTING` | Mutation would create unsupported nested/overlapping revisions |
|
|
825
|
+
| `UNSUPPORTED_INSERTION_AFFINITY` | Requested container placement is not legal or deterministic |
|
|
826
|
+
| `UNSAFE_PARAGRAPH_BOUNDARY` | Split/join crosses unsupported structural ownership |
|
|
827
|
+
| `REVISION_MISMATCH` | Expected package/document token does not match current state |
|
|
828
|
+
| `REVISION_TOKEN_SCOPE_MISMATCH` | Token scope/version cannot guard the selected API |
|
|
829
|
+
| `DUPLICATE_CAPTURE_KEY` | More than one step exports the same capture key |
|
|
830
|
+
| `CAPTURE_NOT_FOUND` | No step exports the requested capture |
|
|
831
|
+
| `CAPTURE_DEPENDENCY_CYCLE` | Capture dependencies cannot be topologically ordered |
|
|
832
|
+
| `AMBIGUOUS_CAPTURE_SELECTION` | `select` matches multiple locations inside a capture |
|
|
833
|
+
| `CAPTURE_STALE` | Captured identity no longer resolves to the verified current entity |
|
|
834
|
+
|
|
835
|
+
Add codes to runtime validation, TypeScript declarations, JSON schema, README,
|
|
836
|
+
and tests in the same work package that first emits them.
|
|
837
|
+
|
|
838
|
+
### 17.4 Public-contract propagation checklist
|
|
839
|
+
|
|
840
|
+
For every public option, operation field, result field, or error payload, inspect
|
|
841
|
+
and update all applicable surfaces:
|
|
842
|
+
|
|
843
|
+
- `index.js` and `index.d.ts`;
|
|
844
|
+
- `services/document-operation-contract.js`;
|
|
845
|
+
- `services/standalone-operation-runner.d.ts`;
|
|
846
|
+
- `docs/schemas/document-operations.schema.json`;
|
|
847
|
+
- `node/docx-document.js`;
|
|
848
|
+
- CLI parsing/help/examples;
|
|
849
|
+
- `README.md`, `ARCHITECTURE.md`, `docs/AGENT-WORKFLOW.md`,
|
|
850
|
+
`docs/TESTING.md`, and `CHANGELOG.md`;
|
|
851
|
+
- contract, schema, package facade, CLI, and type-export tests.
|
|
852
|
+
|
|
853
|
+
Do not update only the JavaScript implementation and leave declarations or the
|
|
854
|
+
operation schema behind.
|
|
855
|
+
|
|
856
|
+
### 17.5 Shared verification gates
|
|
857
|
+
|
|
858
|
+
Run these after every work package:
|
|
859
|
+
|
|
860
|
+
```bash
|
|
861
|
+
npm test
|
|
862
|
+
npm run test:isolation
|
|
863
|
+
npm run check:types
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
Run these when OOXML generation or package plumbing changes:
|
|
867
|
+
|
|
868
|
+
```bash
|
|
869
|
+
node scripts/export-validation-fixtures.mjs
|
|
870
|
+
docx-redline validate <representative-output.docx>
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
Run the relevant Word differential suite before completing any work package that
|
|
874
|
+
changes paragraph marks, revision wrappers, run properties, numbering, tables,
|
|
875
|
+
comments, relationships, or content types. Visual suites require human review of
|
|
876
|
+
the generated evidence; successful rendering alone is not visual approval.
|
|
877
|
+
|
|
878
|
+
## 18. Ordered Implementation Work Packages
|
|
879
|
+
|
|
880
|
+
### WP-00 — Capability and Gap Audit [COMPLETED 2026-09-05]
|
|
881
|
+
|
|
882
|
+
**Depends on:** None
|
|
883
|
+
**Produces runtime changes:** No
|
|
884
|
+
**Status:** Completed — audit matrix published at `docs/plans/structural-revision-capability-matrix.md` with all 67 baseline test suites verified passing.
|
|
885
|
+
|
|
886
|
+
**Files to inspect**
|
|
887
|
+
|
|
888
|
+
- `engine/run-builders.js`
|
|
889
|
+
- `engine/surgical-run-splitting.js`
|
|
890
|
+
- `engine/surgical-diff-application.js`
|
|
891
|
+
- `engine/reconstruction-mode.js`
|
|
892
|
+
- `pipeline/list-generation.js`
|
|
893
|
+
- `pipeline/structured-content.js`
|
|
894
|
+
- `core/paragraph-text.js`
|
|
895
|
+
- `core/paragraph-targeting.js`
|
|
896
|
+
- `services/document-operation-session.js`
|
|
897
|
+
- `services/batch-operation-orchestrator.js`
|
|
898
|
+
- `services/revision-comment-management.js`
|
|
899
|
+
- `services/operation-preflight.js`
|
|
900
|
+
- `node/docx-document.js`
|
|
901
|
+
|
|
902
|
+
**Steps**
|
|
903
|
+
|
|
904
|
+
1. Create `docs/plans/structural-revision-capability-matrix.md`.
|
|
905
|
+
2. Add one row for every behavior named in Section 2.
|
|
906
|
+
3. For each row, record status, production symbol, existing test, missing case,
|
|
907
|
+
and proposed work package.
|
|
908
|
+
4. Mark a capability `implemented` only when generation, validation, Accept All,
|
|
909
|
+
and Reject All are covered.
|
|
910
|
+
5. Mark behavior that exists only for lists or only for whole-paragraph deletion
|
|
911
|
+
as `partial`, not implemented.
|
|
912
|
+
6. Record current serialized output for one canonical fixture per partial area.
|
|
913
|
+
|
|
914
|
+
**Tests**
|
|
915
|
+
|
|
916
|
+
No new test is required, but all tests linked from the matrix must be executed.
|
|
917
|
+
|
|
918
|
+
**Done when**
|
|
919
|
+
|
|
920
|
+
No later work package describes existing behavior as absent, and every claimed
|
|
921
|
+
gap has a reproducible fixture or test.
|
|
922
|
+
|
|
923
|
+
### WP-01 — Package Part Inventory Helper [COMPLETED 2026-09-05]
|
|
924
|
+
|
|
925
|
+
**Depends on:** WP-00
|
|
926
|
+
**Produces runtime changes:** No; test helper only
|
|
927
|
+
**Status:** Completed — helper implemented in `tests/helpers/package-fidelity.mjs`, verified by `tests/package_fidelity_inventory_tests.mjs`.
|
|
928
|
+
|
|
929
|
+
**Files to inspect**
|
|
930
|
+
|
|
931
|
+
- `node/zip-archive.js`
|
|
932
|
+
- `services/package-builder.js`
|
|
933
|
+
- `services/standalone-docx-plumbing.js`
|
|
934
|
+
- existing ZIP/package transaction tests
|
|
935
|
+
|
|
936
|
+
**New files**
|
|
937
|
+
|
|
938
|
+
- `tests/helpers/package-fidelity.mjs`
|
|
939
|
+
- `tests/package_fidelity_inventory_tests.mjs`
|
|
940
|
+
|
|
941
|
+
**Algorithm**
|
|
942
|
+
|
|
943
|
+
1. Read every ZIP entry as its uncompressed byte payload.
|
|
944
|
+
2. Normalize entry names to OPC forward-slash form; reject duplicate normalized
|
|
945
|
+
names.
|
|
946
|
+
3. Sort by entry name for reporting only. Never treat archive order, compression
|
|
947
|
+
method, CRC field position, or ZIP timestamps as content.
|
|
948
|
+
4. Return `{ entries: Map<name, { size, sha256, bytes }> }`.
|
|
949
|
+
5. Provide `comparePackageEntries(before, after, allowedChangedEntries)`.
|
|
950
|
+
6. Fail with a report containing unexpected changed, added, and removed entries.
|
|
951
|
+
7. Do not silently ignore `docProps`, relationships, media, content types, or
|
|
952
|
+
custom XML.
|
|
953
|
+
|
|
954
|
+
**Required tests**
|
|
955
|
+
|
|
956
|
+
- Recompressing identical entries produces no content differences.
|
|
957
|
+
- One-byte payload changes are detected.
|
|
958
|
+
- Added and removed entries are reported separately.
|
|
959
|
+
- Duplicate normalized paths are refused.
|
|
960
|
+
- Binary media is compared without text decoding.
|
|
961
|
+
|
|
962
|
+
**Done when**
|
|
963
|
+
|
|
964
|
+
The helper can prove exact untouched entry payload preservation without claiming
|
|
965
|
+
the entire ZIP byte stream remains identical.
|
|
966
|
+
|
|
967
|
+
### WP-02 — OOXML Canonical Subtree Helper [COMPLETED 2026-09-05]
|
|
968
|
+
|
|
969
|
+
**Depends on:** WP-01
|
|
970
|
+
**Produces runtime changes:** No; test helper only
|
|
971
|
+
**Status:** Completed — canonical XML helper implemented in `tests/helpers/canonical-ooxml.mjs` supporting QName-valued attribute canonicalization, verified by `tests/canonical_ooxml_tests.mjs`.
|
|
972
|
+
|
|
973
|
+
**Files to inspect**
|
|
974
|
+
|
|
975
|
+
- `adapters/xml-adapter.js`
|
|
976
|
+
- `core/word-xml.js`
|
|
977
|
+
- `core/xml-query.js`
|
|
978
|
+
- fixtures containing `mc:Ignorable`, alternate prefixes, drawings, and SDTs
|
|
979
|
+
|
|
980
|
+
**New files**
|
|
981
|
+
|
|
982
|
+
- `tests/helpers/canonical-ooxml.mjs`
|
|
983
|
+
- `tests/canonical_ooxml_tests.mjs`
|
|
984
|
+
|
|
985
|
+
**Steps**
|
|
986
|
+
|
|
987
|
+
1. Evaluate an existing standards-compliant canonicalization dependency before
|
|
988
|
+
writing custom logic. Do not add a dependency without documenting bundle and
|
|
989
|
+
browser impact.
|
|
990
|
+
2. Represent element and attribute names as `{ namespaceURI, localName }`.
|
|
991
|
+
3. Preserve element child order and exact character data.
|
|
992
|
+
4. Canonicalize namespace declarations without breaking QName-valued attribute
|
|
993
|
+
contents.
|
|
994
|
+
5. Treat comments and processing instructions consistently and document whether
|
|
995
|
+
they participate in the digest.
|
|
996
|
+
6. Return both canonical bytes and SHA-256 so failures can print a readable
|
|
997
|
+
canonical diff.
|
|
998
|
+
|
|
999
|
+
**Required tests**
|
|
1000
|
+
|
|
1001
|
+
- Different harmless prefixes compare equal.
|
|
1002
|
+
- Different attribute orders compare equal.
|
|
1003
|
+
- Different child orders compare unequal.
|
|
1004
|
+
- `w:b` absent and `w:b w:val="0"` compare unequal.
|
|
1005
|
+
- Leading/trailing spaces and `xml:space` differences compare unequal when they
|
|
1006
|
+
change Word text semantics.
|
|
1007
|
+
- `mc:Ignorable` remains bound to the intended namespace prefixes.
|
|
1008
|
+
|
|
1009
|
+
**Stop condition**
|
|
1010
|
+
|
|
1011
|
+
If QName-valued attributes cannot be canonicalized safely, limit the first
|
|
1012
|
+
oracle to exact serialized subtree bytes and document the limitation. Do not
|
|
1013
|
+
ship a prefix-replacement approximation as canonical XML.
|
|
1014
|
+
|
|
1015
|
+
### WP-03 — Mutation Envelope Registry and Fidelity Suite [COMPLETED 2026-09-05]
|
|
1016
|
+
|
|
1017
|
+
**Depends on:** WP-01, WP-02
|
|
1018
|
+
**Produces runtime changes:** No unless a discovered fidelity bug is separately scoped
|
|
1019
|
+
**Status:** Completed — mutation envelopes registry implemented in `tests/helpers/mutation-envelopes.mjs`, verified against the multi-feature fixture matrix in `tests/fidelity_oracle_tests.mjs`.
|
|
1020
|
+
|
|
1021
|
+
**New files**
|
|
1022
|
+
|
|
1023
|
+
- `tests/helpers/mutation-envelopes.mjs`
|
|
1024
|
+
- `tests/fidelity_oracle_tests.mjs`
|
|
1025
|
+
|
|
1026
|
+
**Steps**
|
|
1027
|
+
|
|
1028
|
+
1. Define envelopes by operation kind and structural route: surgical text,
|
|
1029
|
+
reconstruction, list, structured content, table reconciliation, comment,
|
|
1030
|
+
highlight, accept/reject, and package plumbing.
|
|
1031
|
+
2. Each envelope lists allowed part changes and affected subtree classes.
|
|
1032
|
+
3. Capture before-state entry hashes and candidate subtree identities.
|
|
1033
|
+
4. Apply exactly one operation.
|
|
1034
|
+
5. Reopen the output package before measuring after-state.
|
|
1035
|
+
6. Compare untouched package entries exactly.
|
|
1036
|
+
7. Reacquire untouched subtrees using IDs plus fingerprints and ordered context.
|
|
1037
|
+
8. Compare their canonical digests.
|
|
1038
|
+
9. Run internal Accept All and Reject All and compare expected structure.
|
|
1039
|
+
10. Emit a diagnostic that names the first unexpected entry or subtree.
|
|
1040
|
+
|
|
1041
|
+
**Required fixture matrix**
|
|
1042
|
+
|
|
1043
|
+
- normal paragraph surrounded by untouched paragraphs;
|
|
1044
|
+
- hyperlink and bookmark adjacent to an edit;
|
|
1045
|
+
- comment range adjacent to an edit;
|
|
1046
|
+
- field-code paragraph;
|
|
1047
|
+
- drawing and image relationship;
|
|
1048
|
+
- SDT containing text;
|
|
1049
|
+
- numbered and bulleted lists;
|
|
1050
|
+
- existing table and newly inserted Markdown table;
|
|
1051
|
+
- header, footer, footnote, and endnote package parts;
|
|
1052
|
+
- documents with and without comments/numbering parts.
|
|
1053
|
+
|
|
1054
|
+
**Done when**
|
|
1055
|
+
|
|
1056
|
+
The suite passes current behavior or produces separately tracked fidelity bugs.
|
|
1057
|
+
Do not weaken an envelope merely to make a failure disappear.
|
|
1058
|
+
|
|
1059
|
+
### WP-04 — Revision-View Segment Extraction [COMPLETED 2026-09-05]
|
|
1060
|
+
|
|
1061
|
+
**Depends on:** WP-03
|
|
1062
|
+
**Produces runtime changes:** Additive inspection API
|
|
1063
|
+
**Status:** Completed — implemented `extractParagraphRevisionSegments` in `core/paragraph-text.js`, wired `segments` into `inspectDocumentParts` in `services/document-inspection.js`, exported types in `index.d.ts` and `index.js`, and verified by `tests/revision_view_segment_tests.mjs`.
|
|
1064
|
+
|
|
1065
|
+
**Files to edit**
|
|
1066
|
+
|
|
1067
|
+
- `core/paragraph-text.js`
|
|
1068
|
+
- `services/document-inspection.js`
|
|
1069
|
+
- public exports and declarations from Section 17.4
|
|
1070
|
+
|
|
1071
|
+
**New tests**
|
|
1072
|
+
|
|
1073
|
+
- `tests/revision_view_segment_tests.mjs`
|
|
1074
|
+
|
|
1075
|
+
**Algorithm**
|
|
1076
|
+
|
|
1077
|
+
1. Add one depth-first paragraph walker that emits structural text pieces.
|
|
1078
|
+
2. Track ancestors for `ins`, `del`, `moveFrom`, and `moveTo`.
|
|
1079
|
+
3. Emit text for `w:t`, `w:delText`, tabs, breaks, carriage returns,
|
|
1080
|
+
soft hyphens, and no-break hyphens.
|
|
1081
|
+
4. Compute accepted and rejected visibility using the existing canonical rules.
|
|
1082
|
+
5. Maintain accepted and rejected UTF-16 cursors independently.
|
|
1083
|
+
6. For a hidden segment, store `null` for that view's start.
|
|
1084
|
+
7. Merge adjacent pieces only when kind, author, revision ID, and DOM carrier are
|
|
1085
|
+
compatible.
|
|
1086
|
+
8. Reimplement existing accepted/rejected string extraction as a projection of
|
|
1087
|
+
the walker, or prove parity before retaining both implementations.
|
|
1088
|
+
|
|
1089
|
+
**Required assertions**
|
|
1090
|
+
|
|
1091
|
+
- Concatenating accepted-visible segments equals current accepted extraction.
|
|
1092
|
+
- Concatenating rejected-visible segments equals current rejected extraction.
|
|
1093
|
+
- Entity decoding does not alter DOM offsets.
|
|
1094
|
+
- An emoji counts as two UTF-16 code units.
|
|
1095
|
+
- A replacement exposes deletion and insertion as separate segments, never as
|
|
1096
|
+
one concatenated “tracked” word.
|
|
1097
|
+
- Move revisions map consistently with existing accept/reject behavior.
|
|
1098
|
+
|
|
1099
|
+
**Compatibility gate**
|
|
1100
|
+
|
|
1101
|
+
Existing extraction output must be byte-for-byte unchanged for all current
|
|
1102
|
+
fixtures.
|
|
1103
|
+
|
|
1104
|
+
### WP-05 — Revision-View Target Resolution [COMPLETED 2026-09-05]
|
|
1105
|
+
|
|
1106
|
+
**Depends on:** WP-04
|
|
1107
|
+
**Produces runtime changes:** Additive target option for inspection/preflight
|
|
1108
|
+
**Status:** Completed — implemented revisionView-aware caching and conjunctive descriptor resolution in `core/paragraph-targeting.js`, validation in `services/document-operation-contract.js`, preflight multi-view support in `services/operation-preflight.js`, mutation refusal with `UNSUPPORTED_REVISION_VIEW_MUTATION` in `services/document-operation-applier.js`, JSON schema updates, and verified by `tests/revision_view_target_tests.mjs`.
|
|
1109
|
+
|
|
1110
|
+
**Files to edit**
|
|
1111
|
+
|
|
1112
|
+
- `core/paragraph-targeting.js`
|
|
1113
|
+
- `services/operation-preflight.js`
|
|
1114
|
+
- `services/document-operation-contract.js`
|
|
1115
|
+
- public contract surfaces from Section 17.4
|
|
1116
|
+
|
|
1117
|
+
**Steps**
|
|
1118
|
+
|
|
1119
|
+
1. Add `revisionView` validation with only `accepted` and `rejected` values.
|
|
1120
|
+
2. Build or cache paragraph metadata per revision view; never reuse accepted
|
|
1121
|
+
text for rejected matching.
|
|
1122
|
+
3. Resolve all supplied descriptor fields conjunctively.
|
|
1123
|
+
4. Calculate `occurrence` within the selected view.
|
|
1124
|
+
5. Include the selected view in candidate diagnostics and fingerprints.
|
|
1125
|
+
6. Restrict the initial implementation to inspection and preflight.
|
|
1126
|
+
7. If apply receives `revisionView: 'rejected'`, return
|
|
1127
|
+
`UNSUPPORTED_REVISION_VIEW_MUTATION` until WP-06 explicitly supports that
|
|
1128
|
+
operation kind.
|
|
1129
|
+
|
|
1130
|
+
**Required tests**
|
|
1131
|
+
|
|
1132
|
+
- Same phrase at different accepted/rejected locations.
|
|
1133
|
+
- Phrase exists only in an insertion.
|
|
1134
|
+
- Phrase exists only in a deletion.
|
|
1135
|
+
- Duplicate phrase in one view but unique in the other.
|
|
1136
|
+
- Descriptor fields disagree despite one individually matching.
|
|
1137
|
+
- Invalid view is rejected by runtime contract and JSON schema.
|
|
1138
|
+
|
|
1139
|
+
### WP-06 — Existing-Revision Mutation Policy [COMPLETED 2026-09-05]
|
|
1140
|
+
|
|
1141
|
+
**Depends on:** WP-05
|
|
1142
|
+
**Produces runtime changes:** Opt-in only
|
|
1143
|
+
**Status:** Completed — implemented policy matrix: allow schema-valid comments inside pending insertions across same and different authors with selective accept/reject preservation; refuse deletion comments, cross-container comment ranges, and deletion/move replacements with `UNSAFE_REVISION_NESTING`; refuse pending insertion replacements with `EXISTING_REVISIONS`; verified by `tests/existing_revision_mutation_policy_tests.mjs`.
|
|
1144
|
+
|
|
1145
|
+
**Goal**
|
|
1146
|
+
|
|
1147
|
+
Decide which mutations can safely preserve existing revisions. Do not add a
|
|
1148
|
+
general `preserve-input` switch until the matrix below is implemented.
|
|
1149
|
+
|
|
1150
|
+
**Policy matrix to implement and test**
|
|
1151
|
+
|
|
1152
|
+
| Existing content | Same author | Different author | Initial action |
|
|
1153
|
+
|---|---:|---:|---|
|
|
1154
|
+
| Baseline text | n/a | n/a | Normal tracked mutation |
|
|
1155
|
+
| Pending insertion, comment only | Yes | Yes | Allow if comment range is schema-valid |
|
|
1156
|
+
| Pending insertion, text replacement | Yes | No | Refuse initially; later consider same-author coalescing |
|
|
1157
|
+
| Pending deletion, comment only | Yes | Yes | Refuse until native Word fixture proves representation |
|
|
1158
|
+
| Pending deletion, text replacement | Yes | No | Refuse with `UNSAFE_REVISION_NESTING` |
|
|
1159
|
+
| Move source/destination | Any | Any | Refuse until move-specific lifecycle is designed |
|
|
1160
|
+
|
|
1161
|
+
**Steps**
|
|
1162
|
+
|
|
1163
|
+
1. Generate native Word fixtures for every proposed allowed case.
|
|
1164
|
+
2. Inspect exact OOXML before copying the pattern.
|
|
1165
|
+
3. Add schema, Word open/save, selective-author Accept/Reject, and internal
|
|
1166
|
+
lifecycle tests.
|
|
1167
|
+
4. Add only narrowly named policies for proven cases.
|
|
1168
|
+
5. Retain current `reject-input` default.
|
|
1169
|
+
|
|
1170
|
+
**Stop condition**
|
|
1171
|
+
|
|
1172
|
+
If internal and Word selective-author outcomes differ, keep the case refused.
|
|
1173
|
+
|
|
1174
|
+
### WP-07 — Replacement Event Metadata [COMPLETED 2026-09-05]
|
|
1175
|
+
|
|
1176
|
+
**Depends on:** WP-03
|
|
1177
|
+
**Produces runtime changes:** Opt-in paired metadata
|
|
1178
|
+
**Status:** Completed — implemented opt-in `pairReplacements` with shared author/timestamp metadata, distinct document-allocated revision IDs, structural boundary safety fallback emitting `PAIRING_SKIPPED_STRUCTURAL_BOUNDARY`, lifecycle equivalence, schema and type declarations, and verified by `tests/replacement_event_metadata_tests.mjs`.
|
|
1179
|
+
|
|
1180
|
+
**Files to inspect/edit**
|
|
1181
|
+
|
|
1182
|
+
- `engine/surgical-diff-application.js`
|
|
1183
|
+
- `engine/surgical-run-splitting.js`
|
|
1184
|
+
- `engine/run-builders.js`
|
|
1185
|
+
- `core/types.js`
|
|
1186
|
+
- `core/redline-validation.js`
|
|
1187
|
+
|
|
1188
|
+
**Steps**
|
|
1189
|
+
|
|
1190
|
+
1. Identify delete/insert token pairs before either wrapper is created.
|
|
1191
|
+
2. Confirm both sides can be emitted under the same legal parent.
|
|
1192
|
+
3. Allocate one event author/date and two unique revision IDs.
|
|
1193
|
+
4. Pass explicit metadata into builders; do not call the clock independently for
|
|
1194
|
+
each half.
|
|
1195
|
+
5. Remove only empty runs introduced by the current split operation.
|
|
1196
|
+
6. Preserve all pre-existing zero-width structural nodes in their original order.
|
|
1197
|
+
7. Fall back to independent revisions when safe adjacency is impossible.
|
|
1198
|
+
8. Return a warning such as `PAIRING_SKIPPED_STRUCTURAL_BOUNDARY` only when the
|
|
1199
|
+
caller explicitly requested pairing.
|
|
1200
|
+
|
|
1201
|
+
**Tests**
|
|
1202
|
+
|
|
1203
|
+
- Plain run replacement receives identical author/date and distinct IDs.
|
|
1204
|
+
- Accepted/rejected output matches independent mode.
|
|
1205
|
+
- Replacement across two formatting runs retains both formats.
|
|
1206
|
+
- Hyperlink, comment, bookmark, field, SDT, and existing-revision boundaries
|
|
1207
|
+
trigger safe fallback without node movement.
|
|
1208
|
+
- Multiple replacements in one operation receive separate event pairs.
|
|
1209
|
+
- Validation rejects duplicate revision IDs.
|
|
1210
|
+
|
|
1211
|
+
**Word evidence**
|
|
1212
|
+
|
|
1213
|
+
Record Word version, build, platform, displayed revision count, and screenshots.
|
|
1214
|
+
Do not encode “one balloon” as a cross-version API guarantee.
|
|
1215
|
+
|
|
1216
|
+
### WP-08 — Insertion Affinity [COMPLETED 2026-09-05]
|
|
1217
|
+
|
|
1218
|
+
**Depends on:** WP-07
|
|
1219
|
+
**Produces runtime changes:** Additive explicit option
|
|
1220
|
+
**Status:** Completed — implemented `InsertionAffinity` (`formatting`, `hyperlink`, `revision`, `bookmark`, `comment`), `describeInsertionBoundary` descriptor, strict validation returning `UNSUPPORTED_INSERTION_AFFINITY` for illegal positions, exact legacy fallback when omitted, schema and type declarations, and verified by `tests/insertion_affinity_tests.mjs`.
|
|
1221
|
+
|
|
1222
|
+
**Files to inspect/edit**
|
|
1223
|
+
|
|
1224
|
+
- `engine/surgical-spans.js`
|
|
1225
|
+
- `engine/surgical-run-splitting.js`
|
|
1226
|
+
- `engine/surgical-diff-application.js`
|
|
1227
|
+
- hyperlink/bookmark/comment targeting helpers
|
|
1228
|
+
|
|
1229
|
+
**Steps**
|
|
1230
|
+
|
|
1231
|
+
1. Create an internal boundary descriptor containing left/right carrier runs and
|
|
1232
|
+
all open/closing structural boundaries at the insertion point.
|
|
1233
|
+
2. Apply formatting affinity independently from hyperlink/revision affinity.
|
|
1234
|
+
3. Treat bookmark and comment range markers as ordered boundaries, not wrappers.
|
|
1235
|
+
4. Validate requested affinity before mutation.
|
|
1236
|
+
5. If the requested placement is illegal or ambiguous, return
|
|
1237
|
+
`UNSUPPORTED_INSERTION_AFFINITY`.
|
|
1238
|
+
6. When omitted, call the exact legacy path.
|
|
1239
|
+
|
|
1240
|
+
**Minimum test grid**
|
|
1241
|
+
|
|
1242
|
+
For insertion at the start and end of each structure, test left/right formatting
|
|
1243
|
+
and legal inside/outside placement:
|
|
1244
|
+
|
|
1245
|
+
- normal to bold run;
|
|
1246
|
+
- paragraph start/end with no carrier on one side;
|
|
1247
|
+
- hyperlink;
|
|
1248
|
+
- bookmark range;
|
|
1249
|
+
- comment range;
|
|
1250
|
+
- field begin/separate/end sequence;
|
|
1251
|
+
- SDT content;
|
|
1252
|
+
- same-author insertion;
|
|
1253
|
+
- different-author insertion.
|
|
1254
|
+
|
|
1255
|
+
Assert exact parent, sibling order, inherited `w:rPr`, accepted/rejected text,
|
|
1256
|
+
and preservation of every range marker.
|
|
1257
|
+
|
|
1258
|
+
### WP-09 — Paragraph Boundary Fixture Matrix
|
|
1259
|
+
|
|
1260
|
+
**Depends on:** WP-03
|
|
1261
|
+
**Produces runtime changes:** No; fixture/oracle work
|
|
1262
|
+
**Status:** Completed — generated 10 Word-native golden fixture triples (30 DOCX files and 30 extracted XML parts in `tests/fixtures/paragraph-boundaries/`) covering split, boundary deletion, middle paragraph deletion, blank paragraph insertion, style boundaries, list levels, section breaks, table cells, bookmarks/comments, and multi-author revisions using desktop Word COM automation (`scripts/generate-paragraph-boundary-fixtures.ps1`). Validated all 10 fixtures and codified the empirical ownership rules in `tests/paragraph_boundary_matrix_tests.mjs`.
|
|
1263
|
+
|
|
1264
|
+
**New files**
|
|
1265
|
+
|
|
1266
|
+
- `tests/fixtures/paragraph-boundaries/`
|
|
1267
|
+
- `tests/paragraph_boundary_matrix_tests.mjs`
|
|
1268
|
+
- `scripts/generate-paragraph-boundary-fixtures.ps1`
|
|
1269
|
+
|
|
1270
|
+
**Fixture creation**
|
|
1271
|
+
|
|
1272
|
+
Use desktop Word to create golden examples for:
|
|
1273
|
+
|
|
1274
|
+
- split one paragraph in the middle;
|
|
1275
|
+
- delete only the boundary between two paragraphs;
|
|
1276
|
+
- delete an entire middle paragraph;
|
|
1277
|
+
- insert a blank paragraph;
|
|
1278
|
+
- boundaries between different paragraph styles;
|
|
1279
|
+
- boundaries with different list levels/numIds;
|
|
1280
|
+
- boundary before a section break;
|
|
1281
|
+
- first/last paragraph in a table cell;
|
|
1282
|
+
- adjacent bookmark/comment ranges;
|
|
1283
|
+
- same-author and multi-author revisions.
|
|
1284
|
+
|
|
1285
|
+
For every golden fixture, save pending, accepted, and rejected copies. Extract
|
|
1286
|
+
the relevant XML and record which paragraph owns the inserted/deleted mark and
|
|
1287
|
+
which `w:pPr` survives acceptance.
|
|
1288
|
+
|
|
1289
|
+
**Done when**
|
|
1290
|
+
|
|
1291
|
+
The ownership rules are evidence, not assumptions in prose:
|
|
1292
|
+
1. **Paragraph Split**: Paragraph A owns the inserted paragraph mark (`w:pPr/w:rPr/w:ins`), Paragraph B contains the moved runs without false insertion wrappers; on rejection, Paragraph B content is folded back into Paragraph A and Paragraph B is removed.
|
|
1293
|
+
2. **Boundary Deletion**: Paragraph A owns the deleted paragraph mark (`w:pPr/w:rPr/w:del`), Paragraph B's content is untouched in pending state; on acceptance, Paragraph A's contents are prepended into Paragraph B, Paragraph A is removed, and Paragraph B's properties survive (with any style inheritance tracked via `w:pPrChange`); on rejection, Paragraph A's deleted mark is removed and both paragraphs remain distinct.
|
|
1294
|
+
3. **Paragraph Deletion**: The deleted paragraph owns both its paragraph mark deletion (`w:pPr/w:rPr/w:del`) and text deletions (`w:del/w:r/w:delText`). On acceptance, the paragraph is removed; on rejection, the paragraph and its runs are restored intact.
|
|
1295
|
+
4. **Section Breaks**: Embedded in `w:pPr/w:sectPr`; deleting the break marks the paragraph mark ending Paragraph A deleted.
|
|
1296
|
+
5. **Table Cells**: Cell terminal paragraph constraints must be preserved; internal cell paragraph boundary deletions follow the standard merge.
|
|
1297
|
+
6. **Bookmarks and Comments**: Range start and end markers are retained within their respective paragraph nodes across boundary revisions.
|
|
1298
|
+
|
|
1299
|
+
### WP-10 — Paragraph Boundary Implementation [COMPLETED 2026-09-05]
|
|
1300
|
+
|
|
1301
|
+
**Depends on:** WP-09
|
|
1302
|
+
**Produces runtime changes:** New proven structural routes
|
|
1303
|
+
**Status:** Completed — implemented structural paragraph boundary mutations (split, join, blank and whole-paragraph deletions) with explicit schema safeguards (refusing sole table cell paragraph deletion, cross-cell joins, section break paragraph deletion, field instruction splits, open bookmark splits returning `UNSAFE_PARAGRAPH_BOUNDARY`). Verified across 11 unit tests in `tests/paragraph_boundary_implementation_tests.mjs`, all 10 Word-native golden fixture tests in `tests/paragraph_boundary_matrix_tests.mjs`, and full regression/fuzz suites (77/77 test suites passing).
|
|
1304
|
+
|
|
1305
|
+
**Files to inspect/edit**
|
|
1306
|
+
|
|
1307
|
+
- `engine/reconstruction-mode.js`
|
|
1308
|
+
- `engine/reconstruction-writer.js`
|
|
1309
|
+
- `engine/run-builders.js`
|
|
1310
|
+
- `services/revision-comment-management.js`
|
|
1311
|
+
- `services/document-operation-mutations.js`
|
|
1312
|
+
|
|
1313
|
+
**Implementation order**
|
|
1314
|
+
|
|
1315
|
+
1. Boundary removal retaining both texts.
|
|
1316
|
+
2. Paragraph split retaining all source text.
|
|
1317
|
+
3. Entire paragraph deletion.
|
|
1318
|
+
4. Blank paragraph insertion/deletion.
|
|
1319
|
+
5. List paragraph cases.
|
|
1320
|
+
6. Only then consider section/table-cell cases supported by WP-09 evidence.
|
|
1321
|
+
|
|
1322
|
+
**Boundary removal algorithm**
|
|
1323
|
+
|
|
1324
|
+
1. Resolve adjacent A/B paragraphs under the same legal parent.
|
|
1325
|
+
2. Verify no unsupported boundary ownership.
|
|
1326
|
+
3. Mark the paragraph mark ending A as deleted.
|
|
1327
|
+
4. Leave B text active and in B while the revision is pending.
|
|
1328
|
+
5. On acceptance, merge using the golden-fixture property ownership rule.
|
|
1329
|
+
6. On rejection, remove only the boundary deletion marker.
|
|
1330
|
+
|
|
1331
|
+
**Split algorithm**
|
|
1332
|
+
|
|
1333
|
+
1. Resolve a safe UTF-16 split boundary using surgical span mapping.
|
|
1334
|
+
2. Clone only the paragraph properties proven to transfer.
|
|
1335
|
+
3. Move trailing retained nodes into B without wrapping them as inserted text.
|
|
1336
|
+
4. Mark the paragraph mark ending A as inserted.
|
|
1337
|
+
5. On rejection, move retained B content back into A in exact order and remove B.
|
|
1338
|
+
|
|
1339
|
+
**Required safeguards**
|
|
1340
|
+
|
|
1341
|
+
- Never create an empty numbered/bulleted ghost paragraph after Reject All.
|
|
1342
|
+
- Never place `w:sectPr` on both paragraphs.
|
|
1343
|
+
- Never split a field instruction or move only one bookmark/comment boundary.
|
|
1344
|
+
- Preserve tabs, breaks, hyperlinks, drawings, and SDTs as nodes.
|
|
1345
|
+
- Return `UNSAFE_PARAGRAPH_BOUNDARY` before mutation for unsupported cases.
|
|
1346
|
+
|
|
1347
|
+
### WP-11 — Formatting Contract Completion [COMPLETED 2026-09-05]
|
|
1348
|
+
|
|
1349
|
+
**Depends on:** WP-03, WP-06
|
|
1350
|
+
**Produces runtime changes:** Additive operations; current default retained
|
|
1351
|
+
**Status:** Completed — implemented explicit character-format (`format`, `character-format`) and paragraph-format (`paragraph-format`) operations with tracked revisions (`w:rPrChange`, `w:pPrChange`), schema ordering (`RPR_SCHEMA_ORDER`, `PPR_SCHEMA_ORDER`), direct prior state snapshots excluding nested change records, fresh ID preservation on run clones, author-aware coalescing policy (`coalesce-own-insertion`), and no-op handling without ID consumption. Verified in `tests/formatting_contract_tests.mjs` and all 78 test suites.
|
|
1352
|
+
|
|
1353
|
+
**Files to inspect/edit**
|
|
1354
|
+
|
|
1355
|
+
- `engine/format-application.js`
|
|
1356
|
+
- `engine/formatting-removal.js`
|
|
1357
|
+
- `engine/run-builders.js`
|
|
1358
|
+
- `engine/rpr-helpers.js`
|
|
1359
|
+
- `services/document-operation-contract.js`
|
|
1360
|
+
- `services/revision-comment-management.js`
|
|
1361
|
+
|
|
1362
|
+
**Steps**
|
|
1363
|
+
|
|
1364
|
+
1. Inventory current Markdown format hints and highlight behavior.
|
|
1365
|
+
2. Define explicit character-format and paragraph-format operations in runtime,
|
|
1366
|
+
declarations, and schema.
|
|
1367
|
+
3. Split only the requested character span.
|
|
1368
|
+
4. Snapshot complete direct prior properties without nested change records.
|
|
1369
|
+
5. Apply explicit on/off properties in schema order.
|
|
1370
|
+
6. Generate one property change per resulting changed carrier.
|
|
1371
|
+
7. Preserve fresh unique IDs when a run containing history is cloned.
|
|
1372
|
+
8. Implement paragraph `w:pPrChange` generation separately from run formatting.
|
|
1373
|
+
9. Add author-aware coalescing only after the WP-06 selective-author policy
|
|
1374
|
+
permits it.
|
|
1375
|
+
|
|
1376
|
+
**Tests**
|
|
1377
|
+
|
|
1378
|
+
- Every supported property: absent→on, on→off, and value→different value.
|
|
1379
|
+
- Partial-run and multi-run spans.
|
|
1380
|
+
- Character style plus direct formatting.
|
|
1381
|
+
- Existing `w:rPrChange` and `w:pPrChange` history.
|
|
1382
|
+
- Same/different author pending insertion.
|
|
1383
|
+
- Accept/reject one author while retaining another's property change.
|
|
1384
|
+
- No requested property change returns `no_change` without consuming an ID.
|
|
1385
|
+
|
|
1386
|
+
### WP-12 — Revision Token Core [COMPLETED 2026-09-05]
|
|
1387
|
+
|
|
1388
|
+
**Depends on:** WP-01
|
|
1389
|
+
**Produces runtime changes:** Additive inspection utility
|
|
1390
|
+
**Status:** Completed — implemented binary framing revision token generator in `services/revision-token.js` (`buildRevisionTokenFraming`, `computeRevisionToken`, `computeRevisionTokenSync`, `computeDocumentPartsRevisionToken`, `validateRevisionToken`), package-scoped token computation in `node/docx-document.js` (`computePackageRevisionToken`, `DocxDocument.prototype.getRevisionToken`, `DocxDocument.prototype.revisionToken`), integrated into `inspectDocumentParts`, browser-safe with 0 Node imports in root graph, verified in `tests/revision_token_tests.mjs` and all 79 test suites.
|
|
1391
|
+
|
|
1392
|
+
**New/edited files**
|
|
1393
|
+
|
|
1394
|
+
- `services/revision-token.js` or an equivalently focused leaf module
|
|
1395
|
+
- `services/document-inspection.js`
|
|
1396
|
+
- `node/docx-document.js`
|
|
1397
|
+
- browser/root dependency isolation tests
|
|
1398
|
+
|
|
1399
|
+
**Binary framing**
|
|
1400
|
+
|
|
1401
|
+
Hash this conceptual sequence using UTF-8 names and raw payload bytes:
|
|
1402
|
+
|
|
1403
|
+
```text
|
|
1404
|
+
magic = "docx-redline-revision-token\0"
|
|
1405
|
+
version = uint32be(1)
|
|
1406
|
+
scopeLength + scope
|
|
1407
|
+
entryCount
|
|
1408
|
+
for each entry sorted by normalized name:
|
|
1409
|
+
nameLength + name
|
|
1410
|
+
payloadLength + payload
|
|
1411
|
+
```
|
|
1412
|
+
|
|
1413
|
+
Use fixed-width unsigned big-endian lengths or another unambiguous documented
|
|
1414
|
+
encoding. Reject duplicate normalized entry names. The browser implementation
|
|
1415
|
+
may use Web Crypto asynchronously; the Node package path may use `node:crypto`
|
|
1416
|
+
behind a Node-only module. Do not import Node crypto from the browser-safe root
|
|
1417
|
+
graph.
|
|
1418
|
+
|
|
1419
|
+
**Tests**
|
|
1420
|
+
|
|
1421
|
+
- Stable across ZIP compression/order/timestamp changes.
|
|
1422
|
+
- Changes when any entry name or payload changes.
|
|
1423
|
+
- Stable regardless of Map/object enumeration order.
|
|
1424
|
+
- Different scope or version produces a different token.
|
|
1425
|
+
- Binary zero bytes and non-ASCII entry names frame correctly.
|
|
1426
|
+
|
|
1427
|
+
### WP-13 — Revision Token Enforcement [COMPLETED 2026-09-05]
|
|
1428
|
+
|
|
1429
|
+
**Depends on:** WP-12
|
|
1430
|
+
**Produces runtime changes:** Additive apply precondition
|
|
1431
|
+
**Status:** Completed — added `expectedRevision` precondition support across package facade (`DocxDocument.prototype.applyOperations`), batch runner (`applyOperationsToDocumentXml`), single-op runner (`applyOperationToDocumentXml`), CLI (`--expected-revision` and JSON file wrapper), and document-operations schema. Validates token syntax, enforces scope isolation (`REVISION_TOKEN_SCOPE_MISMATCH`), timing-safe token equality comparison with `areRevisionTokensEqual`, structured rejection with `REVISION_MISMATCH` before mutating state or creating artifacts in both atomic and non-atomic modes. Verified in `tests/revision_token_enforcement_tests.mjs` and all 80 test suites.
|
|
1432
|
+
|
|
1433
|
+
**Steps**
|
|
1434
|
+
|
|
1435
|
+
1. Add `expectedRevision` to package and applicable lower-level options.
|
|
1436
|
+
2. Validate token syntax, version, algorithm, and scope before comparison.
|
|
1437
|
+
3. Recompute from the exact input supplied to apply.
|
|
1438
|
+
4. Perform comparison before creating session mutations or companion artifacts.
|
|
1439
|
+
5. Use a timing-safe equality primitive where available; correctness must not
|
|
1440
|
+
depend on it.
|
|
1441
|
+
6. Return `REVISION_TOKEN_SCOPE_MISMATCH` for incompatible scopes and
|
|
1442
|
+
`REVISION_MISMATCH` for unequal valid tokens.
|
|
1443
|
+
7. Ensure atomic and non-atomic modes both leave input untouched on mismatch.
|
|
1444
|
+
|
|
1445
|
+
**Tests**
|
|
1446
|
+
|
|
1447
|
+
- Matching token applies.
|
|
1448
|
+
- Stale token refuses.
|
|
1449
|
+
- Token from another document refuses.
|
|
1450
|
+
- Parts token cannot guard package API and vice versa.
|
|
1451
|
+
- No operation result or artifact is partially produced.
|
|
1452
|
+
- Omitting the option follows the byte-for-byte legacy path.
|
|
1453
|
+
|
|
1454
|
+
### WP-14 — Capture Contract and Dependency Graph [COMPLETED 2026-09-05]
|
|
1455
|
+
|
|
1456
|
+
**Depends on:** WP-05
|
|
1457
|
+
**Produces runtime changes:** Validation and scheduling for capture batches
|
|
1458
|
+
**Status:** Completed — added `operationId`, `captureKey`, `target.captureRef`, and `target.select` to document operation contract, TypeScript declarations, and JSON schema. Implemented `buildOperationDependencyPlan` performing Kahn topological sort with comment priority among ready nodes, duplicate key detection (`DUPLICATE_CAPTURE_KEY`), missing producer detection (`CAPTURE_NOT_FOUND`), and cycle detection (`CAPTURE_DEPENDENCY_CYCLE`). Integrated dependency graph planning into `applyOperationsToDocumentXml` and `orderOperationsForStableTargets`. Updated `preflightOperations` to validate dependency graph and mark capture consumers as `status: 'deferred'` without searching static DOM. Ensured batch `results` remain sorted by original operation index while `executionOrder` reports actual execution order. Verified in `tests/capture_dependency_graph_tests.mjs` and all 81 test suites.
|
|
1459
|
+
|
|
1460
|
+
**Files to inspect/edit**
|
|
1461
|
+
|
|
1462
|
+
- `services/document-operation-contract.js`
|
|
1463
|
+
- `services/operation-preflight.js`
|
|
1464
|
+
- `services/batch-operation-orchestrator.js`
|
|
1465
|
+
- public contract surfaces from Section 17.4
|
|
1466
|
+
|
|
1467
|
+
**Steps**
|
|
1468
|
+
|
|
1469
|
+
1. Validate `operationId`, `captureKey`, and `target.captureRef` as non-empty,
|
|
1470
|
+
bounded strings.
|
|
1471
|
+
2. Build `captureKey -> source operation index`.
|
|
1472
|
+
3. Reject duplicates and missing producers.
|
|
1473
|
+
4. Add dependency edges from consumer to producer.
|
|
1474
|
+
5. Perform a stable topological sort using original operation index as the tie
|
|
1475
|
+
breaker.
|
|
1476
|
+
6. Apply existing comment-first priority only among currently ready independent
|
|
1477
|
+
nodes.
|
|
1478
|
+
7. Detect cycles before constructing `DocumentOperationSession` mutations.
|
|
1479
|
+
8. Preflight static targets immediately and mark capture consumers `deferred`.
|
|
1480
|
+
|
|
1481
|
+
**Tests**
|
|
1482
|
+
|
|
1483
|
+
- Linear dependency, diamond dependency, independent comments, duplicate key,
|
|
1484
|
+
missing producer, self-cycle, multi-node cycle, and stable tie order.
|
|
1485
|
+
- `results` remain sorted by original index while `executionOrder` reports actual
|
|
1486
|
+
execution.
|
|
1487
|
+
- Batches without capture fields preserve current scheduling exactly.
|
|
1488
|
+
|
|
1489
|
+
### WP-15 — Capture Storage, Resolution, and Savepoints [COMPLETED 2026-09-05]
|
|
1490
|
+
|
|
1491
|
+
**Depends on:** WP-14
|
|
1492
|
+
**Produces runtime changes:** Capture execution
|
|
1493
|
+
**Status:** Completed — added `captureTable` to `DocumentOperationSession` with savepoint clone/restore and atomic rollback clearing. Implemented `deriveCapturedEntity`, `invalidateAffectedCaptures`, and `resolveTargetFromCapture` in `services/capture-engine.js`. Wired capture resolution and live/removed mutation node tracking into `services/document-operation-mutations.js` and `services/document-operation-applier.js`. Verified all 10 required routes (plain paragraph, structured content, lists, tables, producer no-ops, producer failures, consumer rollback, staleness detection, ambiguous selection, non-atomic isolation) in `tests/capture_resolution_tests.mjs` and all 82 test suites.
|
|
1494
|
+
|
|
1495
|
+
**Files to inspect/edit**
|
|
1496
|
+
|
|
1497
|
+
- `services/document-operation-session.js`
|
|
1498
|
+
- `services/document-operation-applier.js`
|
|
1499
|
+
- `services/document-operation-mutations.js`
|
|
1500
|
+
- structured/list/table replacement paths
|
|
1501
|
+
|
|
1502
|
+
**Steps**
|
|
1503
|
+
|
|
1504
|
+
1. Add `captureTable` to the session.
|
|
1505
|
+
2. Clone/restore it with every savepoint.
|
|
1506
|
+
3. After a successful changed producer, derive captures from the actual imported
|
|
1507
|
+
live-DOM nodes—not from the requested text alone.
|
|
1508
|
+
4. Store serializable identity evidence; do not store raw nodes across
|
|
1509
|
+
savepoints.
|
|
1510
|
+
5. Reacquire candidates from the current paragraph/table index.
|
|
1511
|
+
6. Verify fingerprints and expected text before resolving `select`.
|
|
1512
|
+
7. Require a unique `select` match within the capture.
|
|
1513
|
+
8. Invalidate or deterministically update captures affected by later structural
|
|
1514
|
+
replacement.
|
|
1515
|
+
9. Discard all captures on atomic rollback.
|
|
1516
|
+
|
|
1517
|
+
**Required route tests**
|
|
1518
|
+
|
|
1519
|
+
- Plain paragraph insertion → comment.
|
|
1520
|
+
- Structured heading/paragraph insertion → comment.
|
|
1521
|
+
- List insertion → format one item.
|
|
1522
|
+
- Table insertion → target one cell only if the capture schema supports cells.
|
|
1523
|
+
- Producer no-op, producer failure, consumer failure, stale capture, ambiguous
|
|
1524
|
+
selection, and later replacement of captured content.
|
|
1525
|
+
|
|
1526
|
+
### WP-16 — Receipt Collector [COMPLETED 2026-09-05]
|
|
1527
|
+
|
|
1528
|
+
**Depends on:** WP-03
|
|
1529
|
+
**Produces runtime changes:** Internal telemetry collector
|
|
1530
|
+
**Status:** Completed — added `ReceiptCollector` in `services/receipt-collector.js` owned by `DocumentOperationSession` with savepoint snapshots and restore. Instrumented `core/types.js` (`createRevisionMetadata`, `createReplacementRevisionEvent`), `engine/run-builders.js` (`createTrackChange`, `markParagraphMark`, `snapshotAndAttachRPrChange`, `snapshotAndAttachPPrChange`), `engine/reconstruction-writer.js`, `engine/table-mode.js`, `pipeline/serialization.js`, `services/comment-engine.js`, `services/document-operation-mutations.js`, `services/batch-operation-orchestrator.js`, and `services/standalone-docx-plumbing.js` to record revisions, comments, numbering, relationships, and targets directly at point of allocation/attachment. Verified via `tests/receipt_collector_tests.mjs` and all 83 test suites.
|
|
1531
|
+
|
|
1532
|
+
**Files to inspect/edit**
|
|
1533
|
+
|
|
1534
|
+
- `core/types.js`
|
|
1535
|
+
- revision builders
|
|
1536
|
+
- comment engine
|
|
1537
|
+
- numbering helpers
|
|
1538
|
+
- package relationship/content-type helpers
|
|
1539
|
+
- `services/document-operation-session.js`
|
|
1540
|
+
|
|
1541
|
+
**Steps**
|
|
1542
|
+
|
|
1543
|
+
1. Add a session-owned collector with `beginOperation(index)`, record methods,
|
|
1544
|
+
`commitOperation()`, and `restore(savepoint)`.
|
|
1545
|
+
2. Record IDs at the successful allocation/attachment point, not by reparsing a
|
|
1546
|
+
partially serialized string.
|
|
1547
|
+
3. Associate every item with kind and part name.
|
|
1548
|
+
4. Snapshot collector state in operation savepoints.
|
|
1549
|
+
5. Ensure failed/no-op allocations disappear on restore.
|
|
1550
|
+
6. Keep the collector internal until WP-17 proves reconciliation with output.
|
|
1551
|
+
|
|
1552
|
+
**Tests**
|
|
1553
|
+
|
|
1554
|
+
- Text replacement records distinct deletion/insertion IDs.
|
|
1555
|
+
- Formatting records property-change IDs.
|
|
1556
|
+
- Comment/list/package plumbing records companion IDs.
|
|
1557
|
+
- Failed and no-op operations leave collector state unchanged.
|
|
1558
|
+
|
|
1559
|
+
### WP-17 — Public Commit-Aware Receipts
|
|
1560
|
+
|
|
1561
|
+
**Depends on:** WP-16
|
|
1562
|
+
**Produces runtime changes:** Additive result field
|
|
1563
|
+
**Status:** Completed — added `MutationReceipt` and `MutationReceiptRevisionItem` interfaces to types/index. Instrumented batch orchestrator and single-operation applier to attach receipts to every item result and top-level batch results (`receipts`). Implemented output reconciliation via `reconcileReceiptsAgainstOutput` verifying all committed IDs are present in serialized XML or failing the transaction. Supported atomic rollback marking prior items `rolled_back` with `committed: false`. Added `tests/mutation_receipt_tests.mjs` verifying all required test cases. All 84 test suites pass.
|
|
1564
|
+
|
|
1565
|
+
**Steps**
|
|
1566
|
+
|
|
1567
|
+
1. Convert internal collector records to the receipt schema in Section 12.
|
|
1568
|
+
2. Attach provisional receipts to item results.
|
|
1569
|
+
3. At successful transaction commit, set applied receipts `committed: true`.
|
|
1570
|
+
4. On atomic rollback, rewrite every provisionally applied receipt to
|
|
1571
|
+
`finalDisposition: 'rolled_back'` and `committed: false`.
|
|
1572
|
+
5. Mark unexecuted steps `not_attempted` when `continueOnError: false`.
|
|
1573
|
+
6. Reopen committed output and reconcile every reported durable ID.
|
|
1574
|
+
7. If reconciliation fails, treat it as a transaction error rather than return
|
|
1575
|
+
inaccurate telemetry.
|
|
1576
|
+
|
|
1577
|
+
**Tests**
|
|
1578
|
+
|
|
1579
|
+
- Successful atomic and non-atomic batches.
|
|
1580
|
+
- Early stop and continue-on-error.
|
|
1581
|
+
- Later failure rolling back earlier success.
|
|
1582
|
+
- Mixed text/comment/list/table/format operations.
|
|
1583
|
+
- Existing IDs are never incorrectly reported as newly allocated.
|
|
1584
|
+
|
|
1585
|
+
### WP-18 — Strict Application Warning Cycle
|
|
1586
|
+
|
|
1587
|
+
**Depends on:** WP-05
|
|
1588
|
+
**Produces runtime changes:** Warning only before v1.0.0
|
|
1589
|
+
**Status:** Completed — added `AMBIGUOUS_TARGET_HEURISTIC_USED` warning in `core/paragraph-targeting.js` triggered only when permissive resolution chooses among multiple identical candidate paragraphs. Integrated candidate count and migration guidance into warnings in `document-operation-applier.js`, `batch-operation-orchestrator.js`, and `operation-preflight.js`. Updated `README.md`, `AGENTS.md`, `ARCHITECTURE.md`, and `CHANGELOG.md` with migration guidance and strict descriptor examples. Created `tests/strict_application_warning_tests.mjs` verifying all warning cycle invariants. All 85 test suites pass.
|
|
1590
|
+
|
|
1591
|
+
**Steps**
|
|
1592
|
+
|
|
1593
|
+
1. Identify every public application entry point and its current strict default.
|
|
1594
|
+
2. Add a warning only when permissive resolution actually chooses among multiple
|
|
1595
|
+
candidates—not merely when `strictTargets: false` is present.
|
|
1596
|
+
3. Include candidate count and migration guidance without exposing document
|
|
1597
|
+
content beyond existing diagnostics policy.
|
|
1598
|
+
4. Ensure preflight and strict application share the same candidate resolver.
|
|
1599
|
+
5. Update recommended examples to strict descriptors.
|
|
1600
|
+
|
|
1601
|
+
**v1.0.0 task**
|
|
1602
|
+
|
|
1603
|
+
Change the remaining permissive defaults only in a dedicated major-version
|
|
1604
|
+
work package. Update semver notes, migration examples, and compatibility tests at
|
|
1605
|
+
that time; do not silently make the change during v0.x implementation.
|
|
1606
|
+
|
|
1607
|
+
## 19. Test Fixture Naming and Assertions
|
|
1608
|
+
|
|
1609
|
+
Use names that state the invariant, not the implementation. Examples:
|
|
1610
|
+
|
|
1611
|
+
- `paragraph-boundary-join-retains-second-text`
|
|
1612
|
+
- `paragraph-split-reject-restores-source-properties`
|
|
1613
|
+
- `replacement-pair-preserves-comment-boundary`
|
|
1614
|
+
- `rejected-view-target-is-ambiguous`
|
|
1615
|
+
- `capture-savepoint-reacquires-current-node`
|
|
1616
|
+
- `atomic-receipt-marks-prior-success-rolled-back`
|
|
1617
|
+
|
|
1618
|
+
Every structural fixture should assert at least:
|
|
1619
|
+
|
|
1620
|
+
1. operation status and error code;
|
|
1621
|
+
2. generated revision element location and unique IDs;
|
|
1622
|
+
3. exact accepted text;
|
|
1623
|
+
4. exact rejected text;
|
|
1624
|
+
5. accepted paragraph/table/list structure;
|
|
1625
|
+
6. rejected paragraph/table/list structure;
|
|
1626
|
+
7. preservation of nearby unknown/structural nodes;
|
|
1627
|
+
8. validation result;
|
|
1628
|
+
9. package artifact presence/absence;
|
|
1629
|
+
10. no unexpected mutation-envelope drift.
|
|
1630
|
+
|
|
1631
|
+
Avoid assertions that merely search the serialized XML for a tag. Parse the
|
|
1632
|
+
output and assert parent, child, sibling order, namespace, attributes, and
|
|
1633
|
+
affected text. Regex assertions may supplement but not replace DOM assertions.
|
|
1634
|
+
|
|
1635
|
+
## 20. Implementation Reporting Template
|
|
1636
|
+
|
|
1637
|
+
The implementing agent should finish each work package with this report:
|
|
1638
|
+
|
|
1639
|
+
```text
|
|
1640
|
+
Work package: WP-XX — Name
|
|
1641
|
+
Outcome: complete | partial | blocked
|
|
1642
|
+
|
|
1643
|
+
Behavior implemented:
|
|
1644
|
+
- ...
|
|
1645
|
+
|
|
1646
|
+
Compatibility:
|
|
1647
|
+
- Public API change: ...
|
|
1648
|
+
- Default behavior change: none | ...
|
|
1649
|
+
- Serialized OOXML change: none | ...
|
|
1650
|
+
|
|
1651
|
+
Files changed:
|
|
1652
|
+
- path — purpose
|
|
1653
|
+
|
|
1654
|
+
Tests added:
|
|
1655
|
+
- test — invariant
|
|
1656
|
+
|
|
1657
|
+
Verification run:
|
|
1658
|
+
- command — result
|
|
1659
|
+
|
|
1660
|
+
Deferred or refused cases:
|
|
1661
|
+
- case — reason and error code
|
|
1662
|
+
|
|
1663
|
+
Manual Word evidence:
|
|
1664
|
+
- not required | artifact path and review status
|
|
1665
|
+
```
|
|
1666
|
+
|
|
1667
|
+
Do not report a work package complete when focused tests pass but shared gates,
|
|
1668
|
+
package validation, required Word evidence, declarations, schemas, or docs remain
|
|
1669
|
+
unfinished.
|