python-hwpx-automation 6.0.3__py3-none-any.whl

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 (217) hide show
  1. hwpx_automation/__init__.py +61 -0
  2. hwpx_automation/__init__.pyi +27 -0
  3. hwpx_automation/__main__.py +8 -0
  4. hwpx_automation/agent_document.py +392 -0
  5. hwpx_automation/api.py +136 -0
  6. hwpx_automation/blind_eval.py +407 -0
  7. hwpx_automation/capabilities.py +110 -0
  8. hwpx_automation/compat.py +48 -0
  9. hwpx_automation/configuration.py +60 -0
  10. hwpx_automation/core/__init__.py +2 -0
  11. hwpx_automation/core/content.py +762 -0
  12. hwpx_automation/core/context.py +111 -0
  13. hwpx_automation/core/diff.py +53 -0
  14. hwpx_automation/core/document.py +37 -0
  15. hwpx_automation/core/formatting.py +513 -0
  16. hwpx_automation/core/handles.py +24 -0
  17. hwpx_automation/core/locations.py +205 -0
  18. hwpx_automation/core/locator.py +162 -0
  19. hwpx_automation/core/plan.py +680 -0
  20. hwpx_automation/core/resources.py +42 -0
  21. hwpx_automation/core/search.py +296 -0
  22. hwpx_automation/core/transactions.py +434 -0
  23. hwpx_automation/core/txn.py +48 -0
  24. hwpx_automation/document_state.py +95 -0
  25. hwpx_automation/errors.py +174 -0
  26. hwpx_automation/execution_lock.py +15 -0
  27. hwpx_automation/fastmcp_adapter.py +672 -0
  28. hwpx_automation/form_fill.py +1177 -0
  29. hwpx_automation/form_output_models.py +223 -0
  30. hwpx_automation/handlers/__init__.py +2 -0
  31. hwpx_automation/handlers/_shared.py +377 -0
  32. hwpx_automation/handlers/agent_document.py +257 -0
  33. hwpx_automation/handlers/authoring.py +750 -0
  34. hwpx_automation/handlers/content_edit.py +1078 -0
  35. hwpx_automation/handlers/form_fill.py +607 -0
  36. hwpx_automation/handlers/layout_style.py +660 -0
  37. hwpx_automation/handlers/quality_render.py +566 -0
  38. hwpx_automation/handlers/read_export.py +1295 -0
  39. hwpx_automation/handlers/specialized.py +624 -0
  40. hwpx_automation/handlers/tracked_changes.py +589 -0
  41. hwpx_automation/handlers/workflow.py +105 -0
  42. hwpx_automation/hwp_converter.py +227 -0
  43. hwpx_automation/hwp_support.py +94 -0
  44. hwpx_automation/hwpx_ops.py +1439 -0
  45. hwpx_automation/identity.json +263 -0
  46. hwpx_automation/identity.py +18 -0
  47. hwpx_automation/ingest_adapters.py +85 -0
  48. hwpx_automation/markdown_plan.py +216 -0
  49. hwpx_automation/mcp_cli.py +29 -0
  50. hwpx_automation/metadata/tools_meta.py +40 -0
  51. hwpx_automation/mixed_form.py +3007 -0
  52. hwpx_automation/mutation_models.py +401 -0
  53. hwpx_automation/network_policy.py +232 -0
  54. hwpx_automation/office/__init__.py +14 -0
  55. hwpx_automation/office/agent/__init__.py +125 -0
  56. hwpx_automation/office/agent/_batch_verification.py +383 -0
  57. hwpx_automation/office/agent/blueprint/__init__.py +58 -0
  58. hwpx_automation/office/agent/blueprint/bundle.py +282 -0
  59. hwpx_automation/office/agent/blueprint/catalog.py +136 -0
  60. hwpx_automation/office/agent/blueprint/dump.py +520 -0
  61. hwpx_automation/office/agent/blueprint/mapping.py +312 -0
  62. hwpx_automation/office/agent/blueprint/model.py +722 -0
  63. hwpx_automation/office/agent/blueprint/native.py +621 -0
  64. hwpx_automation/office/agent/blueprint/replay.py +622 -0
  65. hwpx_automation/office/agent/catalog.py +252 -0
  66. hwpx_automation/office/agent/cli.py +647 -0
  67. hwpx_automation/office/agent/commands.py +1383 -0
  68. hwpx_automation/office/agent/document.py +801 -0
  69. hwpx_automation/office/agent/form_plan.py +1760 -0
  70. hwpx_automation/office/agent/model.py +808 -0
  71. hwpx_automation/office/agent/path.py +155 -0
  72. hwpx_automation/office/agent/query.py +230 -0
  73. hwpx_automation/office/agent/story.py +207 -0
  74. hwpx_automation/office/authoring/__init__.py +3542 -0
  75. hwpx_automation/office/authoring/advanced_generators.py +154 -0
  76. hwpx_automation/office/authoring/builder/__init__.py +52 -0
  77. hwpx_automation/office/authoring/builder/core.py +996 -0
  78. hwpx_automation/office/authoring/builder/report.py +195 -0
  79. hwpx_automation/office/authoring/design/__init__.py +30 -0
  80. hwpx_automation/office/authoring/design/_support.py +144 -0
  81. hwpx_automation/office/authoring/design/composer.py +282 -0
  82. hwpx_automation/office/authoring/design/harvest.py +305 -0
  83. hwpx_automation/office/authoring/design/plan.py +69 -0
  84. hwpx_automation/office/authoring/design/profile.py +88 -0
  85. hwpx_automation/office/authoring/design/profiles/application_form/fragments/body.xml +1 -0
  86. hwpx_automation/office/authoring/design/profiles/application_form/fragments/heading.xml +1 -0
  87. hwpx_automation/office/authoring/design/profiles/application_form/fragments/info_table.xml +1 -0
  88. hwpx_automation/office/authoring/design/profiles/application_form/fragments/title.xml +1 -0
  89. hwpx_automation/office/authoring/design/profiles/application_form/profile.json +25 -0
  90. hwpx_automation/office/authoring/design/profiles/application_form/template.hwpx +0 -0
  91. hwpx_automation/office/authoring/design/profiles/home_notice/fragments/body.xml +1 -0
  92. hwpx_automation/office/authoring/design/profiles/home_notice/fragments/heading.xml +1 -0
  93. hwpx_automation/office/authoring/design/profiles/home_notice/fragments/title.xml +1 -0
  94. hwpx_automation/office/authoring/design/profiles/home_notice/profile.json +24 -0
  95. hwpx_automation/office/authoring/design/profiles/home_notice/template.hwpx +0 -0
  96. hwpx_automation/office/authoring/design/profiles/official_notice/fragments/body.xml +1 -0
  97. hwpx_automation/office/authoring/design/profiles/official_notice/fragments/heading.xml +1 -0
  98. hwpx_automation/office/authoring/design/profiles/official_notice/fragments/info_table.xml +1 -0
  99. hwpx_automation/office/authoring/design/profiles/official_notice/fragments/title.xml +1 -0
  100. hwpx_automation/office/authoring/design/profiles/official_notice/profile.json +25 -0
  101. hwpx_automation/office/authoring/design/profiles/official_notice/template.hwpx +0 -0
  102. hwpx_automation/office/authoring/design/profiles/report/fragments/body.xml +1 -0
  103. hwpx_automation/office/authoring/design/profiles/report/fragments/heading.xml +1 -0
  104. hwpx_automation/office/authoring/design/profiles/report/fragments/info_table.xml +1 -0
  105. hwpx_automation/office/authoring/design/profiles/report/fragments/title.xml +1 -0
  106. hwpx_automation/office/authoring/design/profiles/report/profile.json +25 -0
  107. hwpx_automation/office/authoring/design/profiles/report/template.hwpx +0 -0
  108. hwpx_automation/office/authoring/design/validator.py +107 -0
  109. hwpx_automation/office/authoring/presets/__init__.py +22 -0
  110. hwpx_automation/office/authoring/presets/proposal.py +538 -0
  111. hwpx_automation/office/authoring/report_parser.py +141 -0
  112. hwpx_automation/office/authoring/style_profile.py +437 -0
  113. hwpx_automation/office/authoring/template_analyzer.py +657 -0
  114. hwpx_automation/office/compliance/__init__.py +38 -0
  115. hwpx_automation/office/compliance/official_lint.py +478 -0
  116. hwpx_automation/office/compliance/pii.py +388 -0
  117. hwpx_automation/office/document_ops/__init__.py +13 -0
  118. hwpx_automation/office/document_ops/comparison.py +62 -0
  119. hwpx_automation/office/document_ops/mail_merge.py +73 -0
  120. hwpx_automation/office/document_ops/redline.py +35 -0
  121. hwpx_automation/office/evalplan/__init__.py +36 -0
  122. hwpx_automation/office/evalplan/runtime.py +2762 -0
  123. hwpx_automation/office/exam/__init__.py +44 -0
  124. hwpx_automation/office/exam/compose.py +282 -0
  125. hwpx_automation/office/exam/ir.py +44 -0
  126. hwpx_automation/office/exam/measure.py +163 -0
  127. hwpx_automation/office/exam/parser.py +150 -0
  128. hwpx_automation/office/exam/profile.py +123 -0
  129. hwpx_automation/office/form_fill/__init__.py +66 -0
  130. hwpx_automation/office/form_fill/classification.py +108 -0
  131. hwpx_automation/office/form_fill/fill_residue.py +242 -0
  132. hwpx_automation/office/form_fill/fit/__init__.py +36 -0
  133. hwpx_automation/office/form_fill/fit/apply.py +24 -0
  134. hwpx_automation/office/form_fill/fit/engine.py +24 -0
  135. hwpx_automation/office/form_fill/fit/measure.py +50 -0
  136. hwpx_automation/office/form_fill/fit/policy.py +28 -0
  137. hwpx_automation/office/form_fill/fit/report.py +28 -0
  138. hwpx_automation/office/form_fill/fit/seal.py +457 -0
  139. hwpx_automation/office/form_fill/fit/wordbox.py +1343 -0
  140. hwpx_automation/office/form_fill/guidance.py +704 -0
  141. hwpx_automation/office/form_fill/quality.py +961 -0
  142. hwpx_automation/office/form_fill/split_run.py +333 -0
  143. hwpx_automation/office/form_fill/template_formfit.py +656 -0
  144. hwpx_automation/office/house_style/__init__.py +196 -0
  145. hwpx_automation/office/house_style/composition.py +68 -0
  146. hwpx_automation/office/house_style/data/bank.json +625 -0
  147. hwpx_automation/office/house_style/data/genres.json +43 -0
  148. hwpx_automation/office/quality/__init__.py +14 -0
  149. hwpx_automation/office/quality/page_guard.py +277 -0
  150. hwpx_automation/office/rendering/__init__.py +145 -0
  151. hwpx_automation/office/rendering/_hancom_open_rate.ps1 +374 -0
  152. hwpx_automation/office/rendering/_refresh_hwpx_mac.applescript +162 -0
  153. hwpx_automation/office/rendering/_render_hwpx.ps1 +72 -0
  154. hwpx_automation/office/rendering/_render_hwpx_mac.applescript +249 -0
  155. hwpx_automation/office/rendering/block_splits.py +76 -0
  156. hwpx_automation/office/rendering/detectors.py +151 -0
  157. hwpx_automation/office/rendering/diff.py +153 -0
  158. hwpx_automation/office/rendering/fixture_corpus.py +215 -0
  159. hwpx_automation/office/rendering/oracle.py +909 -0
  160. hwpx_automation/office/rendering/page_qa.py +245 -0
  161. hwpx_automation/office/rendering/qa_contracts.py +293 -0
  162. hwpx_automation/office/rendering/qa_metrics.py +241 -0
  163. hwpx_automation/office/rendering/worker.py +290 -0
  164. hwpx_automation/office/utilities/__init__.py +12 -0
  165. hwpx_automation/office/utilities/table_compute.py +477 -0
  166. hwpx_automation/ops_services/__init__.py +1 -0
  167. hwpx_automation/ops_services/_border_fill.py +283 -0
  168. hwpx_automation/ops_services/composition.py +55 -0
  169. hwpx_automation/ops_services/content_layout.py +322 -0
  170. hwpx_automation/ops_services/context.py +213 -0
  171. hwpx_automation/ops_services/form_fields.py +557 -0
  172. hwpx_automation/ops_services/media.py +178 -0
  173. hwpx_automation/ops_services/memo_style.py +477 -0
  174. hwpx_automation/ops_services/package_validation.py +166 -0
  175. hwpx_automation/ops_services/planning.py +201 -0
  176. hwpx_automation/ops_services/preview_export.py +585 -0
  177. hwpx_automation/ops_services/read_query.py +601 -0
  178. hwpx_automation/ops_services/save_policy.py +604 -0
  179. hwpx_automation/ops_services/tables.py +539 -0
  180. hwpx_automation/ops_services/transactions.py +616 -0
  181. hwpx_automation/preview_output_models.py +69 -0
  182. hwpx_automation/public-modules.json +206 -0
  183. hwpx_automation/py.typed +1 -0
  184. hwpx_automation/quality.py +351 -0
  185. hwpx_automation/quality_generation.py +725 -0
  186. hwpx_automation/runtime.py +321 -0
  187. hwpx_automation/runtime_services.py +100 -0
  188. hwpx_automation/server.py +259 -0
  189. hwpx_automation/storage.py +747 -0
  190. hwpx_automation/tool_bindings.py +170 -0
  191. hwpx_automation/tool_contract.py +982 -0
  192. hwpx_automation/upstream.py +755 -0
  193. hwpx_automation/utils/__init__.py +2 -0
  194. hwpx_automation/utils/helpers.py +29 -0
  195. hwpx_automation/visual_qa.py +667 -0
  196. hwpx_automation/workflow/__init__.py +55 -0
  197. hwpx_automation/workflow/adapters.py +482 -0
  198. hwpx_automation/workflow/dispatcher.py +213 -0
  199. hwpx_automation/workflow/models.py +243 -0
  200. hwpx_automation/workflow/policy.py +198 -0
  201. hwpx_automation/workflow/render_contracts.py +173 -0
  202. hwpx_automation/workflow/render_metrics.py +196 -0
  203. hwpx_automation/workflow/render_queue.py +482 -0
  204. hwpx_automation/workflow/render_security.py +172 -0
  205. hwpx_automation/workflow/render_transport.py +369 -0
  206. hwpx_automation/workflow/rendering.py +206 -0
  207. hwpx_automation/workflow/service.py +758 -0
  208. hwpx_automation/workflow/state_machine.py +65 -0
  209. hwpx_automation/workflow/store.py +747 -0
  210. hwpx_automation/workspace.py +1694 -0
  211. python_hwpx_automation-6.0.3.dist-info/METADATA +279 -0
  212. python_hwpx_automation-6.0.3.dist-info/RECORD +217 -0
  213. python_hwpx_automation-6.0.3.dist-info/WHEEL +5 -0
  214. python_hwpx_automation-6.0.3.dist-info/entry_points.txt +3 -0
  215. python_hwpx_automation-6.0.3.dist-info/licenses/LICENSE +178 -0
  216. python_hwpx_automation-6.0.3.dist-info/licenses/NOTICE +14 -0
  217. python_hwpx_automation-6.0.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,312 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Deterministic preflight and dependency mapping for blueprint replay."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import hashlib
7
+ import json
8
+ from dataclasses import dataclass
9
+ from typing import Any
10
+
11
+ from hwpx.document import HwpxDocument
12
+
13
+ from ..document import HwpxAgentDocument, NodeRecord
14
+ from ..model import AgentContractError
15
+ from .dump import _dependency, _numbering_properties, _style_dependency
16
+ from .native import create_character_format, create_numbering, create_paragraph_style
17
+
18
+ _CHILDREN: dict[str, frozenset[str]] = {
19
+ "document": frozenset({"section"}),
20
+ "section": frozenset({"paragraph", "memo"}),
21
+ "paragraph": frozenset(
22
+ {"run", "table", "picture", "shape", "footnote", "endnote", "form-field"}
23
+ ),
24
+ "table": frozenset({"row"}),
25
+ "row": frozenset({"cell"}),
26
+ "cell": frozenset({"paragraph"}),
27
+ "run": frozenset(),
28
+ "picture": frozenset(),
29
+ "shape": frozenset(),
30
+ "footnote": frozenset(),
31
+ "endnote": frozenset(),
32
+ "form-field": frozenset(),
33
+ "memo": frozenset(),
34
+ }
35
+
36
+ _ROOT_PARENT = {
37
+ "document": "document",
38
+ "section": "document",
39
+ "paragraph": "section",
40
+ "run": "paragraph",
41
+ "table": "paragraph",
42
+ "picture": "paragraph",
43
+ "shape": "paragraph",
44
+ "footnote": "paragraph",
45
+ "endnote": "paragraph",
46
+ "form-field": "paragraph",
47
+ "memo": "section",
48
+ "row": "table",
49
+ }
50
+
51
+
52
+ @dataclass(frozen=True, slots=True)
53
+ class ReplayPlan:
54
+ mode: str
55
+ root_id: str
56
+ root_kind: str
57
+ target_parent: str
58
+ dependency_decisions: tuple[dict[str, Any], ...]
59
+ node_fidelity: tuple[dict[str, Any], ...]
60
+
61
+ def to_dict(self) -> dict[str, Any]:
62
+ return {
63
+ "mode": self.mode,
64
+ "rootId": self.root_id,
65
+ "rootKind": self.root_kind,
66
+ "targetParent": self.target_parent,
67
+ "dependencyDecisions": [dict(item) for item in self.dependency_decisions],
68
+ "nodeFidelity": [dict(item) for item in self.node_fidelity],
69
+ }
70
+
71
+
72
+ def _stable_identity(value: dict[str, Any]) -> str:
73
+ return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
74
+
75
+
76
+ def _target_dependency_indexes(
77
+ document: HwpxDocument, view: HwpxAgentDocument
78
+ ) -> tuple[dict[str, list[dict[str, Any]]], dict[str, list[dict[str, Any]]], dict[str, list[dict[str, Any]]]]:
79
+ styles: dict[str, list[dict[str, Any]]] = {}
80
+ numbering: dict[str, list[dict[str, Any]]] = {}
81
+ resources: dict[str, list[dict[str, Any]]] = {}
82
+ for record in view.records:
83
+ dependency = _style_dependency(view, record)
84
+ if dependency is not None:
85
+ if record.kind == "paragraph":
86
+ identity = {
87
+ "styleIDRef": str(record.native.element.get("styleIDRef") or "0"),
88
+ "paraPrIDRef": str(record.native.element.get("paraPrIDRef") or "0"),
89
+ "name": str(record.summary.get("style") or ""),
90
+ }
91
+ else:
92
+ identity = {"charPrIDRef": str(record.native.char_pr_id_ref or "0")}
93
+ styles.setdefault(str(dependency["signature"]), []).append(identity)
94
+ properties = _numbering_properties(view, record)
95
+ if properties is not None:
96
+ dependency = _dependency("numbering", properties, name=str(properties["type"]))
97
+ numbering.setdefault(str(dependency["signature"]), []).append(
98
+ {"paraPrIDRef": str(record.native.element.get("paraPrIDRef") or "0")}
99
+ )
100
+ for item in document._package._manifest_items(): # type: ignore[attr-defined]
101
+ media_type = str(item.get("media-type") or "").casefold()
102
+ item_id = str(item.get("id") or "")
103
+ package_name = str(item.get("href") or "")
104
+ if not item_id or not package_name or not media_type.startswith("image/"):
105
+ continue
106
+ try:
107
+ payload = document._package.read(package_name) # type: ignore[attr-defined]
108
+ except Exception:
109
+ continue
110
+ digest = "sha256:" + hashlib.sha256(payload).hexdigest()
111
+ resources.setdefault(digest, []).append(
112
+ {"binaryItemIDRef": item_id, "mediaType": media_type}
113
+ )
114
+ for index in (styles, numbering, resources):
115
+ for signature in index:
116
+ index[signature] = sorted(index[signature], key=_stable_identity)
117
+ return styles, numbering, resources
118
+
119
+
120
+ def _preflight_graph(
121
+ manifest: dict[str, Any], target_parent: NodeRecord, mode: str
122
+ ) -> tuple[str, str]:
123
+ if manifest["mode"] != mode:
124
+ raise AgentContractError("verification_failed", "replay mode does not match bundle mode", target="mode")
125
+ if manifest["unsupported"] or not manifest["fidelity"]["replayable"]:
126
+ raise AgentContractError("unsupported_content", "blueprint is inspection-only", target="bundle")
127
+ nodes = {str(node["blueprintId"]): node for node in manifest["nodes"]}
128
+ root_id = str(manifest["root"]["blueprintId"])
129
+ root_kind = str(nodes[root_id]["kind"])
130
+ expected_parent = _ROOT_PARENT.get(root_kind)
131
+ if expected_parent is None or target_parent.kind != expected_parent:
132
+ raise AgentContractError(
133
+ "incompatible_parent",
134
+ f"{root_kind} replay requires a {expected_parent} parent, not {target_parent.kind}",
135
+ target=target_parent.path,
136
+ )
137
+ if root_kind in {"cell", "form-field"}:
138
+ raise AgentContractError("unsupported_content", f"standalone {root_kind} replay is unavailable")
139
+ parents: dict[str, str] = {}
140
+ for node_id, node in nodes.items():
141
+ allowed = _CHILDREN[str(node["kind"])]
142
+ for child_id in node["children"]:
143
+ child_kind = str(nodes[str(child_id)]["kind"])
144
+ if child_kind not in allowed:
145
+ raise AgentContractError(
146
+ "incompatible_parent",
147
+ f"{child_kind} is not a valid {node['kind']} child",
148
+ target=str(child_id),
149
+ )
150
+ parents[str(child_id)] = node_id
151
+ references = {
152
+ (str(item["from"]), str(item["field"])): str(item["to"])
153
+ for item in manifest["references"]
154
+ }
155
+ for node_id, node in nodes.items():
156
+ kind = str(node["kind"])
157
+ host_id = references.get((node_id, "hostRun"))
158
+ if host_id is not None:
159
+ if nodes[host_id]["kind"] != "run" or parents.get(host_id) != parents.get(node_id):
160
+ raise AgentContractError("invariant_violation", "hostRun must be a sibling run", target=node_id)
161
+ if kind == "form-field":
162
+ end_id = references.get((node_id, "endRun"))
163
+ if host_id is None or end_id is None or nodes[end_id]["kind"] != "run":
164
+ raise AgentContractError("invariant_violation", "form field run references are incomplete", target=node_id)
165
+ if parents.get(end_id) != parents.get(node_id):
166
+ raise AgentContractError("invariant_violation", "form field endRun is not a sibling", target=node_id)
167
+ if kind == "shape" and str(node["properties"].get("shapeType")) not in {"rect", "ellipse"}:
168
+ raise AgentContractError("unsupported_content", "shape type is not replayable", target=node_id)
169
+ return root_id, root_kind
170
+
171
+
172
+ def plan_replay(
173
+ document: HwpxDocument,
174
+ view: HwpxAgentDocument,
175
+ manifest: dict[str, Any],
176
+ target_parent: NodeRecord,
177
+ *,
178
+ mode: str,
179
+ ) -> ReplayPlan:
180
+ """Produce a complete no-mutation mapping plan."""
181
+
182
+ root_id, root_kind = _preflight_graph(manifest, target_parent, mode)
183
+ target_styles, target_numbering, target_resources = _target_dependency_indexes(document, view)
184
+ decisions: list[dict[str, Any]] = []
185
+ for dependency in manifest["styles"]:
186
+ source_key = str(dependency["key"])
187
+ signature = str(dependency["signature"])
188
+ candidates = target_styles.get(signature, [])
189
+ if candidates:
190
+ decisions.append(
191
+ {
192
+ "sourceKey": source_key,
193
+ "family": str(dependency.get("kind") or "style"),
194
+ "action": "reuse",
195
+ "fidelity": "exact",
196
+ "identity": candidates[0],
197
+ "properties": dict(dependency["properties"]),
198
+ }
199
+ )
200
+ elif mode == "source-bound":
201
+ raise AgentContractError("not_found", "source-bound style fingerprint is missing", target=source_key)
202
+ else:
203
+ decisions.append(
204
+ {
205
+ "sourceKey": source_key,
206
+ "family": str(dependency.get("kind") or "style"),
207
+ "action": "create",
208
+ "fidelity": "mapped",
209
+ "identity": None,
210
+ "properties": dict(dependency["properties"]),
211
+ }
212
+ )
213
+ for dependency in manifest["numbering"]:
214
+ source_key = str(dependency["key"])
215
+ candidates = target_numbering.get(str(dependency["signature"]), [])
216
+ if candidates:
217
+ action, fidelity, identity = "reuse", "exact", candidates[0]
218
+ elif mode == "source-bound":
219
+ raise AgentContractError("not_found", "source-bound numbering fingerprint is missing", target=source_key)
220
+ else:
221
+ action, fidelity, identity = "create", "mapped", None
222
+ decisions.append(
223
+ {
224
+ "sourceKey": source_key,
225
+ "family": "numbering",
226
+ "action": action,
227
+ "fidelity": fidelity,
228
+ "identity": identity,
229
+ "properties": dict(dependency["properties"]),
230
+ }
231
+ )
232
+ for resource in manifest["resources"]:
233
+ source_key = str(resource["key"])
234
+ candidates = [
235
+ item
236
+ for item in target_resources.get(str(resource["sha256"]), [])
237
+ if item["mediaType"] == resource["mediaType"]
238
+ ]
239
+ if candidates:
240
+ action, fidelity, identity = "reuse", "exact", candidates[0]
241
+ elif mode == "source-bound":
242
+ raise AgentContractError("not_found", "source-bound resource fingerprint is missing", target=source_key)
243
+ else:
244
+ action, fidelity, identity = "create", "mapped", None
245
+ decisions.append(
246
+ {
247
+ "sourceKey": source_key,
248
+ "family": "resource",
249
+ "action": action,
250
+ "fidelity": fidelity,
251
+ "identity": identity,
252
+ "properties": dict(resource),
253
+ }
254
+ )
255
+ decisions.sort(key=lambda item: (str(item["family"]), str(item["sourceKey"])))
256
+ node_fidelity = tuple(
257
+ {
258
+ "blueprintId": str(node["blueprintId"]),
259
+ "kind": str(node["kind"]),
260
+ "fidelity": "exact" if mode == "source-bound" else "mapped",
261
+ }
262
+ for node in manifest["nodes"]
263
+ )
264
+ return ReplayPlan(
265
+ mode=mode,
266
+ root_id=root_id,
267
+ root_kind=root_kind,
268
+ target_parent=target_parent.path,
269
+ dependency_decisions=tuple(decisions),
270
+ node_fidelity=node_fidelity,
271
+ )
272
+
273
+
274
+ def materialize_dependencies(
275
+ document: HwpxDocument,
276
+ plan: ReplayPlan,
277
+ assets: dict[str, bytes],
278
+ ) -> dict[str, dict[str, Any]]:
279
+ """Execute the already-complete dependency portion of a replay plan."""
280
+
281
+ targets: dict[str, dict[str, Any]] = {}
282
+ for raw in plan.dependency_decisions:
283
+ item = dict(raw)
284
+ if item["action"] == "create":
285
+ family = str(item["family"])
286
+ properties = dict(item["properties"])
287
+ if family == "style":
288
+ item["identity"] = create_paragraph_style(
289
+ document, properties, source_key=str(item["sourceKey"])
290
+ )
291
+ elif family == "char":
292
+ item["identity"] = {
293
+ "charPrIDRef": create_character_format(document, properties)
294
+ }
295
+ elif family == "numbering":
296
+ item["identity"] = {
297
+ "paraPrIDRef": create_numbering(document, properties)
298
+ }
299
+ elif family == "resource":
300
+ asset_path = str(properties["assetPath"])
301
+ suffix = asset_path.rsplit(".", 1)[-1]
302
+ item["identity"] = {
303
+ "binaryItemIDRef": document.add_image(assets[asset_path], suffix),
304
+ "mediaType": properties["mediaType"],
305
+ }
306
+ else: # pragma: no cover - validated dependency family exhaustiveness
307
+ raise AgentContractError("invariant_violation", f"unknown dependency family: {family}")
308
+ targets[str(item["sourceKey"])] = item
309
+ return targets
310
+
311
+
312
+ __all__ = ["ReplayPlan", "materialize_dependencies", "plan_replay"]