Skip to content

feat(schema): add optional crossImplementation.suiteScenarioIds corpus field#526

Open
stevenobiajulu wants to merge 2 commits into
mainfrom
391-corpus-cross-implementation-suite-join-20260626
Open

feat(schema): add optional crossImplementation.suiteScenarioIds corpus field#526
stevenobiajulu wants to merge 2 commits into
mainfrom
391-corpus-cross-implementation-suite-join-20260626

Conversation

@stevenobiajulu

Copy link
Copy Markdown
Member

What

Adds an optional, additive crossImplementation: { suiteScenarioIds: string[] } field to each corpus entry in tests-corpus.schema.json, so a downstream tests renderer can join a safe-docx corpus entry to the cross-implementation suite repo's published results JSON.

Corpus entry ids are file/line-derived (scripts/build_tests_corpus.mjs) and unusable as cross-repo join keys; this field supplies stable suite scenario ids for "Other implementations" rows.

Changes

  • packages/test-narrative/src/tagSchema.ts: new SUITE_SCENARIO_IDS_TAG + suiteScenarioIdsSchema (non-empty, unique, trimmed id list). Kept out of the prose tagDefinitions (no word counts) since join keys are not prose.
  • packages/test-narrative/src/astExtractor.ts: parses a new @suiteScenarioIds JSDoc tag (comma/whitespace-separated, multi-line) into ScenarioEvidence.suiteScenarioIds, separate from the narrative object so it never leaks into the prose schema.
  • scripts/generate_tests_corpus_schema.mjs: adds the optional crossImplementation field + CrossImplementation $def to the generated schema.
  • scripts/build_tests_corpus.mjs: emits crossImplementation only when the tag is present, validating ids via the Zod schema with a file:line error on failure.
  • tests-corpus.schema.json: regenerated.
  • OpenSpec change add-corpus-cross-implementation-join (validated --strict), adding an ADDED requirement under the test-corpus-narrative capability.

Additive and optional: entries without the field stay valid.

OpenSpec

New capability/behavior on the corpus contract, so a change proposal was scaffolded and validated before coding.

Gates run (all green by exit code)

  • npm run build -w @usejunior/test-narrative — 0
  • npm run test:run -w @usejunior/test-narrative — 0 (43 tests; new AST + schema cases)
  • npm run lint:workspaces — 0 (warnings pre-existing)
  • npm run check:spec-coverage — 0
  • npm run check:conformance-citations — 0
  • npm run check:conformance-doc — 0
  • node scripts/check_tests_corpus_schema.mjs (drift gate) — 0
  • ajv spot-check: schema accepts entries with/without the field, rejects empty/duplicate id lists

Reviewer notes

  • The schema-drift gate (check:tests-corpus-schema) runs only in the release workflow, not on PR CI; verified locally instead. The regenerated tests-corpus.schema.json is committed.
  • The end-to-end build (build:tests-corpus) needs allure-results/ and was not run; population is covered by the in-build Zod validation + ajv spot-check. No test in-repo yet authors @suiteScenarioIds (none correspond to suite scenarios today) — the field stays absent until a test opts in.

Ref: #391 (tracking: #283)

…s field

Corpus entry ids are file/line-derived and unusable as cross-repo join
keys, so a tests renderer cannot place per-test "Other implementations"
rows next to a safe-docx scenario. Add an optional, additive
crossImplementation.suiteScenarioIds field to the corpus contract,
authored via a new @suiteScenarioIds JSDoc tag (a comma/whitespace list
of join keys, parsed statically), and emitted only when present. The
keys are not prose, so they live outside the word-count tagDefinitions
and outside the entry narrative object.

Ref: #391
@vercel

vercel Bot commented Jun 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
site Ready Ready Preview, Comment Jun 26, 2026 5:54am

Request Review

@github-actions github-actions Bot added the feat label Jun 26, 2026
@stevenobiajulu

Copy link
Copy Markdown
Member Author

Review: approve-with-nits

Adversarial pass done. The change is additive, optional, well-scoped to #391, and self-consistent. I verified the claimed gates rather than trusting the PR body.

Verified locally

  • npm run test:run -w @usejunior/test-narrative — green.
  • node scripts/check_tests_corpus_schema.mjsOK (regenerated tests-corpus.schema.json is not stale vs generate_tests_corpus_schema.mjs).
  • openspec validate add-corpus-cross-implementation-join --strict — valid (1 ADDED requirement, 3 scenarios).
  • Tests are real THEN-observable assertions, not tag-stuffing: astExtractor.test.ts:310-356 asserts exact suiteScenarioIds arrays, multiline comma/space splitting, absence→undefined, and that the key never leaks into narrative; tagSchema.test.ts:149-174 exercises accept/empty/blank/duplicate.
  • Parsing is sound: @suiteScenarioIds is not in tagDefinitions/rejectedAliases, so extractNarrative drops it (KNOWN_NARRATIVE_KEYS gate) while extractSuiteScenarioIds collects it separately — no prose/word-count contamination. Build-time buildCrossImplementation re-validates via Zod with a file:line error.
  • Conventions: branch 391-...-20260626 matches {issue}-{desc}-{YYYYMMDD}; footer uses Ref: #391 (correctly avoids Fixes # auto-close, appropriate since Optional: corpus-schema crossImplementation.suiteScenarioIds field for renderer joins #391 is an optional tracking item under Explore wpt.fyi-style cross-implementation conformance comparison (OOXML conformance subset only) #283). No TEST_FEATURE landmine — the touched files are it(...) unit tests, not .openspec()-tagged scenario files, and the test.openspec(...) strings are pre-existing fixtures.

No blocking findings.

Nits (non-blocking)

  1. PR body claims "43 tests"; the suite actually runs 35 (Tests 35 passed). Cosmetic, but the description should match.
  2. Commit scope feat(schema): — AGENTS.md says scope to the package; schema isn't a package and the change spans test-narrative + root scripts. feat(test-narrative) (or similar) would be more conventional.
  3. The end-to-end population path (scripts/build_tests_corpus.mjsbuildCrossImplementation) has no integration test and build:tests-corpus wasn't run (acknowledged in the PR). The error/emit path is only covered transitively by unit tests on the schema + AST. Acceptable for an additive, currently-unused field, but the first test that authors @suiteScenarioIds should add an e2e assertion.

@github-actions

Copy link
Copy Markdown
Contributor

LLM-Based Quality Gate

Overall: ⚠️ WARN (4 pass · 1 warn · 12 skipped · 17 total)

Check Verdict
⏭️ SKIPPED read_file response metadata parity paths not touched by this PR
⏭️ SKIPPED Live DOM namespace-safe OOXML writes paths not touched by this PR
Deleted field markup keeps w:fldChar outside w:del The PR does not touch field atomization, validateFieldStructure, or any w:fldChar/w:del elements, focusing instead on adding cross-implementation suite join keys to the test-narrative corpus.
⏭️ SKIPPED Field validation per story, not global paths not touched by this PR
⏭️ SKIPPED Revision IDs seeded from all revision-bearing side parts paths not touched by this PR
Accept/reject sweep side parts and caches The PR does not touch DocxDocument.acceptChanges, DocxDocument.rejectChanges, REVISION_STORY_PART_PATHS, accept_changes, reject_changes, or side-part revision markup, as it only adds cross-implementation join keys to the test-narrative extractor and schema.
⏭️ SKIPPED DocumentViewNode.heading stays canonical paths not touched by this PR
⏭️ SKIPPED AI-author parity across entry points paths not touched by this PR
⏭️ SKIPPED Property-change wrapper discipline paths not touched by this PR
⏭️ SKIPPED SUPPORT.md Table A drift vs. implementation paths not touched by this PR
⏭️ SKIPPED Table A / Table B boundary on side-part revisions paths not touched by this PR
⏭️ SKIPPED Canonical-emission surface completeness paths not touched by this PR
⏭️ SKIPPED Lean predicate drift against engine semantics (asymmetric) paths not touched by this PR
Unit-test quality (avoid tautological / change-detector tests) The tests added in astExtractor.test.ts:310 and tagSchema.test.ts:149 verify suiteScenarioIds extraction and schema parsing using concrete, independent input fixtures and schema validation assertions without tautological or change-detector SUT mocking.
⚠️ Re-derived facts vs canonical sources fldChar walk: n/a; footnote map: n/a; sibling re-implementation: WARN — extractSuiteScenarioIds in packages/test-narrative/src/astExtractor.ts:248 is a sibling re-implementation of the JSDoc parsing loop in extractNarrative (line 216) without using a shared helper or providing explicit justification.
.openspec tag ↔ test-assertion drift The PR does not add, move, or change any actual .openspec tags on test cases, only touching narrative-extractor and schema tests in packages/test-narrative.
⏭️ SKIPPED Library stays general (no downstream-domain leakage) paths not touched by this PR
Full checklist questions
  1. read_file response metadata parity: If this PR touches packages/docx-mcp/src/tools/read_file.ts, budgeted pagination returns, or additive response metadata like warnings / comment_load_error, do every successful return path (default budgeted early return, non-budget fallthrough, explicit limit/node_ids) preserve the same additive diagnostic fields? read_file has multiple success exits; diagnostics have already disappeared on one path before. Reference: fix(docx-core): declare xmlns:w14/w15 on comments root before writing prefixed attributes (#154) #180 surfaced comment_load_error, fix(docx-mcp): warn when read_file budget is exceeded by a single node (closes #184) #186 added an early budget return + warnings, fix(docx-mcp): surface comment_load_error on the default budgeted read path (closes #189) #191 fixed the missing comment_load_error on the default budgeted path.

  2. Live DOM namespace-safe OOXML writes: If this PR touches packages/docx-core/src/primitives/comments.ts or writes prefixed OOXML attributes/elements (w14:*, w15:*, xmlns:*, comments.xml, commentsExtended.xml, people.xml), are prefixed OOXML names written with namespace-aware APIs — root aliases bound with setAttributeNS(XMLNS_NS, ...), prefixed attributes with setAttributeNS(W14_NS/W15_NS, ...), and is there a test that proves the live DOM works before serialization/reparse? String-prefixed attributes can serialize plausibly while the live DOM still throws namespace errors. Reference: fix(docx-core): declare xmlns:w14/w15 on comments root before writing prefixed attributes (#154) #180 (xmlns:w14/w15 declared on comments root before writing prefixed attrs).

  3. Deleted field markup keeps w:fldChar outside w:del: If this PR touches field atomization, validateFieldStructure, hasFldCharInsideDel, w:fldChar, w:instrText, w:delInstrText, or collapsed field comparison logic, does deleted field output stay ECMA-376-conformant — w:fldChar sibling-level (never inside w:del), deleted instructions use w:delInstrText only inside valid delete wrappers, accept/reject safety checks still reject malformed combined output? Word treats deleted field-state markup in the wrong container as document-corrupting. References: fix(docx-core): validate w:delInstrText placement and reject w:fldChar inside <w:del> #211, fix(docx-core): partition field-closure validation by ECMA-376 story (#212) #225, fix(docx-core): fragment w:fldChar outside w:del per ECMA-376 Part 4 #228.

  4. Field validation per story, not global: If this PR touches packages/docx-core/src/baselines/atomizer/pipeline.ts, splitStories, validateFieldStructure, side-part merge logic, or footnote/endnote field handling, is field validation run independently per ECMA story (document.xml, each footnote, each endnote), with sidecars from both original and revised archives considered, and global counter balance not treated as sufficient? A document can be globally balanced but have an invalid field sequence inside one story. References: fix(docx-core): partition field-closure validation by ECMA-376 story (#212) #225, fix(docx-core): fragment w:fldChar outside w:del per ECMA-376 Part 4 #228, feat(docx-core): sweep side-part revisions on accept/reject #218.

  5. Revision IDs seeded from all revision-bearing side parts: If this PR touches packages/docx-mcp/src/session/manager.ts (especially getRevisionContextForSession or FIXED_REVISION_ID_SEED_PARTS), createRevisionContext, revision-ID allocation, or MCP tools that create tracked changes/comments/footnotes, does revision-ID allocation scan all relevant package parts before issuing new IDs — comments, footnotes, endnotes, glossary, headers, footers — ignore non-revision w:id values (comment IDs, bookmarks), and handle malformed optional parts gracefully? Revision IDs are package-wide; document-only seeding collides with existing side-part revisions. Reference: fix(docx-mcp): seed revision ids from side parts #216 (seed revision ids from side parts).

  6. Accept/reject sweep side parts and caches: If this PR touches DocxDocument.acceptChanges, DocxDocument.rejectChanges, REVISION_STORY_PART_PATHS, accept_changes, reject_changes, or side-part revision markup, does accept/reject process every revision-bearing story — updating document.xml + footnotes.xml + endnotes.xml + comments.xml, writing back only changed side parts while refreshing cached XML, and pruning orphan footnotes without deleting reserved separator entries? Accepting only in the main document leaves stale revisions and dangling references in the package. References: feat(docx-core): sweep side-part revisions on accept/reject #218, fix(docx-mcp): seed revision ids from side parts #216, fix(docx-core): partition field-closure validation by ECMA-376 story (#212) #225.

  7. DocumentViewNode.heading stays canonical: If this PR touches packages/docx-core/src/primitives/document_view.ts, HeadingValue, heading heuristics, ListMetadata.header_style, or Google Docs document-view heading normalization, does node.heading remain a structural heading signal — exact Word styles Heading1Heading6 win, heuristic sources suppressed inside table cells while real Word heading styles still pass, ordinary body paragraphs omit the heading key? Consumers use node.heading != null as a structural test; heuristic false positives break downstream navigation. References: fix(docx-core): harden heading detection (#157 Phase 1) #178, fix(docx-core): suppress non-sectional false-positive headings (closes #187) #188, feat(docx-core): add derived heading object to DocumentViewNode (closes #179) #190.

  8. AI-author parity across entry points: If this PR touches packages/docx-mcp/src/server.ts, packages/docx-mcp/src/cli/tool_runner.ts, packages/docx-mcp/src/cli/commands/**, or adds any new new SessionManager(...) call site in docx-mcp, does every entry point that constructs a SessionManager resolve SAFE_DOCX_AI_AUTHOR with the same three-way semantics (set → use it; empty string → opt out to untracked; unset → defaultAiAuthor), or has a new entry path silently bypassed tracked emission? Each entry path looks locally correct while diverging from another; tracked emission has gone dark in one path before anyone noticed. References: feat(docx-mcp): wire configurable AI author through MCP layer (#142) #172 (production MCP wiring would have kept tracked emission dark), fix(docx-mcp): honor SAFE_DOCX_AI_AUTHOR in CLI entry points (#181) #182 (CLI runners constructing bare SessionManager() silently produced untracked edits).

  9. Property-change wrapper discipline: If this PR touches packages/docx-core/src/primitives/layout.ts, packages/docx-core/src/primitives/text.ts, packages/docx-mcp/src/tools/clear_formatting.ts, or packages/docx-core/src/primitives/track-changes-emitter.ts, do tracked formatting/property edits emit exactly one correct *PrChange wrapper (pPrChange / rPrChange / trPrChange / tcPrChange) carrying a snapshot of the prior live properties — not stacking stale wrappers, not stripping valid historical children (cellIns/cellDel/cellMerge), and not omitting the snapshot when the operation is formatting-aware? Emitted OOXML is visually plausible but subtle snapshot mistakes only surface during later accept/reject or in Word's tracked-changes UI. References: feat(docx-core): emit pPrChange/trPrChange/tcPrChange from layout setters (#140) #167 (duplicate pPrChange/trPrChange/tcPrChange stacking + over-broad tcPr exclusion), feat(docx-mcp): emit rPrChange from clear_formatting MCP tool (#141) #170 (clear_formatting failing to strip stale rPrChange), feat(docx-core): emit rPrChange for formatted paragraph replacements #215 (rPrChange for formatted paragraph replacements + filtering nested stale records).

  10. SUPPORT.md Table A drift vs. implementation: If this PR modifies OOXML revision emission behavior (w:ins, w:del, w:rPrChange, etc.) in packages/docx-core/src/primitives/**, or touches packages/docx-core/SUPPORT.md, does the PR symmetrically update Table A in SUPPORT.md when the supported revision-emission surface in primitives changed — added, removed, or weakened — or is the documented contract now lying about what's supported? Reviewers focus on TS AST correctness and golden tests; Markdown contract tables get treated as an afterthought, so the documented surface drifts from the actual surface. Reference: [120.8] Regression suite for canonical revision emission across the surface #143 review caught replaceParagraphTextRange should emit w:rPrChange when run formatting changes #173 (formatting mismatch in Table A) and addCommentReply should emit body revision markup OR SUPPORT.md should be softened #174 (comment body revision omission forcing a Table A softening) late in peer review.

  11. Table A / Table B boundary on side-part revisions: If this PR touches packages/docx-core/src/primitives/comments.ts, packages/docx-core/src/primitives/footnotes.ts, or other side-part primitives, and adds/changes revision markup (w:ins, w:del), does tracked-change revision logic stay scoped to Table A (document-body content inside the side part) without leaking revision markup into Table B (the side-part package bootstrap — comments.xml/footnotes.xml element registration itself)? Body runs and side-part package elements share nearly identical XML namespace schemas; revisions emitted in the wrong table corrupt the package contract while looking plausible. References: [120.3] Emit w:ins/w:del for comment body anchors #138 (comment-body straddle constraints), [120.4] Emit w:ins/w:del for footnote reference and text #139 (footnote-reference straddle constraints).

  12. Canonical-emission surface completeness: If this PR adds or changes a tracked-edit surface in packages/docx-core/src/primitives/** or packages/docx-mcp/src/tools/**, are the paired artifacts updated together — packages/docx-core/src/integration/canonical-emission-regression.test.ts, packages/docx-mcp/src/integration/canonical-emission-mcp.test.ts, and the documented emitter surface (Table A) — or is the rollout only partially wired? The primitive change looks done before the MCP path, regression matrix, and documented contract are wired through; partial rollouts ship undocumented surface that drifts. References: feat(docx-mcp): wire configurable AI author through MCP layer (#142) #172 (RevisionContext threaded through every Table A MCP tool), test(docx-core,docx-mcp): final regression suite for canonical emission (#143) #175 (24-test regression suite + verified write-time emitter rows), feat(docx-core): emit rPrChange for formatted paragraph replacements #215 (re-enabled rPrChange regression + updated support surface for replaceParagraphTextRange).

  13. Lean predicate drift against engine semantics (asymmetric): If this PR changes field-wrapper semantics, the proof boundary, or atomizer behavior — packages/docx-core/src/baselines/atomizer/**, verification/lean/LeanSpike/Spec.lean, verification/lean/Tier2/**, or packages/docx-core/src/integration/lean-spec-bridge.test.ts — and if the TS engine semantics shifted, did the PR also update the Lean residual predicate and bridge tests, or is the proof now pinned to a stale stronger/weaker assumption? Asymmetric: a TS change without a corresponding Lean update is WARN; a Lean-only change without a TS update should not fire. The Lean side can still compile while the abstraction boundary is subtly wrong for the next engine refactor. References: feat(verification): close inv_field_001 with Tier 2 OoxmlDoc subset #208 (closed inv_field_001 using stronger recursivelyWellformed), refactor(verification): weaken inv_field_001 axiom to document-level preservationFriendly (rebased follow-up to #208) #220 (weakened the axiom to document-level preservationFriendly to avoid breakage when field fragmentation lands).

  14. Unit-test quality (avoid tautological / change-detector tests): If this PR adds or modifies any **/*.test.ts (or other test files), are the test assertions independent of the system under test — expected values constructed from first principles rather than re-derived from the function under test, mocks limited to external boundaries (filesystem, network, clocks) rather than mocking the SUT itself, assertions making concrete semantic claims rather than just snapshotting current behavior or asserting non-null, and any test added alongside a bug fix actually exercising the bug? Tests that re-implement the production code as the "expected" value, or mock out the system under test, pass green while providing no regression protection.

  15. Re-derived facts vs canonical sources: If this PR adds logic that re-computes a fact the codebase already derives elsewhere — another fldChar begin/separate/end visible-content walk (canonical: getParagraphRuns in packages/docx-core/src/primitives/text.ts), a second footnote display-number map (buildFootnoteDisplayMap vs getFootnotes().displayNumber), or any sibling re-implementation of an existing helper instead of importing or exporting it — does the PR consume the canonical source, or explicitly justify the new derivation and add a test pinning the copies in agreement? Independent derivations of the same fact drift apart; read_file renders every footnote marker twice in text/tagged_text ([^N][^N]) #382's doubled footnote markers were two passes disagreeing about one fact, and the fix initially added a fifth copy of the field-state walk. References: read_file renders every footnote marker twice in text/tagged_text ([^N][^N]) #382, fix(docx-mcp): render each footnote marker exactly once in read_file output #386, refactor: expose footnote references as document-view node metadata — one derivation of "visible footnote refs", not five fldChar walks #393.

  16. .openspec tag ↔ test-assertion drift: If this PR adds, moves, or changes any .openspec('[ID] …') tag on a test(...)/it(...), does the tagged test body actually exercise that scenario's GIVEN/WHEN/THEN — establishing the scenario's precondition and asserting its THEN observable — or is the tag attached without a covering assertion (tag-stuffing)? Judge each added/changed [ID] independently against the body of the test it sits on. Pay special attention to (a) tags moved onto generic clean/round-trip/property tests that never set up the scenario's specific precondition, (b) a single test absorbing several distinct scenario IDs it does not separately assert, and (c) "empirical proxy" / repo-convention justifications standing in for a real covering assertion. Deterministic coverage checks confirm the tag exists and (with the THEN-keyword guard) that a scenario token is mentioned; they cannot judge whether the assertion actually covers the scenario — this asymmetric, oversells-coverage drift is exactly what the gate is for. References: test(docx-core): raise docx-comparison spec coverage to 68/69 and split coverage scripts #513 (first pass tag-stuffed multiple docx-comparison scenarios onto tests that didn't assert them; only peer review caught it), check:spec-coverage never passes --strict to validate_openspec_coverage.mjs, so the docx-core matrix gate is WARN-only in CI #469.

  17. Library stays general (no downstream-domain leakage): If this PR adds or renames public API, types, recipes, functions, or identifiers in packages/*/src/**, does it avoid baking in names or concepts specific to a single downstream consumer's domain — agreements especially (signature, signatory, party, coverTerms, print-name/title/date signing semantics, or other agreement field names)? safe-docx is a general OOXML library: its surface should be Word/OOXML vocabulary (table, row, cell, border, run) plus a small allowlist of genuinely LLM-driven affordances that are not in Word but exist because LLMs are the primary consumers (e.g. an outline/TOC view sized for a context window). Domain-shaped composition (a "signature block" is just a w:tbl built from TableSpec/BorderSpec/RunProps) belongs in the consumer, not here. WARN if a new symbol is named for, or shaped around, one product rather than the general OOXML/LLM surface. See CONTRIBUTING "Library scope & domain boundaries". Reference: remove-agreement-domain-recipes removed coverTermsTable/signatureBlock for this reason.

Estimated cost (this run): $0.0087 — 26,265 input + 291 output tokens (≈4 chars/token) on gemini-3.5-flash. Char-count estimate, not provider telemetry.

@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

extractNarrative and extractSuiteScenarioIds each re-implemented the same
JSDoc comment loop (split lines, strip the `* ` gutter, match
@tag/continuation lines). The sibling derivation could silently drift from
the canonical narrative parse. Consolidate both onto a single
parseJsDocTags helper so tag recognition has one source of truth, with
extractNarrative last-winning per tag and extractSuiteScenarioIds
accumulating across repeats. Behavior and schema are unchanged; existing
tests pass. Addresses the LLM-gate "re-derived facts vs canonical sources"
finding.

Ref: #391
@stevenobiajulu

Copy link
Copy Markdown
Member Author

Addressed the blocking LLM-gate finding ("Re-derived facts vs canonical sources"). extractSuiteScenarioIds and extractNarrative no longer each re-implement the JSDoc comment loop — both now consume a single parseJsDocTags helper that owns tag recognition and the * gutter strip, so the two derivations can't drift. Observable behavior and schema are unchanged (last-wins per tag for narrative, accumulate-across-repeats for suite ids); all 43 test-narrative tests pass and build + lint:workspaces are green. (0af3e55)

@github-actions

Copy link
Copy Markdown
Contributor

LLM-Based Quality Gate

Overall: ✅ PASS (5 pass · 0 warn · 12 skipped · 17 total)

Check Verdict
⏭️ SKIPPED read_file response metadata parity paths not touched by this PR
⏭️ SKIPPED Live DOM namespace-safe OOXML writes paths not touched by this PR
Deleted field markup keeps w:fldChar outside w:del The PR does not touch field atomization, validateFieldStructure, or any field-state logic, focusing only on adding cross-implementation suite scenario join keys.
⏭️ SKIPPED Field validation per story, not global paths not touched by this PR
⏭️ SKIPPED Revision IDs seeded from all revision-bearing side parts paths not touched by this PR
Accept/reject sweep side parts and caches The PR does not touch DocxDocument.acceptChanges, DocxDocument.rejectChanges, REVISION_STORY_PART_PATHS, accept_changes, reject_changes, or side-part revision markup.
⏭️ SKIPPED DocumentViewNode.heading stays canonical paths not touched by this PR
⏭️ SKIPPED AI-author parity across entry points paths not touched by this PR
⏭️ SKIPPED Property-change wrapper discipline paths not touched by this PR
⏭️ SKIPPED SUPPORT.md Table A drift vs. implementation paths not touched by this PR
⏭️ SKIPPED Table A / Table B boundary on side-part revisions paths not touched by this PR
⏭️ SKIPPED Canonical-emission surface completeness paths not touched by this PR
⏭️ SKIPPED Lean predicate drift against engine semantics (asymmetric) paths not touched by this PR
Unit-test quality (avoid tautological / change-detector tests) The tests added in packages/test-narrative/src/astExtractor.test.ts:310-354 and packages/test-narrative/src/tagSchema.test.ts:149-178 are independent of the SUT. Assertions use explicitly defined inputs and expected values constructed from first principles without any mocking.
Re-derived facts vs canonical sources The PR avoids re-deriving JSDoc tag-parsing logic by refactoring the line-parsing loop into a shared canonical helper parseJsDocTags in packages/test-narrative/src/astExtractor.ts:220, which is then consumed by both extractNarrative and the new extractSuiteScenarioIds helper.
.openspec tag ↔ test-assertion drift The PR does not add, move, or change any actual .openspec tags on executable tests; it only updates extractor and schema tests in packages/test-narrative with mock fixtures.
⏭️ SKIPPED Library stays general (no downstream-domain leakage) paths not touched by this PR
Full checklist questions
  1. read_file response metadata parity: If this PR touches packages/docx-mcp/src/tools/read_file.ts, budgeted pagination returns, or additive response metadata like warnings / comment_load_error, do every successful return path (default budgeted early return, non-budget fallthrough, explicit limit/node_ids) preserve the same additive diagnostic fields? read_file has multiple success exits; diagnostics have already disappeared on one path before. Reference: fix(docx-core): declare xmlns:w14/w15 on comments root before writing prefixed attributes (#154) #180 surfaced comment_load_error, fix(docx-mcp): warn when read_file budget is exceeded by a single node (closes #184) #186 added an early budget return + warnings, fix(docx-mcp): surface comment_load_error on the default budgeted read path (closes #189) #191 fixed the missing comment_load_error on the default budgeted path.

  2. Live DOM namespace-safe OOXML writes: If this PR touches packages/docx-core/src/primitives/comments.ts or writes prefixed OOXML attributes/elements (w14:*, w15:*, xmlns:*, comments.xml, commentsExtended.xml, people.xml), are prefixed OOXML names written with namespace-aware APIs — root aliases bound with setAttributeNS(XMLNS_NS, ...), prefixed attributes with setAttributeNS(W14_NS/W15_NS, ...), and is there a test that proves the live DOM works before serialization/reparse? String-prefixed attributes can serialize plausibly while the live DOM still throws namespace errors. Reference: fix(docx-core): declare xmlns:w14/w15 on comments root before writing prefixed attributes (#154) #180 (xmlns:w14/w15 declared on comments root before writing prefixed attrs).

  3. Deleted field markup keeps w:fldChar outside w:del: If this PR touches field atomization, validateFieldStructure, hasFldCharInsideDel, w:fldChar, w:instrText, w:delInstrText, or collapsed field comparison logic, does deleted field output stay ECMA-376-conformant — w:fldChar sibling-level (never inside w:del), deleted instructions use w:delInstrText only inside valid delete wrappers, accept/reject safety checks still reject malformed combined output? Word treats deleted field-state markup in the wrong container as document-corrupting. References: fix(docx-core): validate w:delInstrText placement and reject w:fldChar inside <w:del> #211, fix(docx-core): partition field-closure validation by ECMA-376 story (#212) #225, fix(docx-core): fragment w:fldChar outside w:del per ECMA-376 Part 4 #228.

  4. Field validation per story, not global: If this PR touches packages/docx-core/src/baselines/atomizer/pipeline.ts, splitStories, validateFieldStructure, side-part merge logic, or footnote/endnote field handling, is field validation run independently per ECMA story (document.xml, each footnote, each endnote), with sidecars from both original and revised archives considered, and global counter balance not treated as sufficient? A document can be globally balanced but have an invalid field sequence inside one story. References: fix(docx-core): partition field-closure validation by ECMA-376 story (#212) #225, fix(docx-core): fragment w:fldChar outside w:del per ECMA-376 Part 4 #228, feat(docx-core): sweep side-part revisions on accept/reject #218.

  5. Revision IDs seeded from all revision-bearing side parts: If this PR touches packages/docx-mcp/src/session/manager.ts (especially getRevisionContextForSession or FIXED_REVISION_ID_SEED_PARTS), createRevisionContext, revision-ID allocation, or MCP tools that create tracked changes/comments/footnotes, does revision-ID allocation scan all relevant package parts before issuing new IDs — comments, footnotes, endnotes, glossary, headers, footers — ignore non-revision w:id values (comment IDs, bookmarks), and handle malformed optional parts gracefully? Revision IDs are package-wide; document-only seeding collides with existing side-part revisions. Reference: fix(docx-mcp): seed revision ids from side parts #216 (seed revision ids from side parts).

  6. Accept/reject sweep side parts and caches: If this PR touches DocxDocument.acceptChanges, DocxDocument.rejectChanges, REVISION_STORY_PART_PATHS, accept_changes, reject_changes, or side-part revision markup, does accept/reject process every revision-bearing story — updating document.xml + footnotes.xml + endnotes.xml + comments.xml, writing back only changed side parts while refreshing cached XML, and pruning orphan footnotes without deleting reserved separator entries? Accepting only in the main document leaves stale revisions and dangling references in the package. References: feat(docx-core): sweep side-part revisions on accept/reject #218, fix(docx-mcp): seed revision ids from side parts #216, fix(docx-core): partition field-closure validation by ECMA-376 story (#212) #225.

  7. DocumentViewNode.heading stays canonical: If this PR touches packages/docx-core/src/primitives/document_view.ts, HeadingValue, heading heuristics, ListMetadata.header_style, or Google Docs document-view heading normalization, does node.heading remain a structural heading signal — exact Word styles Heading1Heading6 win, heuristic sources suppressed inside table cells while real Word heading styles still pass, ordinary body paragraphs omit the heading key? Consumers use node.heading != null as a structural test; heuristic false positives break downstream navigation. References: fix(docx-core): harden heading detection (#157 Phase 1) #178, fix(docx-core): suppress non-sectional false-positive headings (closes #187) #188, feat(docx-core): add derived heading object to DocumentViewNode (closes #179) #190.

  8. AI-author parity across entry points: If this PR touches packages/docx-mcp/src/server.ts, packages/docx-mcp/src/cli/tool_runner.ts, packages/docx-mcp/src/cli/commands/**, or adds any new new SessionManager(...) call site in docx-mcp, does every entry point that constructs a SessionManager resolve SAFE_DOCX_AI_AUTHOR with the same three-way semantics (set → use it; empty string → opt out to untracked; unset → defaultAiAuthor), or has a new entry path silently bypassed tracked emission? Each entry path looks locally correct while diverging from another; tracked emission has gone dark in one path before anyone noticed. References: feat(docx-mcp): wire configurable AI author through MCP layer (#142) #172 (production MCP wiring would have kept tracked emission dark), fix(docx-mcp): honor SAFE_DOCX_AI_AUTHOR in CLI entry points (#181) #182 (CLI runners constructing bare SessionManager() silently produced untracked edits).

  9. Property-change wrapper discipline: If this PR touches packages/docx-core/src/primitives/layout.ts, packages/docx-core/src/primitives/text.ts, packages/docx-mcp/src/tools/clear_formatting.ts, or packages/docx-core/src/primitives/track-changes-emitter.ts, do tracked formatting/property edits emit exactly one correct *PrChange wrapper (pPrChange / rPrChange / trPrChange / tcPrChange) carrying a snapshot of the prior live properties — not stacking stale wrappers, not stripping valid historical children (cellIns/cellDel/cellMerge), and not omitting the snapshot when the operation is formatting-aware? Emitted OOXML is visually plausible but subtle snapshot mistakes only surface during later accept/reject or in Word's tracked-changes UI. References: feat(docx-core): emit pPrChange/trPrChange/tcPrChange from layout setters (#140) #167 (duplicate pPrChange/trPrChange/tcPrChange stacking + over-broad tcPr exclusion), feat(docx-mcp): emit rPrChange from clear_formatting MCP tool (#141) #170 (clear_formatting failing to strip stale rPrChange), feat(docx-core): emit rPrChange for formatted paragraph replacements #215 (rPrChange for formatted paragraph replacements + filtering nested stale records).

  10. SUPPORT.md Table A drift vs. implementation: If this PR modifies OOXML revision emission behavior (w:ins, w:del, w:rPrChange, etc.) in packages/docx-core/src/primitives/**, or touches packages/docx-core/SUPPORT.md, does the PR symmetrically update Table A in SUPPORT.md when the supported revision-emission surface in primitives changed — added, removed, or weakened — or is the documented contract now lying about what's supported? Reviewers focus on TS AST correctness and golden tests; Markdown contract tables get treated as an afterthought, so the documented surface drifts from the actual surface. Reference: [120.8] Regression suite for canonical revision emission across the surface #143 review caught replaceParagraphTextRange should emit w:rPrChange when run formatting changes #173 (formatting mismatch in Table A) and addCommentReply should emit body revision markup OR SUPPORT.md should be softened #174 (comment body revision omission forcing a Table A softening) late in peer review.

  11. Table A / Table B boundary on side-part revisions: If this PR touches packages/docx-core/src/primitives/comments.ts, packages/docx-core/src/primitives/footnotes.ts, or other side-part primitives, and adds/changes revision markup (w:ins, w:del), does tracked-change revision logic stay scoped to Table A (document-body content inside the side part) without leaking revision markup into Table B (the side-part package bootstrap — comments.xml/footnotes.xml element registration itself)? Body runs and side-part package elements share nearly identical XML namespace schemas; revisions emitted in the wrong table corrupt the package contract while looking plausible. References: [120.3] Emit w:ins/w:del for comment body anchors #138 (comment-body straddle constraints), [120.4] Emit w:ins/w:del for footnote reference and text #139 (footnote-reference straddle constraints).

  12. Canonical-emission surface completeness: If this PR adds or changes a tracked-edit surface in packages/docx-core/src/primitives/** or packages/docx-mcp/src/tools/**, are the paired artifacts updated together — packages/docx-core/src/integration/canonical-emission-regression.test.ts, packages/docx-mcp/src/integration/canonical-emission-mcp.test.ts, and the documented emitter surface (Table A) — or is the rollout only partially wired? The primitive change looks done before the MCP path, regression matrix, and documented contract are wired through; partial rollouts ship undocumented surface that drifts. References: feat(docx-mcp): wire configurable AI author through MCP layer (#142) #172 (RevisionContext threaded through every Table A MCP tool), test(docx-core,docx-mcp): final regression suite for canonical emission (#143) #175 (24-test regression suite + verified write-time emitter rows), feat(docx-core): emit rPrChange for formatted paragraph replacements #215 (re-enabled rPrChange regression + updated support surface for replaceParagraphTextRange).

  13. Lean predicate drift against engine semantics (asymmetric): If this PR changes field-wrapper semantics, the proof boundary, or atomizer behavior — packages/docx-core/src/baselines/atomizer/**, verification/lean/LeanSpike/Spec.lean, verification/lean/Tier2/**, or packages/docx-core/src/integration/lean-spec-bridge.test.ts — and if the TS engine semantics shifted, did the PR also update the Lean residual predicate and bridge tests, or is the proof now pinned to a stale stronger/weaker assumption? Asymmetric: a TS change without a corresponding Lean update is WARN; a Lean-only change without a TS update should not fire. The Lean side can still compile while the abstraction boundary is subtly wrong for the next engine refactor. References: feat(verification): close inv_field_001 with Tier 2 OoxmlDoc subset #208 (closed inv_field_001 using stronger recursivelyWellformed), refactor(verification): weaken inv_field_001 axiom to document-level preservationFriendly (rebased follow-up to #208) #220 (weakened the axiom to document-level preservationFriendly to avoid breakage when field fragmentation lands).

  14. Unit-test quality (avoid tautological / change-detector tests): If this PR adds or modifies any **/*.test.ts (or other test files), are the test assertions independent of the system under test — expected values constructed from first principles rather than re-derived from the function under test, mocks limited to external boundaries (filesystem, network, clocks) rather than mocking the SUT itself, assertions making concrete semantic claims rather than just snapshotting current behavior or asserting non-null, and any test added alongside a bug fix actually exercising the bug? Tests that re-implement the production code as the "expected" value, or mock out the system under test, pass green while providing no regression protection.

  15. Re-derived facts vs canonical sources: If this PR adds logic that re-computes a fact the codebase already derives elsewhere — another fldChar begin/separate/end visible-content walk (canonical: getParagraphRuns in packages/docx-core/src/primitives/text.ts), a second footnote display-number map (buildFootnoteDisplayMap vs getFootnotes().displayNumber), or any sibling re-implementation of an existing helper instead of importing or exporting it — does the PR consume the canonical source, or explicitly justify the new derivation and add a test pinning the copies in agreement? Independent derivations of the same fact drift apart; read_file renders every footnote marker twice in text/tagged_text ([^N][^N]) #382's doubled footnote markers were two passes disagreeing about one fact, and the fix initially added a fifth copy of the field-state walk. References: read_file renders every footnote marker twice in text/tagged_text ([^N][^N]) #382, fix(docx-mcp): render each footnote marker exactly once in read_file output #386, refactor: expose footnote references as document-view node metadata — one derivation of "visible footnote refs", not five fldChar walks #393.

  16. .openspec tag ↔ test-assertion drift: If this PR adds, moves, or changes any .openspec('[ID] …') tag on a test(...)/it(...), does the tagged test body actually exercise that scenario's GIVEN/WHEN/THEN — establishing the scenario's precondition and asserting its THEN observable — or is the tag attached without a covering assertion (tag-stuffing)? Judge each added/changed [ID] independently against the body of the test it sits on. Pay special attention to (a) tags moved onto generic clean/round-trip/property tests that never set up the scenario's specific precondition, (b) a single test absorbing several distinct scenario IDs it does not separately assert, and (c) "empirical proxy" / repo-convention justifications standing in for a real covering assertion. Deterministic coverage checks confirm the tag exists and (with the THEN-keyword guard) that a scenario token is mentioned; they cannot judge whether the assertion actually covers the scenario — this asymmetric, oversells-coverage drift is exactly what the gate is for. References: test(docx-core): raise docx-comparison spec coverage to 68/69 and split coverage scripts #513 (first pass tag-stuffed multiple docx-comparison scenarios onto tests that didn't assert them; only peer review caught it), check:spec-coverage never passes --strict to validate_openspec_coverage.mjs, so the docx-core matrix gate is WARN-only in CI #469.

  17. Library stays general (no downstream-domain leakage): If this PR adds or renames public API, types, recipes, functions, or identifiers in packages/*/src/**, does it avoid baking in names or concepts specific to a single downstream consumer's domain — agreements especially (signature, signatory, party, coverTerms, print-name/title/date signing semantics, or other agreement field names)? safe-docx is a general OOXML library: its surface should be Word/OOXML vocabulary (table, row, cell, border, run) plus a small allowlist of genuinely LLM-driven affordances that are not in Word but exist because LLMs are the primary consumers (e.g. an outline/TOC view sized for a context window). Domain-shaped composition (a "signature block" is just a w:tbl built from TableSpec/BorderSpec/RunProps) belongs in the consumer, not here. WARN if a new symbol is named for, or shaped around, one product rather than the general OOXML/LLM surface. See CONTRIBUTING "Library scope & domain boundaries". Reference: remove-agreement-domain-recipes removed coverTermsTable/signatureBlock for this reason.

Estimated cost (this run): $0.0092 — 27,967 input + 264 output tokens (≈4 chars/token) on gemini-3.5-flash. Char-count estimate, not provider telemetry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant