@ansonlai/docx-redline-js 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/ARCHITECTURE.md +221 -12
  2. package/CHANGELOG.md +803 -586
  3. package/README.md +1018 -901
  4. package/core/current-properties.js +136 -0
  5. package/core/paragraph-text.js +211 -209
  6. package/core/types.js +9 -3
  7. package/core/word-revision-semantics.js +256 -0
  8. package/dist/docx-redline-js.esm.js +4551 -967
  9. package/dist/docx-redline-js.esm.js.map +4 -4
  10. package/dist/docx-redline-js.esm.min.js +112 -106
  11. package/dist/docx-redline-js.esm.min.js.map +4 -4
  12. package/dist/docx-redline.bundle.cjs +4690 -1106
  13. package/dist/docx-redline.bundle.cjs.map +4 -4
  14. package/dist/docx-redline.bundle.js +4690 -1106
  15. package/dist/docx-redline.bundle.js.map +4 -4
  16. package/docs/AGENT_FAST_START.md +2 -0
  17. package/docs/AGENT_KNOWLEDGE_BASE.md +23 -4
  18. package/docs/SKILL_AUTHORING.md +165 -150
  19. package/docs/TESTING.md +1142 -1059
  20. package/docs/schemas/document-operations.schema.json +221 -168
  21. package/document/docx-document.js +1022 -1010
  22. package/engine/oxml-engine.js +567 -560
  23. package/engine/paragraph-mark-revisions.js +292 -0
  24. package/engine/reconstruction-mapper.js +312 -311
  25. package/engine/reconstruction-mode.js +183 -183
  26. package/engine/reconstruction-writer.js +67 -10
  27. package/engine/rpr-helpers.js +42 -0
  28. package/engine/run-builders.js +0 -73
  29. package/engine/surgical-diff-application.js +604 -605
  30. package/engine/table-mode.js +63 -63
  31. package/index.d.ts +51 -1
  32. package/index.js +47 -3
  33. package/node/cli-help.js +1 -0
  34. package/node/cli.js +992 -974
  35. package/orchestration/list-structural-fallback.js +67 -9
  36. package/package.json +1 -1
  37. package/pipeline/diff-engine.js +130 -1
  38. package/pipeline/ingestion-export.js +227 -243
  39. package/pipeline/ingestion-paragraph.js +93 -98
  40. package/pipeline/list-generation.js +415 -419
  41. package/pipeline/pipeline.js +206 -204
  42. package/pipeline/serialization.js +111 -94
  43. package/scripts/export-validation-fixtures.mjs +19 -6
  44. package/scripts/generate-word-revision-semantics-matrix.mjs +134 -0
  45. package/scripts/lib/word-coverage-catalogue.mjs +7 -2
  46. package/scripts/render-multilevel-bullet-visual.ps1 +2 -2
  47. package/scripts/word-com-corpus-visual-suite.ps1 +2 -2
  48. package/scripts/word-com-differential.ps1 +443 -335
  49. package/scripts/word-com-visual-suite.ps1 +2 -2
  50. package/scripts/word-revision-semantics-matrix.ps1 +108 -0
  51. package/services/batch-operation-orchestrator.js +26 -0
  52. package/services/capture-engine.js +228 -226
  53. package/services/document-operation-applier.js +215 -0
  54. package/services/document-operation-contract.js +628 -537
  55. package/services/document-operation-mutations.js +789 -46
  56. package/services/error-recovery.js +17 -0
  57. package/services/list-conversion-diagnostics.js +296 -0
  58. package/services/list-format-change.js +586 -0
  59. package/services/list-format-mutation.js +416 -0
  60. package/services/list-format.js +305 -0
  61. package/services/numbering-allocation.js +642 -0
  62. package/services/numbering-helpers.js +354 -524
  63. package/services/operation-batch-compiler.js +612 -436
  64. package/services/operation-heuristics.js +197 -173
  65. package/services/operation-preflight.js +1 -0
  66. package/services/receipt-collector.js +288 -288
  67. package/services/revision-comment-management.js +634 -594
  68. package/services/revision-fidelity-verifier.js +498 -0
  69. package/services/standalone-docx-plumbing.js +627 -631
  70. package/services/standalone-operation-runner.d.ts +606 -427
  71. package/services/table-reconciliation.js +103 -49
package/ARCHITECTURE.md CHANGED
@@ -38,8 +38,10 @@ No Word add-in entrypoints or host-specific integration layers are part of this
38
38
  │ ├── logger.js
39
39
  │ └── xml-adapter.js
40
40
  ├── core/
41
+ │ ├── current-properties.js
41
42
  │ ├── paragraph-text.js
42
43
  │ ├── sha256.js
44
+ │ ├── word-revision-semantics.js
43
45
  │ └── word-xml.js
44
46
  ├── document/
45
47
  │ ├── docx-document.js
@@ -50,18 +52,27 @@ No Word add-in entrypoints or host-specific integration layers are part of this
50
52
  │ ├── surgical-mode.js
51
53
  │ ├── reconstruction-mode.js
52
54
  │ ├── run-builders.js
55
+ │ ├── paragraph-mark-revisions.js
53
56
  │ └── formatting-removal.js
54
57
  ├── orchestration/
55
58
  ├── pipeline/
56
59
  ├── services/
57
60
  │ ├── comment-engine.js
58
61
  │ ├── numbering-helpers.js
62
+ │ ├── numbering-allocation.js
63
+ │ ├── list-conversion-diagnostics.js
64
+ │ ├── list-format.js
65
+ │ ├── list-format-mutation.js
66
+ │ ├── list-format-change.js
67
+ │ ├── operation-batch-compiler.js
68
+ │ ├── localized-replacement-compiler.js
59
69
  │ ├── revision-comment-management.js
60
70
  │ ├── document-inspection.js
61
71
  │ ├── document-operation-session.js
62
72
  │ ├── document-operation-applier.js
63
73
  │ ├── document-operation-mutations.js
64
74
  │ ├── batch-operation-orchestrator.js
75
+ │ ├── revision-fidelity-verifier.js
65
76
  │ ├── operation-heuristics.js
66
77
  │ ├── standalone-docx-plumbing.js
67
78
  │ └── standalone-operation-runner.js
@@ -116,6 +127,24 @@ No Word add-in entrypoints or host-specific integration layers are part of this
116
127
  - `core/validation-delta.js`
117
128
  - Stable issue signatures and multiset subtraction for classifying baseline
118
129
  versus generated validation issues without hiding added duplicate errors.
130
+ - `core/word-revision-semantics.js`
131
+ - Pure DOM projection of what Word shows after Reject All or Accept All
132
+ (`projectWordRevisionView`, `wordRevisionParagraphTexts`,
133
+ `paragraphMarkRevision`). It encodes Word's paragraph-mark rules: a
134
+ tracked-inserted mark vanishes on Reject and its content merges into the
135
+ following paragraph of the same container, a tracked-deleted mark does the
136
+ same on Accept, a table cell keeps its last paragraph, rows marked inserted
137
+ or deleted vanish, and Reject restores `w:pPrChange` snapshots. It never
138
+ mutates or serializes. The rules are validated against recorded desktop
139
+ Word 16 COM results (`tests/fixtures/word-revision-semantics/`).
140
+ - `core/current-properties.js`
141
+ - Direct-child accessors for the current paragraph/run properties
142
+ (`getCurrentParagraphProperties`, `getCurrentRunProperties`,
143
+ `getCurrentChild`, `getCurrentNumIdElements`, ...). Descendant lookups cross
144
+ `w:pPrChange` / `w:rPrChange`, so a historical `numPr` or `pStyle` would look
145
+ current; the snapshot is reachable only through the explicit
146
+ `getHistoricalParagraphProperties` / `getHistoricalRunProperties` and
147
+ `getAllReferencedNumIdElements`. Depends only on `core/types.js`.
119
148
  - `core/revision-cloning.js`
120
149
  - Shared effective-property cloning strips historical revision descendants;
121
150
  intentional revision-bearing splits refresh cloned property-change IDs.
@@ -125,7 +154,20 @@ No Word add-in entrypoints or host-specific integration layers are part of this
125
154
  - Internal reconciliation capability matrix and opt-in diagnostic route
126
155
  instrumentation. It observes policy; it does not expose a route override.
127
156
  - `engine/run-builders.js`
128
- - Shared builders for insertion/deletion wrappers, paragraph-mark revisions, visible run content, and run-property changes.
157
+ - Shared builders for insertion/deletion wrappers, visible run content, and run-property changes. It does not build paragraph-mark revisions.
158
+ - `engine/paragraph-mark-revisions.js`
159
+ - The single construction site for a tracked paragraph mark (`w:ins` / `w:del`
160
+ under `w:pPr/w:rPr`). DOM entry points (`markParagraphInserted`,
161
+ `markParagraphDeleted`, `setParagraphPropertiesMarkRevision`,
162
+ `clearParagraphMarkRevision`, `restoreParagraphMarkInserted`) and string
163
+ builders (`paragraphMarkRevisionXml`, `paragraphMarkPPrXml`) share one
164
+ metadata path, so ids are allocated through the document's allocator and
165
+ recorded in receipts. `planParagraphMarks` is a pure check that an
166
+ alignment gives every source paragraph exactly one untracked or deleted
167
+ mark and every new target paragraph an inserted mark, so Reject All yields
168
+ the source paragraph count and a deleted paragraph is never collapsed into
169
+ another. Reconstruction, list and table generation, and document-level
170
+ shortcuts all call it. Depends only on `core/`.
129
171
  - `engine/surgical-*.js`
130
172
  - Surgical run splitting, diff application, and span helpers for localized edits that preserve surrounding markup. Plain-text edit groups execute from right to left with a fresh live span index, while space/NBSP-only replacement hunks are refined to character-local changes so unchanged hyperlink containers survive.
131
173
  The shared carrier splitter also handles explicit rejected-view deletion
@@ -148,7 +190,36 @@ No Word add-in entrypoints or host-specific integration layers are part of this
148
190
  - `services/comment-engine.js`
149
191
  - Comment creation and package-level comment XML handling.
150
192
  - `services/numbering-helpers.js`
151
- - Dynamic numbering ID allocation, numbering payload remapping, and schema-order-safe numbering merges.
193
+ - Document-scoped numbering ID state (`createDynamicNumberingIdState`,
194
+ `reserveNextNumberingIdPair`), numbering payload remapping, and shared DOM
195
+ predicates. It no longer owns a merge.
196
+ - `services/numbering-allocation.js`
197
+ - The single place that creates, continues, merges and validates numbering
198
+ definitions. `allocateNumberingDefinition` builds a new `w:abstractNum` +
199
+ `w:num` (source levels copied, chosen levels redefined, start overrides)
200
+ from ids reserved through the document state; `allocateNumberingForReplacement`
201
+ holds the reuse-source-list policy (`continue-source`, `allocate`,
202
+ `passthrough`) so no call site decides it; `continueBatchListSequence` lets
203
+ separate operations of one batch that generate items of the same kind,
204
+ format and level continue one numbering instance (reused only while its
205
+ `numId` is still bound in the live document). `mergeNumberingParts` /
206
+ `mergeNumberingXmlBySchemaOrder` are the one numbering merge, used by the
207
+ runner's consumers and the `openDocx` facade; an incoming `w:abstractNum` /
208
+ `w:num` id that exists with different content throws `NUMBERING_ID_CONFLICT`.
209
+ `checkNumberingBinding` validates a direct `{ numId, level }` binding and
210
+ `validateNumberingIntegrity` reports dangling `numId`, `abstractNumId` and
211
+ `ilvl` (`NUMBERING_*` issues) for the facade's baseline-delta validation.
212
+ Old definitions are never deleted or overwritten, and tracked-deleted source
213
+ paragraphs keep their `numPr`.
214
+ - `services/list-conversion-diagnostics.js`
215
+ - Safety diagnostics for text edits that retype list markers
216
+ (`LIST_MARKER_NOT_BOUND` / `UNSUPPORTED_LIST_CONVERSION`,
217
+ `LIST_FORMAT_NOT_APPLIED` / `UNSUPPORTED_LIST_FORMAT_CHANGE`; warning by
218
+ default, error under `strictListConversion`, silenced by
219
+ `listMarkers: 'literal'`), the closed `paragraph-format` key set (unknown
220
+ keys are `INVALID_OPERATION`) and the `numbering` shape check behind
221
+ `paragraph-format` numbering binding. Recovery points at `list-format`
222
+ through `recovery.suggestedOperation`.
152
223
  - `services/standalone-docx-plumbing.js`
153
224
  - Package-level extraction/wiring/validation for `word/document.xml`, `word/numbering.xml`, and `word/comments.xml`.
154
225
  - `services/revision-comment-management.js`
@@ -160,7 +231,13 @@ No Word add-in entrypoints or host-specific integration layers are part of this
160
231
  - Canonical single-operation validation, author resolution, dispatch, and
161
232
  result metadata assembly. Before commit it validates the entire live
162
233
  document against the operation savepoint and refuses newly generated
163
- structural errors, including duplicate revision IDs.
234
+ structural errors, including duplicate revision IDs. For localized
235
+ `replacements` it also checks, before commit, that the target's accepted text
236
+ (read from the successor paragraph via `SourceTargetRegistry.previewSuccessor`,
237
+ or the restored paragraph) equals the compiled desired text; otherwise the
238
+ savepoint is restored and `PATCH_ROUNDTRIP_MISMATCH` is returned, because a
239
+ tracked whole-paragraph deletion rejects back to the source and the Reject
240
+ All verifier alone cannot see it.
164
241
  - `services/document-operation-mutations.js`
165
242
  - Coupled OOXML mutation implementations for redline, highlight, and comment
166
243
  operations. For text-bearing edits, the resolved paragraph's exact
@@ -188,6 +265,50 @@ No Word add-in entrypoints or host-specific integration layers are part of this
188
265
  - `services/document-operation-contract.js`
189
266
  - Compatibility normalization, canonical operation kinds, author precedence,
190
267
  and stable runtime validation for document operations.
268
+ - `services/list-format.js` and `services/list-format-mutation.js`
269
+ - The `list-format` operation (`convert-plain`, plus the shared request shape
270
+ for `change-format` and `join`): contract shape, the narrow
271
+ typed-marker grammar (letter versus roman disambiguation by sequence or an
272
+ explicit `format`), and the in-place DOM core. Every refusal is decided
273
+ before mutation; the binding reuses the `paragraph-format.numbering` path
274
+ (`snapshotAndAttachPPrChange` + `applyParagraphPropertiesToPPr`), the marker
275
+ becomes one tracked `w:del`, definitions come from
276
+ `allocateNumberingDefinition`, and batch continuity uses
277
+ `runtimeContext.listFallbackSequenceState` (an entry is reused only while
278
+ its `numId` is bound in the live document). Text-equal redline header
279
+ conversions for letters, roman numerals and bullets call the same core.
280
+ The `paragraph-format` numbering binding (`properties.numbering`,
281
+ `{ numId, level }` or `null`) lives in `document-operation-mutations.js`: it
282
+ snapshots the earliest same-author `w:pPrChange`, refuses another author's
283
+ pending snapshot (`PARAGRAPH_FORMAT_REVISION_CONFLICT`), and checks the
284
+ binding with `checkNumberingBinding`. `list-format` is body-only; `openDocx`
285
+ refuses it with a `part` selector.
286
+ - `services/list-format-change.js`
287
+ - `list-format` `change-format` and `join` (`scope: 'list'` or `'range'`): rebind existing list items with
288
+ one tracked `w:pPrChange` each, never editing a shared definition.
289
+ `change-format` allocates a new definition through
290
+ `allocateNumberingDefinition` (source levels copied, one redefined, start
291
+ values on the new abstract); `join` reuses the earlier list's `numId`.
292
+ Planning models Word's counters (shared abstracts continue each other, a
293
+ level start re-applies on restart, deeper items start unused levels) and
294
+ refuses splits, joins and style-linked or instance-overridden definitions
295
+ it cannot reproduce exactly. Definitions are read from the source part plus
296
+ the batch's pending numbering parts.
297
+ - `services/operation-batch-compiler.js`
298
+ - Binds every operation to batch-start source paragraphs
299
+ (`SourceTargetRegistry`), compiles localized `replacements`, and detects
300
+ overlap before any mutation. Composition rule for `redline` + `list-format`:
301
+ exactly one single-paragraph `redline` (no `targetEnd`, anchor, part or
302
+ structured content) that keeps the typed marker and changes the text after
303
+ it composes with one `list-format` on the same paragraph; the compiler fixes
304
+ the order by setting `_compiledRunAfter` on the list-format, which the batch
305
+ orchestrator honours, so the redline always runs first. Everything else on a
306
+ list-format target is refused with `LIST_FORMAT_COMPOSITION_CONFLICT`
307
+ (`reason`: `marker-changed`, `redline-unchanged`, `redline-shape`, or
308
+ `list-rebind` when a `scope: 'list'` `change-format`/`join` rebinds an item
309
+ that another operation also writes). `list.continueFrom` is bound at batch
310
+ start as a read-only target. `previewSuccessor` lets the applier find a
311
+ target's replacement paragraph before commit.
191
312
  - `services/operation-preflight.js`
192
313
  - Read-only strict target and anchor resolution, revision-policy diagnostics,
193
314
  artifact prediction, and same-paragraph conflict reporting.
@@ -213,6 +334,42 @@ No Word add-in entrypoints or host-specific integration layers are part of this
213
334
  Mutation commands expose a compact contract: package/XML payloads and full
214
335
  validation arrays remain internal, while stdout contains durability fields,
215
336
  per-operation evidence, validation counts, and a derived completion flag.
337
+ - `services/revision-fidelity-verifier.js`
338
+ - Post-apply invariant check built on `core/word-revision-semantics.js`.
339
+ `verifyRevisionFidelity` compares Reject All of the output with Reject All
340
+ of the savepoint (both reject every author's revisions) and reports
341
+ `REJECT_PARAGRAPH_COUNT`, `REJECT_PARAGRAPH_TEXT`, `REJECT_PARAGRAPH_PROPS`,
342
+ `REJECT_NUMBERING_REFS` and `REJECT_WRAPPERS`. Under accept-first
343
+ existing-revision policies the baseline is the accepted view, scoped to the
344
+ resolved target range when known (otherwise the changed region between the
345
+ unchanged prefix and suffix). `REJECT_NUMBERING_REFS`
346
+ (`verifyNumberingReferences`) also refuses a numId that the output binds or
347
+ Reject All restores when the source did not use it and no `w:num` ->
348
+ `w:abstractNum` -> `w:lvl` defines it in the numbering available to the
349
+ operation (numbering existence). `verifyUntrackedLocality` covers
350
+ `generateRedlines: false` single-target operations with `ACCEPT_NEIGHBORS`:
351
+ no paragraph outside the target may change in either view.
352
+ `verifyNoCreatedRevisions` adds `UNTRACKED_CREATED_REVISION`: an untracked
353
+ operation may not introduce a revision id that was not in the source
354
+ (fragments of a sliced source revision, same element, author and date, are
355
+ allowed). `verifyAcceptIntent` adds `ACCEPT_INTENT`: a tracked plain-text
356
+ `redline` with complete `modified` (single paragraph or `targetEnd` range)
357
+ must read as `modified` in the Accept All view of its target; accept-first
358
+ range targets use the exact accepted scope.
359
+ `buildRevisionFidelityError` produces the `REVISION_FIDELITY_VIOLATION`
360
+ payload (`violations[].explanation`, `source.sourceParagraphIndex`,
361
+ `recovery.retryGuidance`). Modes are `enforce` (default, refuse and roll
362
+ back), `warn` (apply and append a warning) and `off`, resolved by
363
+ `resolveRevisionFidelityMode`: explicit `verifyRevisionFidelity` option,
364
+ then the diagnostic `DOCX_REDLINE_REVISION_FIDELITY` environment variable,
365
+ then `enforce`. The view memo is `createFidelityViews(document)`: lazily
366
+ filled `accepted` / `rejected` projections, revision-id signatures and
367
+ numbering references for one DOM. The applier builds a memo for the savepoint
368
+ (taking over `session._fidelityViews` when it still describes the live
369
+ document) and one for the result; after a committed tracked or untracked
370
+ operation the result's memo is stored back in `session._fidelityViews` as
371
+ the next operation's baseline, and a refused operation restores the prior
372
+ memo, so each view is computed at most once per DOM.
216
373
  - `orchestration/*`
217
374
  - Route planning and list fallback orchestration utilities.
218
375
 
@@ -221,15 +378,25 @@ No Word add-in entrypoints or host-specific integration layers are part of this
221
378
  1. Caller imports from `index.js`.
222
379
  2. Caller configures XML provider/logger/defaults when needed via `adapters/*`.
223
380
  3. Caller invokes reconciliation APIs (`applyRedlineToOxml`, operation runner, ingestion/export helpers).
224
- 4. A full-document batch parses once, captures its initial target snapshot, and
225
- creates an operation savepoint before each mutation.
381
+ 4. A full-document batch parses once, captures its initial target snapshot,
382
+ compiles the operations against it (`operation-batch-compiler.js`: batch-start
383
+ source binding, localized replacements, overlap and list-format composition
384
+ checks, all before any mutation), and creates an operation savepoint before
385
+ each mutation.
226
386
  5. `engine/oxml-engine.js` routes each selected scope to format, table, list,
227
387
  surgical, or reconstruction flows; successful scoped output is imported
228
388
  into the live document.
229
- 6. Failed and no-op operations restore their savepoint. A successful changed
389
+ 6. After each mutation the applier (`services/document-operation-applier.js`)
390
+ validates the generated OOXML against the savepoint, checks the localized
391
+ replacement intent, then runs the revision fidelity verifier (Reject All,
392
+ Accept All intent, neighbor, created-revision and numbering-existence
393
+ invariants); a violation in `enforce` mode restores the savepoint and
394
+ refuses the operation before anything is committed. Failed and no-op
395
+ operations restore their savepoint. A successful changed
230
396
  batch serializes the full document once; atomic failures and all-no-op
231
397
  batches return the exact input string without serialization.
232
- 7. Pipeline/services return OOXML, optional package artifacts (`numberingXml`, comments payloads), and non-breaking `status`/`error` fields where applicable.
398
+ 7. Pipeline/services return OOXML, optional package artifacts (`numberingXml`, comments payloads; numbering parts are merged by `mergeNumberingParts` and
399
+ checked by `validateNumberingIntegrity`), and non-breaking `status`/`error` fields where applicable.
233
400
  8. Optional revision/comment management transforms can accept/reject revisions, including move revisions, or delete comments by author.
234
401
  9. Caller writes resulting XML back to package/document boundaries.
235
402
 
@@ -265,6 +432,28 @@ still be re-exported from `index.js`.
265
432
  `COMMENTED_CONTENT_MERGE`; never discard anchors as a side effect of restoring
266
433
  the pre-revision baseline.
267
434
  Pass `existingRevisions: 'accept-all-first'` to normalize prior revisions explicitly.
435
+ - Every operation is checked after it is applied
436
+ (`services/revision-fidelity-verifier.js`, default `enforce`): Word's Reject
437
+ All of a tracked output must equal Word's Reject All of the source (`REJECT_*`),
438
+ a plain-text `redline` must read as its `modified` text under Accept All
439
+ (`ACCEPT_INTENT`), and an untracked edit must neither change other paragraphs
440
+ (`ACCEPT_NEIGHBORS`) nor create a revision (`UNTRACKED_CREATED_REVISION`). A
441
+ violation restores the operation savepoint and returns
442
+ `REVISION_FIDELITY_VIOLATION`; `warn` applies and warns, `off` is for
443
+ diagnosis only.
444
+ - Allocate, continue, merge and validate numbering only through
445
+ `services/numbering-allocation.js`. Never edit a shared `w:abstractNum` or
446
+ `w:num` in place, never delete a definition Reject All may need, and keep
447
+ `numPr` on tracked-deleted source paragraphs.
448
+ - Construct paragraph-mark `w:ins` / `w:del` only through
449
+ `engine/paragraph-mark-revisions.js`, and read current paragraph/run
450
+ properties only through `core/current-properties.js`. Two source-scanning
451
+ tests enforce this: `tests/paragraph_mark_single_site_tests.mjs` rejects
452
+ paragraph-mark revision construction (templates opening an `rPr` with
453
+ `ins`/`del`, DOM creation attached to an `rPr`, retired helper names) outside
454
+ the helper, and `tests/current_properties_usage_tests.mjs` rejects descendant
455
+ lookups of property names (`numPr`, `pStyle`, `b`, ...) outside the accessor.
456
+ Exceptions need an entry with a written reason in each test's allowlist.
268
457
  - Do not write unknown `result.oxml` payloads directly into `word/document.xml`;
269
458
  normalize with `extractReplacementNodesFromOoxml(...)` or use
270
459
  `applyOperationToDocumentXml(...).documentXml` for full-document replacement.
@@ -339,8 +528,10 @@ still be re-exported from `index.js`.
339
528
  - Comment IDs must be allocated against both document anchors and the existing
340
529
  `word/comments.xml` part. Revision IDs must be allocated against the complete
341
530
  document revision scope.
342
- - Numbering payloads must be merged with `mergeNumberingXmlBySchemaOrder` when
343
- a package already contains numbering definitions; replacement is not a merge.
531
+ - Numbering payloads are merged, never replaced, by the single merge in
532
+ `services/numbering-allocation.js` (`mergeNumberingParts`), which both the
533
+ runner's consumers and the `openDocx` facade call; conflicting ids fail with
534
+ `NUMBERING_ID_CONFLICT`.
344
535
  - Safe package mutation is transactional: retain the original package, apply
345
536
  operations, merge all artifacts, validate redline markup and package wiring,
346
537
  and commit only if every required check succeeds.
@@ -370,7 +561,7 @@ still be re-exported from `index.js`.
370
561
  text-bearing mutation. Mutation uses canonical accepted-view source text, and `resolvedTarget.targetTextMatch`
371
562
  records `exact`, `space_equivalent`, or `normalized` selection plus bounded
372
563
  invisible-character diagnostics.
373
- - CLI contract version 3 is deliberately narrower than library result objects.
564
+ - The CLI contract (version 9; `docx-redline version` lists the capabilities) is deliberately narrower than library result objects.
374
565
  `apply`, `accept`, `reject`, and `delete-comments` omit `documentXml`, package
375
566
  parts, inspection text, and full issue arrays. `completion` is true only when
376
567
  `written === true`, top-level status is neither error nor partial, and every
@@ -400,6 +591,10 @@ still be re-exported from `index.js`.
400
591
  is parsed from the serialized output XML parts (`word/document.xml`, `word/comments.xml`,
401
592
  `word/numbering.xml`). Any discrepancy immediately fails the transaction and triggers
402
593
  atomic rollback.
594
+ - The oracle checks that durable IDs are committed; the revision fidelity
595
+ verifier checks what Reject All would restore. A refused operation restores
596
+ its savepoint and receipt state like any other refusal, so it contributes no
597
+ committed IDs (`finalDisposition: 'refused'`).
403
598
 
404
599
 
405
600
  ## Build Output
@@ -437,11 +632,21 @@ The bundle inlines `diff-match-patch` and keeps `@xmldom/xmldom` external.
437
632
  - `npm run smoke:word:diff`
438
633
  - Windows/Word COM differential test: Word itself accepts/rejects the
439
634
  generated fixtures and the resulting text is compared to expectations.
635
+ Structural checks also compare paragraph counts, and list cases compare
636
+ `ListFormat.ListString` labels after `Revisions.AcceptAll()` /
637
+ `RejectAll()`.
440
638
  - `bash scripts/validate-fixtures-xsd.sh`
441
639
  - Validates exported fixtures against the ECMA-376 transitional `wml.xsd`.
640
+ - `tests/reject_roundtrip_matrix_tests.mjs` (part of `npm test`)
641
+ - Generated operation matrix over fixtures and operation kinds, asserting the
642
+ revision fidelity invariants and receipt reconciliation (a
643
+ `RECEIPT_RECONCILIATION_FAILED` fails the case). Replay with `FUZZ_SEED` /
644
+ `MATRIX_ITERATIONS`.
442
645
  - `.github/workflows/validation.yml`
443
646
  - Nightly independent-oracle validation: XSD schema check, LibreOffice
444
- conversion, and an extended 20k-case fuzz sweep with a fresh seed.
647
+ conversion, an extended 20k-case fuzz sweep with a fresh seed, and the
648
+ `matrix-extended` job (1000 reject-roundtrip matrix cases with a
649
+ date-derived seed; a failure prints its seed and replay command).
445
650
 
446
651
  Use these checks before publishing or tagging. See [docs/TESTING.md](./docs/TESTING.md#release-validation-and-independent-oracles).
447
652
 
@@ -454,7 +659,11 @@ Use this sequence to understand or modify behavior without reading everything:
454
659
  3. For targeting bugs, inspect `core/paragraph-targeting.js`, `core/list-targeting.js`, and `core/table-targeting.js`.
455
660
  4. For package wiring issues, inspect `services/standalone-docx-plumbing.js`.
456
661
  5. For revision/comment cleanup behavior, inspect `services/revision-comment-management.js`.
457
- 6. For numbering/list issues, inspect `services/numbering-helpers.js` and orchestration list-fallback modules.
662
+ 6. For numbering/list issues, inspect `services/numbering-allocation.js`,
663
+ `services/list-format*.js`, `services/list-conversion-diagnostics.js` and
664
+ orchestration list-fallback modules; for batch composition,
665
+ `services/operation-batch-compiler.js`; for Reject All refusals,
666
+ `services/revision-fidelity-verifier.js`.
458
667
  7. For reliability regressions, start with `tests/roundtrip_invariant_tests.mjs`,
459
668
  `tests/engine_reliability_tests.mjs`, `tests/paragraph_mark_revision_tests.mjs`,
460
669
  `tests/move_revision_tests.mjs`, and `tests/hardening_status_tests.mjs`.