python-hwpx product boundary

python-hwpx is the primary product: an independently useful HWPX document library comparable in role to python-docx.

Core owns

  • the document object model and public facade;

  • OPC package and OXML part ownership;

  • generic reading, traversal, editing, formatting, and table primitives;

  • serialization, byte/story preservation, validation, rollback, and recovery;

  • renderer-neutral quality contracts such as RenderBackend, EditMask, and VisualReport.

Companion layers own

python-hwpx-automation owns office workflows, genre/profile/policy decisions, agent plans, Hancom discovery, and renderer binding. hwpx-skill owns task judgment, routing, and prompt guidance.

Core must not import either companion package. A new core module that was not present at the 4.2.0 baseline requires an explicit entry in module-ownership.json; falling through the generic core rule is not enough.

Mixed-module decisions

  • quality and table_patch stay core.

  • renderer-neutral protocols are core; Hancom/backend binding and visual execution belong to python-hwpx-automation.

  • document diff keeps a generic diff model in core; task-plan composition moves.

  • mail merge keeps generic HWPX binding in core; sanitization/policy is injected.

  • tracked-change format primitives stay core; oracle verification moves.

  • benchmark and conformance campaign runners are repository QA, not product API.

The 5.0 boundary is closed: every non-core disposition (mcp-migrate, split, dev-only, and undecided) must remain exactly zero. A non-core file fails the ownership gate even if it would have fit beneath the historical baseline or carried a self-consistent ledger exception.

The 4.2 path baseline is the repository fixture module-ownership-baseline-4.2.json, pinned independently by path count and SHA-256 in the checker, ledger, and tests. The gate never reads Git history, so the same result is required in full, shallow, single-commit, and gitless source copies.

Removal is a second, independent invariant. The exact 77 paths removed in 5.0 are recorded in module-ownership-removed-5.0.json at SHA-256 4b8b4da35b3cf44503eb0dba05e335de1894ca5dd0b2fb668692a69e21ae6172. Every one must remain absent even if an empty file, a new implementation, or a new ledger exception would otherwise classify as core. Public-artifact hygiene normalizes both wheel members (hwpx/...) and sdist members (<archive-root>/src/hwpx/...) back to repository paths and applies the same zero-resurrection inventory.

Closed import capability

The gate AST-inspects every core Python module. Absolute imports are fail-closed: the allowed roots are the running Python’s standard library, hwpx, and the three declared runtime dependencies lxml, openpyxl, and latex2mathml. Everything else—including the companion products, MCP SDK, PDF/rendering, numerical, COM, and macOS GUI bindings—is rejected without maintaining a guessable denylist.

Dynamic loading is capability-gated, not inferred from literal target strings. Core cannot acquire importlib loaders, importlib.util, find_spec, builtin __import__, eval, or exec. subprocess and ctypes imports and os.system/os.popen (including the covered alias and reflective forms) are also forbidden. The only exceptions are:

  • one exact from importlib import resources import used by packaged templates;

  • PackageNotFoundError and version imported directly from importlib.metadata, inside _resolve_version() rather than at module scope — a module-level binding leaks hwpx.PackageNotFoundError onto the top-level surface even though it is not in __all__, so keep the import in the function;

  • the two code-pinned importlib.import_module(module_name) calls in src/hwpx/__init__.py.

The two lazy calls, their line/version-neutral AST fingerprint, and their 12-entry literal-map inventory are pinned. Each call also performs a runtime fail-closed check that the selected module is exactly hwpx or below hwpx.*; mutating the private map cannot turn it into an external import.

This is an architectural dependency ratchet, not a Python security sandbox. Arbitrary standard-library laundering, native extensions, generated bytecode, and intentionally obfuscated runtime code are outside its threat model. The gate covers normal source imports and the explicitly tested acquisition, aliasing, assignment, and reflection forms; release review and package tests remain responsible for hostile-code scenarios.