docx/paraid-reproducible-save

The twin of the W5-E/W8-B rsidR fabrication bug: when python-docx saves a Word-authored document under reproducible=True, it synthesises w14:paraId and w14:textId on paragraphs whose source had no such attribute. Word treats paraId as a stable per-paragraph identifier; fabricating one at save-time breaks 'compare documents' and collaborative-editing rebase logic. Paragraph-alignment.docx, preserve-custom-xml.office.docx, and preserve-styles-with-effects.office.docx all surface this diff in the W8-F audit. Expected to go GREEN once paraId stamping is constrained the same way rsidR stamping now is.

Library verdicts: python-docx: — docxjs: —

Metadata

Feature iddocx/paraid-reproducible-save
Formatdocx
Categoryround-trip
Spececma-376-5-part-1 § 17.3.1.33 w14:paraId

XPath assertions

ID / partPredicateXPathpython-docxdocxjs
paraid-not-fabricated
word/document.xml
exist
At least one <w:p> in the body must remain without a w14:paraId attribute. Fails today — python-docx stamps every paragraph under reproducible=True.
//w:p[not(@w14:paraId)]

Render assertions

No render assertions declared.

Generator source

scripts/gen_paraid_reproducible_save.py

#!/usr/bin/env python3
"""Generate ``fixtures/docx/paraid-reproducible-save.docx``.

Loads the Word-authored paragraph-alignment.docx and re-saves it with
``reproducible=True``. Current python-docx will fabricate a
``w14:paraId`` on every paragraph that didn't carry one in the source
— the tombstone manifest fails when that happens. Once the
paraId-stamping fix lands (the twin of the W8-B rsidR fix), this
generator's output becomes assertion-compliant.
"""

from __future__ import annotations

from pathlib import Path

from docx import Document

_REPO_ROOT = Path(__file__).resolve().parent.parent
_SRC = _REPO_ROOT / "fixtures" / "docx" / "paragraph-alignment.docx"
_OUT = _REPO_ROOT / "fixtures" / "docx" / "paraid-reproducible-save.docx"


def main() -> int:
    doc = Document(str(_SRC))
    _OUT.parent.mkdir(parents=True, exist_ok=True)
    doc.save(str(_OUT), reproducible=True)
    print(_OUT)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Manifest (expanded)

{
  "$schema": "../manifest.schema.json",
  "id": "docx/paraid-reproducible-save",
  "title": "Reproducible save: w14:paraId/textId not fabricated on paragraphs that lack them (KNOWN FAILING — python-docx bug)",
  "format": "docx",
  "category": "round-trip",
  "roles": [
    "authoring"
  ],
  "summary": "The twin of the W5-E/W8-B rsidR fabrication bug: when python-docx saves a Word-authored document under reproducible=True, it synthesises w14:paraId and w14:textId on paragraphs whose source had no such attribute. Word treats paraId as a stable per-paragraph identifier; fabricating one at save-time breaks 'compare documents' and collaborative-editing rebase logic. Paragraph-alignment.docx, preserve-custom-xml.office.docx, and preserve-styles-with-effects.office.docx all surface this diff in the W8-F audit. Expected to go GREEN once paraId stamping is constrained the same way rsidR stamping now is.",
  "spec": {
    "source": "ecma-376-5-part-1",
    "clause": "17.3.1.33",
    "element": "w14:paraId",
    "notes": "w14:paraId and w14:textId are Word 2010+ attributes scoped to the http://schemas.microsoft.com/office/word/2010/wordml namespace. They mark paragraph and run identities so collaborative-editing features can track moved/edited content across revisions. python-docx's DocumentPart.before_marshal(reproducible=True) synthesises them deterministically (hash of content) when absent — same shape as the 2026.05.2 rsid generation, same fidelity bug: an already-identified paragraph shouldn't be re-stamped, but neither should an unidentified one be given a fresh identifier. The W8-B fix scoped rsidR to runs that already carry one; this manifest pins the paraId twin."
  },
  "fixtures": {
    "machine": "docx/paraid-reproducible-save"
  },
  "round_trip": {
    "library": "python-docx",
    "source_fixture": "docx/paragraph-alignment",
    "notes": "Source is the Word-authored paragraph-alignment.docx (one <w:p> with no w14:paraId in the source XML). Runner loads and re-saves it; assertion checks that no w14:paraId was fabricated."
  },
  "generator": {
    "python": "scripts/gen_paraid_reproducible_save.py"
  },
  "assertions": [
    {
      "id": "paraid-not-fabricated",
      "part": "word/document.xml",
      "namespaces": {
        "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
        "w14": "http://schemas.microsoft.com/office/word/2010/wordml"
      },
      "xpath": "//w:p[not(@w14:paraId)]",
      "must": "exist",
      "description": "At least one <w:p> in the body must remain without a w14:paraId attribute. Fails today — python-docx stamps every paragraph under reproducible=True."
    }
  ]
}

Fixture

Fixture file not found on disk.

Reference preview

No rendered reference is available for this case.

Spec notes

w14:paraId and w14:textId are Word 2010+ attributes scoped to the http://schemas.microsoft.com/office/word/2010/wordml namespace. They mark paragraph and run identities so collaborative-editing features can track moved/edited content across revisions. python-docx's DocumentPart.before_marshal(reproducible=True) synthesises them deterministically (hash of content) when absent — same shape as the 2026.05.2 rsid generation, same fidelity bug: an already-identified paragraph shouldn't be re-stamped, but neither should an unidentified one be given a fresh identifier. The W8-B fix scoped rsidR to runs that already carry one; this manifest pins the paraId twin.