Design doc (read in full first): docs/design/docx_io_package_split.md — §2, §4 Slice 5, and §3.2 item 5 (order-dependency test).
Slice 5 of 5 — the final and largest behavioral surface of the docx_io.py package split. Land this last. At completion, docx_io.py becomes a thin compatibility shim and the package is complete.
Scope
Split the remaining in-memory hardening and revision-authoring code into two modules, with DocxMap becoming a thin coordinator in marcut/docx/document.py:
hardening.py — scrub_metadata() (docx_io.py:1801), harden_document() (docx_io.py:1711) and their private helpers (_unlink_hyperlinks, _strip_comment_markers*, _comment_visibility_map :1049, _remove_comment_entries, mail-merge/data-binding/hidden-text/invisible-object/header-footer/watermark/ink-annotation removal).
revisions.py (name it e.g. revision_writer.py to avoid confusion with the existing top-level marcut/docx_revisions.py, which handles accepting pre-existing revisions on load — a different concern): track-changes authoring — _make_text_run, _insert_deletion_after, _insert_insertion_after, apply_replacements() (docx_io.py:2292), _ensure_track_revisions_enabled.
document.py — DocxMap as a thin façade composing scan + hardening + revisions + zip_postprocess; owns load()/load_accepting_revisions()/save()/_postprocess_zip() and delegates the rest, preserving every existing public method signature exactly.
The critical order-dependency contract
save() runs ZIP-level hardening (_rewrite_docx_zip) only if scrub_metadata() ran first (i.e. self._metadata_settings was set). This is enforced by convention, not the type system, and this slice is the one most likely to disturb it. The order-dependency regression test from #71 must be green before and after this slice. If the split changes when _metadata_settings gets attached to the coordinator, hardening can silently stop running on save.
Compatibility shim
At the end of this slice, replace marcut/docx_io.py with a shim re-exporting the public surface (from .docx.document import DocxMap; from .docx.settings import MetadataCleaningSettings, CLI_ARG_PAIRS, ...) so any lingering from .docx_io import ... reference (incl. the 8 test files) keeps working during a deprecation window. Then update pipeline.py:13 and cli.py:9 to import from marcut.docx directly (this call-site update is a distinct commit from the extraction, per the "don't combine refactor + behavior change" rule — keep it mechanical and reviewable).
Non-negotiable requirements
- Blocked by BOTH
#70 and #71; land after #75 (scan.py) since document.py composes the scan type.
- Order-dependency test green before and after.
- Public
DocxMap surface byte-for-byte compatible; the shim keeps old import paths working.
- Golden-file harness diffs to zero.
Acceptance criteria
Verification loop (design doc §3.3)
PYTHONPATH=src/python python3 -m pytest tests/test_docx_io.py tests/test_metadata_scrubbing.py tests/test_pipeline.py tests/test_url_redaction.py tests/test_failure_scenarios.py tests/test_security.py -q.
- Golden-file harness — empty diff.
marcut redact against a sample-files/ fixture, diffed vs baseline.
Plus the standard gate: full pytest -q, swift build, swift test, bash scripts/release_preflight.sh.
Dependencies
Blocked by #70, #71, and #75. This is the final slice — land last.
Land as its own PR; do not combine with other issues in this set. Do not reopen design-spike #25.
Design doc (read in full first):
docs/design/docx_io_package_split.md— §2, §4 Slice 5, and §3.2 item 5 (order-dependency test).Slice 5 of 5 — the final and largest behavioral surface of the
docx_io.pypackage split. Land this last. At completion,docx_io.pybecomes a thin compatibility shim and the package is complete.Scope
Split the remaining in-memory hardening and revision-authoring code into two modules, with
DocxMapbecoming a thin coordinator inmarcut/docx/document.py:hardening.py—scrub_metadata()(docx_io.py:1801),harden_document()(docx_io.py:1711) and their private helpers (_unlink_hyperlinks,_strip_comment_markers*,_comment_visibility_map:1049,_remove_comment_entries, mail-merge/data-binding/hidden-text/invisible-object/header-footer/watermark/ink-annotation removal).revisions.py(name it e.g.revision_writer.pyto avoid confusion with the existing top-levelmarcut/docx_revisions.py, which handles accepting pre-existing revisions on load — a different concern): track-changes authoring —_make_text_run,_insert_deletion_after,_insert_insertion_after,apply_replacements()(docx_io.py:2292),_ensure_track_revisions_enabled.document.py—DocxMapas a thin façade composing scan + hardening + revisions + zip_postprocess; ownsload()/load_accepting_revisions()/save()/_postprocess_zip()and delegates the rest, preserving every existing public method signature exactly.The critical order-dependency contract
save()runs ZIP-level hardening (_rewrite_docx_zip) only ifscrub_metadata()ran first (i.e.self._metadata_settingswas set). This is enforced by convention, not the type system, and this slice is the one most likely to disturb it. The order-dependency regression test from#71must be green before and after this slice. If the split changes when_metadata_settingsgets attached to the coordinator, hardening can silently stop running on save.Compatibility shim
At the end of this slice, replace
marcut/docx_io.pywith a shim re-exporting the public surface (from .docx.document import DocxMap; from .docx.settings import MetadataCleaningSettings, CLI_ARG_PAIRS, ...) so any lingeringfrom .docx_io import ...reference (incl. the 8 test files) keeps working during a deprecation window. Then updatepipeline.py:13andcli.py:9to import frommarcut.docxdirectly (this call-site update is a distinct commit from the extraction, per the "don't combine refactor + behavior change" rule — keep it mechanical and reviewable).Non-negotiable requirements
#70and#71; land after#75(scan.py) sincedocument.pycomposes the scan type.DocxMapsurface byte-for-byte compatible; the shim keeps old import paths working.Acceptance criteria
hardening.py+revision_writer.py(or agreed name) +document.pycreated;DocxMapis a thin delegating coordinator with unchanged public signatures._metadata_settings→save()order-dependency test green before and after.marcut/docx_io.pyshim re-exports the full public surface; all 8 test-file imports still resolve.pipeline.py/cli.pyupdated to import frommarcut.docxdirectly (separate mechanical commit).#70) diffs to zero; full §3.3 loop green.marcut redactrun diffed against pre-slice baseline → no difference.Verification loop (design doc §3.3)
PYTHONPATH=src/python python3 -m pytest tests/test_docx_io.py tests/test_metadata_scrubbing.py tests/test_pipeline.py tests/test_url_redaction.py tests/test_failure_scenarios.py tests/test_security.py -q.marcut redactagainst asample-files/fixture, diffed vs baseline.Plus the standard gate: full
pytest -q,swift build,swift test,bash scripts/release_preflight.sh.Dependencies
Blocked by
#70,#71, and#75. This is the final slice — land last.Land as its own PR; do not combine with other issues in this set. Do not reopen design-spike #25.