python-hwpx 3.7.0__tar.gz → 3.8.0__tar.gz

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 (375) hide show
  1. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/NOTICE +13 -1
  2. {python_hwpx-3.7.0/src/python_hwpx.egg-info → python_hwpx-3.8.0}/PKG-INFO +20 -5
  3. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/README.md +15 -4
  4. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/pyproject.toml +20 -1
  5. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/__init__.py +6 -0
  6. python_hwpx-3.8.0/src/hwpx/agent/_batch_verification.py +383 -0
  7. python_hwpx-3.8.0/src/hwpx/agent/blueprint/replay.py +622 -0
  8. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/commands.py +269 -467
  9. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/form_plan.py +167 -92
  10. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/authoring.py +206 -113
  11. python_hwpx-3.8.0/src/hwpx/equation/__init__.py +46 -0
  12. python_hwpx-3.8.0/src/hwpx/equation/eqedit.py +317 -0
  13. python_hwpx-3.8.0/src/hwpx/equation/mathml.py +69 -0
  14. python_hwpx-3.8.0/src/hwpx/equation/render.py +83 -0
  15. python_hwpx-3.8.0/src/hwpx/equation/tokens.py +256 -0
  16. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/evalplan_fill.py +347 -173
  17. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/fill_residue.py +73 -36
  18. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/table.py +216 -128
  19. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/__init__.py +8 -0
  20. python_hwpx-3.8.0/src/hwpx/tools/document_viewer.py +182 -0
  21. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/catalog.py +105 -84
  22. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/layout_preview.py +95 -16
  23. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/markdown_export.py +76 -54
  24. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/object_finder.py +100 -84
  25. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/toc_fidelity.py +87 -56
  26. python_hwpx-3.8.0/src/hwpx/visual/fixture_corpus.py +210 -0
  27. {python_hwpx-3.7.0 → python_hwpx-3.8.0/src/python_hwpx.egg-info}/PKG-INFO +20 -5
  28. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/SOURCES.txt +12 -0
  29. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/requires.txt +5 -0
  30. python_hwpx-3.8.0/tests/test_authoring_v2_block_validation.py +180 -0
  31. python_hwpx-3.8.0/tests/test_document_viewer.py +136 -0
  32. python_hwpx-3.8.0/tests/test_equation_converter.py +139 -0
  33. python_hwpx-3.8.0/tests/test_equation_render.py +69 -0
  34. python_hwpx-3.8.0/tests/test_fuzz_catalog_derive_expected.py +162 -0
  35. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_oxml_modularization.py +3 -1
  36. python_hwpx-3.7.0/src/hwpx/agent/blueprint/replay.py +0 -452
  37. python_hwpx-3.7.0/src/hwpx/visual/fixture_corpus.py +0 -175
  38. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/LICENSE +0 -0
  39. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/setup.cfg +0 -0
  40. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/__init__.py +0 -0
  41. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/_units.py +0 -0
  42. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/fields.py +0 -0
  43. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/layout.py +0 -0
  44. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/media.py +0 -0
  45. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/memos.py +0 -0
  46. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/persistence.py +0 -0
  47. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/shapes.py +0 -0
  48. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/_document/tracked.py +0 -0
  49. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/__init__.py +0 -0
  50. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/__init__.py +0 -0
  51. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/bundle.py +0 -0
  52. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/catalog.py +0 -0
  53. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/dump.py +0 -0
  54. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/mapping.py +0 -0
  55. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/model.py +0 -0
  56. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/native.py +0 -0
  57. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/catalog.py +0 -0
  58. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/cli.py +0 -0
  59. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/document.py +0 -0
  60. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/model.py +0 -0
  61. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/path.py +0 -0
  62. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/query.py +0 -0
  63. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/agent/story.py +0 -0
  64. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/benchmark/__init__.py +0 -0
  65. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/benchmark/blind_eval.py +0 -0
  66. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/body_patch.py +0 -0
  67. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/builder/__init__.py +0 -0
  68. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/builder/core.py +0 -0
  69. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/builder/report.py +0 -0
  70. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/__init__.py +0 -0
  71. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/badges.py +0 -0
  72. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus/corpus.json +0 -0
  73. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus/meeting_summary.hwpx +0 -0
  74. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus/notice.hwpx +0 -0
  75. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus/report_table.hwpx +0 -0
  76. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus.py +0 -0
  77. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/report.py +0 -0
  78. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/roundtrip_batch.py +0 -0
  79. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/conformance/runner.py +0 -0
  80. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/data/Skeleton.hwpx +0 -0
  81. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/__init__.py +0 -0
  82. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/_support.py +0 -0
  83. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/composer.py +0 -0
  84. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/harvest.py +0 -0
  85. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/plan.py +0 -0
  86. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profile.py +0 -0
  87. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/fragments/body.xml +0 -0
  88. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/fragments/heading.xml +0 -0
  89. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/fragments/info_table.xml +0 -0
  90. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/fragments/title.xml +0 -0
  91. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/profile.json +0 -0
  92. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/template.hwpx +0 -0
  93. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/fragments/body.xml +0 -0
  94. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/fragments/heading.xml +0 -0
  95. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/fragments/title.xml +0 -0
  96. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/profile.json +0 -0
  97. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/template.hwpx +0 -0
  98. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/fragments/body.xml +0 -0
  99. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/fragments/heading.xml +0 -0
  100. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/fragments/info_table.xml +0 -0
  101. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/fragments/title.xml +0 -0
  102. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/profile.json +0 -0
  103. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/template.hwpx +0 -0
  104. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/fragments/body.xml +0 -0
  105. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/fragments/heading.xml +0 -0
  106. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/fragments/info_table.xml +0 -0
  107. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/fragments/title.xml +0 -0
  108. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/profile.json +0 -0
  109. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/template.hwpx +0 -0
  110. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/design/validator.py +0 -0
  111. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/document.py +0 -0
  112. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/exam/__init__.py +0 -0
  113. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/exam/compose.py +0 -0
  114. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/exam/ir.py +0 -0
  115. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/exam/measure.py +0 -0
  116. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/exam/parser.py +0 -0
  117. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/exam/profile.py +0 -0
  118. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fill.py +0 -0
  119. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/__init__.py +0 -0
  120. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/apply.py +0 -0
  121. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/engine.py +0 -0
  122. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/measure.py +0 -0
  123. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/policy.py +0 -0
  124. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/report.py +0 -0
  125. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/seal.py +0 -0
  126. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/wordbox.py +0 -0
  127. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/formfill_quality.py +0 -0
  128. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/guidance_scan.py +0 -0
  129. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/ingest/__init__.py +0 -0
  130. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/ingest/base.py +0 -0
  131. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/ingest/hwpx_converter.py +0 -0
  132. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/layout/__init__.py +0 -0
  133. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/layout/lint.py +0 -0
  134. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/layout/report.py +0 -0
  135. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/mutation_report.py +0 -0
  136. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/opc/package.py +0 -0
  137. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/opc/relationships.py +0 -0
  138. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/opc/security.py +0 -0
  139. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/opc/xml_utils.py +0 -0
  140. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/__init__.py +0 -0
  141. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/_document_impl.py +0 -0
  142. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/_document_primitives.py +0 -0
  143. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/body.py +0 -0
  144. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/canonical_defaults.py +0 -0
  145. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/common.py +0 -0
  146. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/document.py +0 -0
  147. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/document_parts.py +0 -0
  148. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/header.py +0 -0
  149. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/header_part.py +0 -0
  150. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/memo.py +0 -0
  151. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/namespaces.py +0 -0
  152. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/numbering.py +0 -0
  153. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/objects.py +0 -0
  154. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/paragraph.py +0 -0
  155. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/parser.py +0 -0
  156. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/run.py +0 -0
  157. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/schema.py +0 -0
  158. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/section.py +0 -0
  159. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/section_format.py +0 -0
  160. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/section_story.py +0 -0
  161. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/simple_parts.py +0 -0
  162. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/oxml/utils.py +0 -0
  163. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/package.py +0 -0
  164. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/patch.py +0 -0
  165. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/presets/__init__.py +0 -0
  166. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/presets/proposal.py +0 -0
  167. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/py.typed +0 -0
  168. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/quality/__init__.py +0 -0
  169. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/quality/ledger.py +0 -0
  170. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/quality/policy.py +0 -0
  171. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/quality/report.py +0 -0
  172. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/quality/save_pipeline.py +0 -0
  173. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/table_patch.py +0 -0
  174. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/template_formfit.py +0 -0
  175. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/templates.py +0 -0
  176. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/_schemas/header.xsd +0 -0
  177. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/_schemas/section.xsd +0 -0
  178. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/advanced_generators.py +0 -0
  179. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/archive_cli.py +0 -0
  180. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/doc_diff.py +0 -0
  181. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/exporter.py +0 -0
  182. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/__init__.py +0 -0
  183. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/__main__.py +0 -0
  184. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/generator.py +0 -0
  185. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/minimize.py +0 -0
  186. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/runner.py +0 -0
  187. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/generic_inventory.py +0 -0
  188. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/id_integrity.py +0 -0
  189. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/idempotence.py +0 -0
  190. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/ir_equality.py +0 -0
  191. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/mail_merge.py +0 -0
  192. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/official_lint.py +0 -0
  193. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/package_reconcile.py +0 -0
  194. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/package_validator.py +0 -0
  195. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/page_guard.py +0 -0
  196. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/pii.py +0 -0
  197. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/read_fidelity.py +0 -0
  198. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/recover.py +0 -0
  199. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/redline.py +0 -0
  200. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/repair.py +0 -0
  201. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/report_parser.py +0 -0
  202. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/report_utils.py +0 -0
  203. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/roundtrip_diff.py +0 -0
  204. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/style_profile.py +0 -0
  205. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/table_cleanup.py +0 -0
  206. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/table_compute.py +0 -0
  207. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/table_navigation.py +0 -0
  208. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/template_analyzer.py +0 -0
  209. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/text_extract_cli.py +0 -0
  210. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/text_extractor.py +0 -0
  211. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/toc_author.py +0 -0
  212. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/tools/validator.py +0 -0
  213. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/__init__.py +0 -0
  214. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/_hancom_open_rate.ps1 +0 -0
  215. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/_refresh_hwpx_mac.applescript +0 -0
  216. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/_render_hwpx.ps1 +0 -0
  217. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/_render_hwpx_mac.applescript +0 -0
  218. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/detectors.py +0 -0
  219. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/diff.py +0 -0
  220. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/hancom_worker.py +0 -0
  221. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/masks.py +0 -0
  222. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/oracle.py +0 -0
  223. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/page_qa.py +0 -0
  224. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/qa_contracts.py +0 -0
  225. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/qa_metrics.py +0 -0
  226. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/hwpx/visual/report.py +0 -0
  227. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
  228. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/entry_points.txt +0 -0
  229. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/top_level.txt +0 -0
  230. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_advanced_generators.py +0 -0
  231. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_blueprint_contracts.py +0 -0
  232. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_blueprint_dump.py +0 -0
  233. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_blueprint_replay.py +0 -0
  234. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_catalog.py +0 -0
  235. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_cli.py +0 -0
  236. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_commands.py +0 -0
  237. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_contracts.py +0 -0
  238. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_document.py +0 -0
  239. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_mixed_form.py +0 -0
  240. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_path_query.py +0 -0
  241. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_agent_story_commands.py +0 -0
  242. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_authoring_native_toc.py +0 -0
  243. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_authoring_profile_routing.py +0 -0
  244. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_authoring_profile_routing_oracle.py +0 -0
  245. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_authoring_quality_corpus.py +0 -0
  246. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_authoring_render_check.py +0 -0
  247. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_blind_eval_fixture.py +0 -0
  248. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_body_patch.py +0 -0
  249. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_builder_core.py +0 -0
  250. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_builder_plan_v2.py +0 -0
  251. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_builder_vertical_slice.py +0 -0
  252. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_byte_patch_identity.py +0 -0
  253. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_cell_line_spacing.py +0 -0
  254. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_comment_node_robustness.py +0 -0
  255. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_conformance.py +0 -0
  256. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_corpus_read_fidelity.py +0 -0
  257. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_corpus_toc_verify.py +0 -0
  258. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_coverage_promotion.py +0 -0
  259. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_coverage_targets.py +0 -0
  260. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_design_builder.py +0 -0
  261. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_deviations_registry.py +0 -0
  262. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_doc_diff.py +0 -0
  263. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_document_context_manager.py +0 -0
  264. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_document_facade_surface.py +0 -0
  265. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_document_formatting.py +0 -0
  266. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_document_plan.py +0 -0
  267. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_document_plan_computed_fields.py +0 -0
  268. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_document_save_api.py +0 -0
  269. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_evalplan_fill.py +0 -0
  270. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_exam_compose.py +0 -0
  271. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_exam_compose_oracle.py +0 -0
  272. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_exam_fixtures.py +0 -0
  273. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_exam_ir.py +0 -0
  274. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_exam_measure.py +0 -0
  275. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_exam_parser.py +0 -0
  276. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_exam_profile.py +0 -0
  277. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_existing_document_format_editing.py +0 -0
  278. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_fill_residue.py +0 -0
  279. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_form_fields.py +0 -0
  280. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_form_fill_split_run.py +0 -0
  281. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_form_fit.py +0 -0
  282. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_form_fit_integration.py +0 -0
  283. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_form_fit_seal.py +0 -0
  284. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_form_fit_seal_placement.py +0 -0
  285. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_form_fit_wordbox.py +0 -0
  286. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_formfill_differential_driver.py +0 -0
  287. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_formfill_quality.py +0 -0
  288. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_fuzz_loop.py +0 -0
  289. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_fuzz_regressions.py +0 -0
  290. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_gap_closure_tools.py +0 -0
  291. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_government_report_preset.py +0 -0
  292. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_government_table_profile.py +0 -0
  293. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_guidance_scan.py +0 -0
  294. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_hancom_render_worker.py +0 -0
  295. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_hp_tab_support.py +0 -0
  296. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_hwpxlib_corpus_read.py +0 -0
  297. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_id_generator_range.py +0 -0
  298. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_id_integrity.py +0 -0
  299. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_image_object_workflow.py +0 -0
  300. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_ingest.py +0 -0
  301. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_inline_models.py +0 -0
  302. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_integration_hwpx_compatibility.py +0 -0
  303. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_integration_roundtrip.py +0 -0
  304. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_kordoc_absorption.py +0 -0
  305. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_layout_cache_scope.py +0 -0
  306. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_layout_lint.py +0 -0
  307. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_layout_preview.py +0 -0
  308. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_m9_p0_spike_tools.py +0 -0
  309. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_mail_merge_fit.py +0 -0
  310. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_mail_merge_table_compute.py +0 -0
  311. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_mail_merge_xlsx.py +0 -0
  312. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_markdown_export.py +0 -0
  313. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_memo_and_style_editing.py +0 -0
  314. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_mixed_form_plan.py +0 -0
  315. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_mutation_report.py +0 -0
  316. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_mutation_report_projections.py +0 -0
  317. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_namespace_handling.py +0 -0
  318. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_new_features.py +0 -0
  319. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_official_document_style.py +0 -0
  320. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_official_lint_gongmun_gate.py +0 -0
  321. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_official_lint_tableaware.py +0 -0
  322. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_opc_package.py +0 -0
  323. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_open_safety_corpus.py +0 -0
  324. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_openrate.py +0 -0
  325. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_oxml_parsing.py +0 -0
  326. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_packaging_license_metadata.py +0 -0
  327. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_packaging_py_typed.py +0 -0
  328. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_paragraph_keep_together.py +0 -0
  329. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_paragraph_section_management.py +0 -0
  330. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_pii.py +0 -0
  331. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_pii_leak_sweep.py +0 -0
  332. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_pii_structural.py +0 -0
  333. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_pii_wiring.py +0 -0
  334. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_proposal_preset.py +0 -0
  335. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_public_runtime_boundary.py +0 -0
  336. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_question_split_detector.py +0 -0
  337. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_read_fidelity.py +0 -0
  338. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_recover_broken_zip.py +0 -0
  339. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_redline_authoring.py +0 -0
  340. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_redline_verify.py +0 -0
  341. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_repair_repack.py +0 -0
  342. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_report_parser.py +0 -0
  343. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_report_utils.py +0 -0
  344. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_repr_snapshots.py +0 -0
  345. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_rhwp_t1_gates.py +0 -0
  346. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_rhwp_t2_verification.py +0 -0
  347. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_roundtrip_fidelity.py +0 -0
  348. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_row_heights.py +0 -0
  349. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_save_pipeline.py +0 -0
  350. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_save_pipeline_no_bypass.py +0 -0
  351. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_section_headers.py +0 -0
  352. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_set_paragraph_format_keep.py +0 -0
  353. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_skeleton_template_ids.py +0 -0
  354. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_split_cell.py +0 -0
  355. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_split_merged_cell.py +0 -0
  356. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_style_profile.py +0 -0
  357. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_table_cleanup.py +0 -0
  358. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_table_navigation.py +0 -0
  359. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_table_patch.py +0 -0
  360. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_table_patch_dryrun.py +0 -0
  361. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_table_patch_m105.py +0 -0
  362. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_tables_default_border.py +0 -0
  363. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_template_analyzer_enrichment.py +0 -0
  364. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_template_formfit.py +0 -0
  365. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_text_extractor_annotations.py +0 -0
  366. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_toc_author.py +0 -0
  367. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_toc_fidelity.py +0 -0
  368. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_validation_severity.py +0 -0
  369. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_validator_comment_nodes.py +0 -0
  370. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_version_metadata.py +0 -0
  371. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_visual_fixture_corpus.py +0 -0
  372. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_visual_oracle.py +0 -0
  373. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_visual_oracle_budget.py +0 -0
  374. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_visual_qa_contracts.py +0 -0
  375. {python_hwpx-3.7.0 → python_hwpx-3.8.0}/tests/test_visual_qa_metrics.py +0 -0
@@ -29,10 +29,22 @@ reimplemented from public references, without copying source code:
29
29
  from public model headers only. hwpxlib sample packages were used only as
30
30
  structure and roundtrip fixtures.
31
31
 
32
- These form-fill references were used as behavior evidence only. The
32
+ - OpenBapul/hml-equation-parser (Apache-2.0): HULK-style EqEdit <-> LaTeX token
33
+ mapping, referenced for reader-direction <hp:equation> script rendering. The
34
+ token maps are a clean-room re-derivation from the public EqEdit grammar; no
35
+ source code was copied, translated, or vendored.
36
+
37
+ These form-fill and equation references were used as behavior evidence only. The
33
38
  implementation is a clean-room Python implementation; no source code was
34
39
  copied, translated, vendored, or linked.
35
40
 
41
+ ## Optional dependency: latex2mathml
42
+
43
+ The optional ``preview`` extra depends on roniemartinez/latex2mathml (MIT), used
44
+ to convert LaTeX to MathML for the document preview viewer. It is an unmodified
45
+ third-party dependency installed via pip; its MIT license travels with the
46
+ package.
47
+
36
48
  ## Test fixtures: hwpxlib sample corpus
37
49
 
38
50
  tests/fixtures/hwpxlib_corpus/ contains .hwpx sample files vendored from
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hwpx
3
- Version: 3.7.0
3
+ Version: 3.8.0
4
4
  Summary: 한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리
5
5
  Author: python-hwpx Maintainers
6
6
  License-Expression: Apache-2.0
@@ -27,10 +27,13 @@ Requires-Dist: pillow>=10.0; extra == "visual"
27
27
  Requires-Dist: numpy>=1.26; extra == "visual"
28
28
  Provides-Extra: xlsx
29
29
  Requires-Dist: openpyxl>=3.1; extra == "xlsx"
30
+ Provides-Extra: preview
31
+ Requires-Dist: latex2mathml>=3.77; extra == "preview"
30
32
  Provides-Extra: dev
31
33
  Requires-Dist: build>=1.0; extra == "dev"
32
34
  Requires-Dist: twine>=4.0; extra == "dev"
33
35
  Requires-Dist: pytest>=7.4; extra == "dev"
36
+ Requires-Dist: latex2mathml>=3.77; extra == "dev"
34
37
  Provides-Extra: test
35
38
  Requires-Dist: build>=1.0; extra == "test"
36
39
  Requires-Dist: numpy>=1.26; extra == "test"
@@ -38,6 +41,7 @@ Requires-Dist: pillow>=10.0; extra == "test"
38
41
  Requires-Dist: pytest>=7.4; extra == "test"
39
42
  Requires-Dist: pytest-cov>=5.0; extra == "test"
40
43
  Requires-Dist: ruff>=0.12; extra == "test"
44
+ Requires-Dist: latex2mathml>=3.77; extra == "test"
41
45
  Provides-Extra: typecheck
42
46
  Requires-Dist: mypy>=1.10; extra == "typecheck"
43
47
  Requires-Dist: pyright>=1.1.390; extra == "typecheck"
@@ -53,6 +57,8 @@ Dynamic: license-file
53
57
  <a href="https://pypi.org/project/python-hwpx/"><img src="https://img.shields.io/pypi/pyversions/python-hwpx" alt="Python"></a>
54
58
  <a href="https://github.com/airmang/python-hwpx/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License"></a>
55
59
  <a href="https://airmang.github.io/python-hwpx/"><img src="https://img.shields.io/badge/docs-Sphinx-8CA1AF" alt="Docs"></a>
60
+ <a href="https://github.com/airmang/python-hwpx/actions/workflows/tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/airmang/python-hwpx/tests.yml?branch=main&label=tests" alt="Tests"></a>
61
+ <a href="https://airmang.github.io/python-hwpx/corpus-metrics.html"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fairmang.github.io%2Fpython-hwpx%2F_static%2Fbadge-hancom-open.json" alt="Hancom open"></a>
56
62
  </p>
57
63
  </p>
58
64
 
@@ -83,11 +89,10 @@ Dynamic: license-file
83
89
  | 🔌 MCP 서버 | [`hwpx-mcp-server`](https://github.com/airmang/hwpx-mcp-server) | MCP 클라이언트(Claude Desktop, VS Code 등)에서 HWPX 조작 |
84
90
  | 🎯 에이전트 스킬 | [`hwpx-plugin`](https://github.com/airmang/hwpx-plugins) | 에이전트가 HWPX를 바로 쓰게 해주는 first-party 플러그인·스킬 번들 |
85
91
 
86
- `python-hwpx`는 HWPX 파싱·편집·생성을 제공하는 코어 라이브러리이며,
87
- `hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동 구성요소입니다.
88
- “first-party”는 프로젝트 유지보수 관계를 뜻하며, 한컴 또는 제3자의 공식 인증을 뜻하지 않습니다.
92
+ `hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동
93
+ 구성요소입니다(유지보수 관계를 뜻하며, 한컴 외부 기관의 인증을 뜻하지 않습니다).
89
94
 
90
- 현재 PyPI 공개 릴리스는 `python-hwpx 3.7.0`입니다. 일반
95
+ 현재 PyPI 공개 릴리스는 `python-hwpx 3.8.0`입니다. 일반
91
96
  `pip install python-hwpx`로 이 릴리스를 설치할 수 있습니다.
92
97
  현재 패키지 분류는 `Development Status :: 3 - Alpha`입니다. 이 분류는 API와 제품의
93
98
  성숙도를 나타내며, 공개 버전이나 플러그인의 최소 호환 버전을 대신하지 않습니다.
@@ -109,6 +114,11 @@ Dynamic: license-file
109
114
  export는 한컴 자체가 거부 — 실측 한계로 발행) + 미검증 17건
110
115
  - wild 공개 양식 채움은 구조결함 픽스 후 **무음 서식파괴 16.7%**(판정 66조합, 못 담는 타깃은 typed 거부 35건·산출분 pass 17/28) — **낮은 숫자도 그대로 발행**하고 잔여(페이지 리플·표 shape)를 명명합니다
111
116
 
117
+ <p align="center">
118
+ <img src="https://raw.githubusercontent.com/airmang/python-hwpx/main/docs/images/redline-hancom.png" alt="python-hwpx가 남긴 변경추적과 AI 에이전트 메모가 실제 한/글에서 열린 화면" width="720">
119
+ </p>
120
+ <p align="center"><sub>python-hwpx로 작성한 변경추적(취소선·삽입)과 AI 에이전트 메모 — 실제 한/글에서 연 화면입니다.</sub></p>
121
+
112
122
  > 이 숫자들은 *생성물 수용률* 축입니다(우리가 만든 파일을 실제 한컴이 받아들이는가).
113
123
  > 문서 *파싱 recall*과는 다른 축이므로 파서 프로젝트 수치와 병치 비교하지 마세요.
114
124
 
@@ -301,6 +311,11 @@ Unsupported-but-preserved / Unsupported-and-rejected**.
301
311
 
302
312
  버그 리포트, 기능 제안, PR 모두 환영합니다.
303
313
 
314
+ - **[help wanted 이슈](https://github.com/airmang/python-hwpx/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)** — 지금 들어오기 좋은 입구입니다. 시작 전에 이슈에 코멘트로 방향을 남겨주세요.
315
+ - **[마일스톤](https://github.com/airmang/python-hwpx/milestones)** — 공개 로드맵입니다. 프로젝트가 어디로 가는지 여기서 보입니다.
316
+ - **[Discussions](https://github.com/airmang/python-hwpx/discussions)** — 질문·아이디어는 이슈 대신 여기로.
317
+ - **[내부 실전 가이드](docs/internals/)** — HWPX 내부 구조가 처음이라면 여기부터. 개발 흐름은 [CONTRIBUTING.md](CONTRIBUTING.md)에 있습니다.
318
+
304
319
  ```bash
305
320
  git clone https://github.com/airmang/python-hwpx.git
306
321
  cd python-hwpx
@@ -8,6 +8,8 @@
8
8
  <a href="https://pypi.org/project/python-hwpx/"><img src="https://img.shields.io/pypi/pyversions/python-hwpx" alt="Python"></a>
9
9
  <a href="https://github.com/airmang/python-hwpx/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License"></a>
10
10
  <a href="https://airmang.github.io/python-hwpx/"><img src="https://img.shields.io/badge/docs-Sphinx-8CA1AF" alt="Docs"></a>
11
+ <a href="https://github.com/airmang/python-hwpx/actions/workflows/tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/airmang/python-hwpx/tests.yml?branch=main&label=tests" alt="Tests"></a>
12
+ <a href="https://airmang.github.io/python-hwpx/corpus-metrics.html"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fairmang.github.io%2Fpython-hwpx%2F_static%2Fbadge-hancom-open.json" alt="Hancom open"></a>
11
13
  </p>
12
14
  </p>
13
15
 
@@ -38,11 +40,10 @@
38
40
  | 🔌 MCP 서버 | [`hwpx-mcp-server`](https://github.com/airmang/hwpx-mcp-server) | MCP 클라이언트(Claude Desktop, VS Code 등)에서 HWPX 조작 |
39
41
  | 🎯 에이전트 스킬 | [`hwpx-plugin`](https://github.com/airmang/hwpx-plugins) | 에이전트가 HWPX를 바로 쓰게 해주는 first-party 플러그인·스킬 번들 |
40
42
 
41
- `python-hwpx`는 HWPX 파싱·편집·생성을 제공하는 코어 라이브러리이며,
42
- `hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동 구성요소입니다.
43
- “first-party”는 프로젝트 유지보수 관계를 뜻하며, 한컴 또는 제3자의 공식 인증을 뜻하지 않습니다.
43
+ `hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동
44
+ 구성요소입니다(유지보수 관계를 뜻하며, 한컴 외부 기관의 인증을 뜻하지 않습니다).
44
45
 
45
- 현재 PyPI 공개 릴리스는 `python-hwpx 3.7.0`입니다. 일반
46
+ 현재 PyPI 공개 릴리스는 `python-hwpx 3.8.0`입니다. 일반
46
47
  `pip install python-hwpx`로 이 릴리스를 설치할 수 있습니다.
47
48
  현재 패키지 분류는 `Development Status :: 3 - Alpha`입니다. 이 분류는 API와 제품의
48
49
  성숙도를 나타내며, 공개 버전이나 플러그인의 최소 호환 버전을 대신하지 않습니다.
@@ -64,6 +65,11 @@
64
65
  export는 한컴 자체가 거부 — 실측 한계로 발행) + 미검증 17건
65
66
  - wild 공개 양식 채움은 구조결함 픽스 후 **무음 서식파괴 16.7%**(판정 66조합, 못 담는 타깃은 typed 거부 35건·산출분 pass 17/28) — **낮은 숫자도 그대로 발행**하고 잔여(페이지 리플·표 shape)를 명명합니다
66
67
 
68
+ <p align="center">
69
+ <img src="https://raw.githubusercontent.com/airmang/python-hwpx/main/docs/images/redline-hancom.png" alt="python-hwpx가 남긴 변경추적과 AI 에이전트 메모가 실제 한/글에서 열린 화면" width="720">
70
+ </p>
71
+ <p align="center"><sub>python-hwpx로 작성한 변경추적(취소선·삽입)과 AI 에이전트 메모 — 실제 한/글에서 연 화면입니다.</sub></p>
72
+
67
73
  > 이 숫자들은 *생성물 수용률* 축입니다(우리가 만든 파일을 실제 한컴이 받아들이는가).
68
74
  > 문서 *파싱 recall*과는 다른 축이므로 파서 프로젝트 수치와 병치 비교하지 마세요.
69
75
 
@@ -256,6 +262,11 @@ Unsupported-but-preserved / Unsupported-and-rejected**.
256
262
 
257
263
  버그 리포트, 기능 제안, PR 모두 환영합니다.
258
264
 
265
+ - **[help wanted 이슈](https://github.com/airmang/python-hwpx/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)** — 지금 들어오기 좋은 입구입니다. 시작 전에 이슈에 코멘트로 방향을 남겨주세요.
266
+ - **[마일스톤](https://github.com/airmang/python-hwpx/milestones)** — 공개 로드맵입니다. 프로젝트가 어디로 가는지 여기서 보입니다.
267
+ - **[Discussions](https://github.com/airmang/python-hwpx/discussions)** — 질문·아이디어는 이슈 대신 여기로.
268
+ - **[내부 실전 가이드](docs/internals/)** — HWPX 내부 구조가 처음이라면 여기부터. 개발 흐름은 [CONTRIBUTING.md](CONTRIBUTING.md)에 있습니다.
269
+
259
270
  ```bash
260
271
  git clone https://github.com/airmang/python-hwpx.git
261
272
  cd python-hwpx
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-hwpx"
7
- version = "3.7.0"
7
+ version = "3.8.0"
8
8
  description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = "Apache-2.0"
@@ -38,10 +38,16 @@ visual = [
38
38
  xlsx = [
39
39
  "openpyxl>=3.1",
40
40
  ]
41
+ # Document preview viewer equation rendering: LaTeX -> MathML. Optional; without
42
+ # it the preview fails closed to a LaTeX code block instead of dropping equations.
43
+ preview = [
44
+ "latex2mathml>=3.77",
45
+ ]
41
46
  dev = [
42
47
  "build>=1.0",
43
48
  "twine>=4.0",
44
49
  "pytest>=7.4",
50
+ "latex2mathml>=3.77",
45
51
  ]
46
52
  test = [
47
53
  "build>=1.0",
@@ -50,6 +56,7 @@ test = [
50
56
  "pytest>=7.4",
51
57
  "pytest-cov>=5.0",
52
58
  "ruff>=0.12",
59
+ "latex2mathml>=3.77",
53
60
  ]
54
61
  typecheck = [
55
62
  "mypy>=1.10",
@@ -110,6 +117,12 @@ follow_imports = "skip"
110
117
  files = [
111
118
  "src/hwpx/document.py",
112
119
  "src/hwpx/mutation_report.py",
120
+ "src/hwpx/equation/__init__.py",
121
+ "src/hwpx/equation/tokens.py",
122
+ "src/hwpx/equation/eqedit.py",
123
+ "src/hwpx/equation/mathml.py",
124
+ "src/hwpx/equation/render.py",
125
+ "src/hwpx/tools/document_viewer.py",
113
126
  "src/hwpx/_document/_units.py",
114
127
  "src/hwpx/_document/fields.py",
115
128
  "src/hwpx/_document/layout.py",
@@ -158,6 +171,12 @@ ignore_missing_imports = true
158
171
  include = [
159
172
  "src/hwpx/document.py",
160
173
  "src/hwpx/mutation_report.py",
174
+ "src/hwpx/equation/__init__.py",
175
+ "src/hwpx/equation/tokens.py",
176
+ "src/hwpx/equation/eqedit.py",
177
+ "src/hwpx/equation/mathml.py",
178
+ "src/hwpx/equation/render.py",
179
+ "src/hwpx/tools/document_viewer.py",
161
180
  "src/hwpx/_document/_units.py",
162
181
  "src/hwpx/_document/fields.py",
163
182
  "src/hwpx/_document/layout.py",
@@ -75,6 +75,10 @@ from .tools.layout_preview import (
75
75
  PreviewPage,
76
76
  render_layout_preview,
77
77
  )
78
+ from .tools.document_viewer import (
79
+ DocumentViewer,
80
+ render_document_viewer,
81
+ )
78
82
  from .ingest import (
79
83
  ConversionAttempt,
80
84
  DocumentConverter,
@@ -206,6 +210,8 @@ __all__ = [
206
210
  "validate_package",
207
211
  "paragraph_patch",
208
212
  "render_layout_preview",
213
+ "render_document_viewer",
214
+ "DocumentViewer",
209
215
  "register_template",
210
216
  "table_compute",
211
217
  ]
@@ -0,0 +1,383 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Verification/orchestration primitives for :func:`hwpx.agent.commands.apply_document_commands`.
3
+
4
+ Split out of ``commands.py`` (S-088 P3, agent/commands.py:apply_document_commands
5
+ complexity decomposition) to keep that module under its enforced line-count
6
+ ratchet. Everything here is self-contained -- it depends only on
7
+ ``hwpx.agent.model``/``hwpx.agent.document`` and absolute ``hwpx`` imports, never
8
+ on ``commands.py`` itself, so ``commands.py`` can import from this module
9
+ without creating an import cycle.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import hashlib
15
+ import json
16
+ from collections.abc import Callable, Mapping, MutableMapping
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ from hwpx.document import HwpxDocument
21
+ from hwpx.mutation_report import member_diff_bytes
22
+ from hwpx.quality import QualityPolicy, SavePipeline
23
+ from hwpx.tools.package_validator import validate_editor_open_safety
24
+
25
+ from .document import HwpxAgentDocument
26
+ from .model import AgentBatchResult, AgentContractError, AgentError
27
+
28
+ _EMPTY_REVISION = "sha256:" + hashlib.sha256(b"").hexdigest()
29
+
30
+ IdempotencyStore = MutableMapping[str, Any]
31
+ FaultInjector = Callable[[str, int | None], None]
32
+ DomainVerifier = Callable[[bytes, Mapping[str, Any]], Mapping[str, Any]]
33
+
34
+
35
+ def _revision(data: bytes) -> str:
36
+ return "sha256:" + hashlib.sha256(data).hexdigest()
37
+
38
+
39
+ def _error_from_exception(exc: BaseException, *, target: str | None = None) -> AgentError:
40
+ if isinstance(exc, AgentContractError):
41
+ code = exc.code
42
+ message = str(exc)
43
+ target = exc.target or target
44
+ elif isinstance(exc, (KeyError, IndexError, TypeError, ValueError)):
45
+ code = "invariant_violation"
46
+ message = str(exc) or type(exc).__name__
47
+ else:
48
+ code = "verification_failed"
49
+ message = f"{type(exc).__name__}: {exc}"
50
+ recoverability = "retryable" if code in {"stale_revision", "idempotency_conflict"} else "terminal"
51
+ if code in {"ambiguous_target", "volatile_target", "unsupported_content"}:
52
+ recoverability = "needs-review"
53
+ suggestions = {
54
+ "stale_revision": "Read the document again and retry with its current revision.",
55
+ "ambiguous_target": "Resolve a unique canonical path before mutation.",
56
+ "volatile_target": "Refresh the positional path from the current document revision.",
57
+ "unknown_property": "Use the node capability catalog to choose an editable property.",
58
+ "incompatible_parent": "Choose a compatible semantic parent and position.",
59
+ "verification_failed": "Inspect verificationReport and do not publish the candidate.",
60
+ }
61
+ return AgentError(
62
+ code=code,
63
+ message=message[:4096],
64
+ target=target,
65
+ recoverability=recoverability,
66
+ suggestion=suggestions.get(code),
67
+ )
68
+
69
+
70
+ def _failure_result(
71
+ *,
72
+ exc: BaseException,
73
+ batch: Mapping[str, Any] | None,
74
+ input_revision: str = _EMPTY_REVISION,
75
+ command_results: list[Mapping[str, Any]] | None = None,
76
+ verification: Mapping[str, Any] | None = None,
77
+ ) -> AgentBatchResult:
78
+ raw = batch or {}
79
+ output = raw.get("output") if isinstance(raw, Mapping) else None
80
+ output_filename = ""
81
+ if isinstance(output, Mapping):
82
+ output_filename = str(output.get("filename") or "")
83
+ return AgentBatchResult(
84
+ ok=False,
85
+ rolled_back=True,
86
+ dry_run=bool(raw.get("dryRun", False)),
87
+ input_revision=input_revision,
88
+ document_revision=input_revision,
89
+ output_filename=output_filename,
90
+ command_results=tuple(command_results or ()),
91
+ verification_report=dict(verification or {}),
92
+ error=_error_from_exception(exc),
93
+ )
94
+
95
+
96
+ def _quality_policy(value: str | Mapping[str, Any] | None) -> QualityPolicy:
97
+ if value is None or value == "transparent":
98
+ return QualityPolicy.transparent()
99
+ if value == "strict":
100
+ return QualityPolicy.strict()
101
+ if not isinstance(value, Mapping): # already contract-validated
102
+ raise AgentContractError("invalid_syntax", "quality is invalid", target="batch.quality")
103
+ mode = value.get("mode", "transparent")
104
+ policy = QualityPolicy.strict() if mode == "strict" else QualityPolicy.transparent()
105
+ mapping = {
106
+ "renderCheck": "render_check",
107
+ "xsdMode": "xsd_mode",
108
+ "overflowPolicy": "overflow_policy",
109
+ "layoutLint": "layout_lint",
110
+ "preserveUnmodifiedParts": "preserve_unmodified_parts",
111
+ "requireReferenceIntegrity": "require_reference_integrity",
112
+ }
113
+ changes = {mapping[name]: setting for name, setting in value.items() if name in mapping}
114
+ return policy.with_(**changes)
115
+
116
+
117
+ def _member_diff(before: bytes, after: bytes) -> dict[str, Any]:
118
+ # Shared with the Safe Write Contract's MutationReport spine: one uncompressed
119
+ # member comparison, one home for the diff shape (mutation_report.py).
120
+ return member_diff_bytes(before, after)
121
+
122
+
123
+ def _verify_header_story_candidates(
124
+ candidate_data: bytes,
125
+ candidate_revision: str,
126
+ expectations: Mapping[str, Mapping[str, str]],
127
+ ) -> dict[str, Any]:
128
+ receipts: list[dict[str, Any]] = []
129
+ if expectations:
130
+ with HwpxDocument.open(candidate_data) as reopened:
131
+ view = HwpxAgentDocument.from_document(
132
+ reopened, revision=candidate_revision
133
+ )
134
+ for expectation in expectations.values():
135
+ path = expectation["path"]
136
+ binding = view._resolve_header_story(path)
137
+ if (
138
+ binding.stable_id != expectation["stableId"]
139
+ or binding.page_type != expectation["pageType"]
140
+ or binding.text != expectation["text"]
141
+ ):
142
+ raise AgentContractError(
143
+ "verification_failed",
144
+ "reopened header story does not match the committed binding",
145
+ target=path,
146
+ )
147
+ receipts.append(
148
+ {
149
+ "commandId": expectation["commandId"],
150
+ "path": binding.path,
151
+ "stableId": binding.stable_id,
152
+ "pageType": binding.page_type,
153
+ "textMatched": True,
154
+ }
155
+ )
156
+ return {
157
+ "schemaVersion": "hwpx.agent-story-preservation/v1",
158
+ "ok": True,
159
+ "storyCount": len(receipts),
160
+ "stories": receipts,
161
+ }
162
+
163
+
164
+ def _request_hash(batch: Mapping[str, Any]) -> str:
165
+ payload = json.dumps(
166
+ batch,
167
+ ensure_ascii=False,
168
+ sort_keys=True,
169
+ separators=(",", ":"),
170
+ ).encode("utf-8")
171
+ return _revision(payload)
172
+
173
+
174
+ def _call_fault(injector: FaultInjector | None, stage: str, index: int | None = None) -> None:
175
+ if injector is not None:
176
+ injector(stage, index)
177
+
178
+
179
+ def _apply_commands_idempotency_lookup(
180
+ verification: dict[str, Any],
181
+ idempotency_store: IdempotencyStore | None,
182
+ *,
183
+ key: str | None,
184
+ request_hash: str,
185
+ ) -> AgentBatchResult | None:
186
+ verification["idempotency"] = {
187
+ "keyProvided": key is not None,
188
+ "requestHash": request_hash,
189
+ "replayed": False,
190
+ "store": "caller-owned" if idempotency_store is not None else "none",
191
+ }
192
+ if key is not None and idempotency_store is not None and key in idempotency_store:
193
+ cached = idempotency_store[key]
194
+ if not isinstance(cached, Mapping) or cached.get("requestHash") != request_hash:
195
+ raise AgentContractError(
196
+ "idempotency_conflict",
197
+ "idempotency key was used for a different request",
198
+ target="batch.idempotencyKey",
199
+ )
200
+ prior = cached.get("result")
201
+ if not isinstance(prior, AgentBatchResult):
202
+ raise AgentContractError(
203
+ "invariant_violation", "idempotency store contains an invalid result", target="batch.idempotencyKey"
204
+ )
205
+ replay_report = dict(prior.verification_report)
206
+ replay_idempotency = dict(replay_report.get("idempotency", {}))
207
+ replay_idempotency["replayed"] = True
208
+ replay_report["idempotency"] = replay_idempotency
209
+ return AgentBatchResult(
210
+ ok=prior.ok,
211
+ rolled_back=prior.rolled_back,
212
+ dry_run=prior.dry_run,
213
+ input_revision=prior.input_revision,
214
+ document_revision=prior.document_revision,
215
+ output_filename=prior.output_filename,
216
+ command_results=prior.command_results,
217
+ semantic_diff=prior.semantic_diff,
218
+ verification_report=replay_report,
219
+ error=prior.error,
220
+ )
221
+ return None
222
+
223
+
224
+ def _validate_apply_commands_input(
225
+ normalized: Mapping[str, Any],
226
+ verification: dict[str, Any],
227
+ input_data: bytes,
228
+ input_revision: str,
229
+ output_path: Path,
230
+ ) -> None:
231
+ verification["revision"] = {
232
+ "expected": normalized["expectedRevision"],
233
+ "actual": input_revision,
234
+ "matched": normalized["expectedRevision"] in {None, input_revision},
235
+ }
236
+ if normalized["expectedRevision"] not in {None, input_revision}:
237
+ raise AgentContractError(
238
+ "stale_revision", "expectedRevision does not match input bytes", target="batch.expectedRevision"
239
+ )
240
+ input_safety = validate_editor_open_safety(input_data)
241
+ verification["inputOpenSafety"] = input_safety.to_dict()
242
+ if not input_safety.ok:
243
+ raise AgentContractError(
244
+ "verification_failed",
245
+ "input failed package/reopen/openSafety verification",
246
+ target="batch.input.filename",
247
+ )
248
+ if output_path.exists() and not normalized["output"]["overwrite"] and not normalized["dryRun"]:
249
+ raise AgentContractError(
250
+ "invariant_violation", "output exists and overwrite is false", target="batch.output.filename"
251
+ )
252
+
253
+
254
+ def _apply_commands_build_candidate_report(
255
+ input_data: bytes,
256
+ candidate_data: bytes,
257
+ input_revision: str,
258
+ semantic_changes: list[Mapping[str, Any]],
259
+ identity_changes: list[Mapping[str, str]],
260
+ story_expectations: Mapping[str, Mapping[str, str]],
261
+ verification: dict[str, Any],
262
+ ) -> tuple[str, dict[str, Any], Any, dict[str, Any]]:
263
+ """Compute the candidate revision/semantic diff and write byte/package safety
264
+ into ``verification``.
265
+
266
+ Does not raise on unsafe candidates -- the original ordering runs domain
267
+ verification before that check, so callers must still inspect the
268
+ returned ``safety``/``byte_report``.
269
+ """
270
+
271
+ candidate_revision = _revision(candidate_data)
272
+ if story_expectations:
273
+ verification["storyPreservation"] = _verify_header_story_candidates(
274
+ candidate_data,
275
+ candidate_revision,
276
+ story_expectations,
277
+ )
278
+ semantic_diff = {
279
+ "schemaVersion": "hwpx.agent-semantic-diff/v1",
280
+ "inputRevision": input_revision,
281
+ "candidateRevision": candidate_revision,
282
+ "changes": semantic_changes,
283
+ "identityMap": identity_changes,
284
+ }
285
+ byte_report = _member_diff(input_data, candidate_data)
286
+ safety = validate_editor_open_safety(candidate_data)
287
+ safety_dict = safety.to_dict()
288
+ verification.update(
289
+ {
290
+ "candidateRevision": candidate_revision,
291
+ "package": safety_dict["validatePackage"],
292
+ "reopen": safety_dict["reopen"],
293
+ "openSafety": safety_dict,
294
+ "semanticDiff": {"ok": True, "changeCount": len(semantic_changes)},
295
+ "bytePreservation": byte_report,
296
+ }
297
+ )
298
+ return candidate_revision, semantic_diff, safety, byte_report
299
+
300
+
301
+ def _apply_commands_domain_verification(
302
+ candidate_data: bytes,
303
+ normalized: Mapping[str, Any],
304
+ requirements: set[str],
305
+ domain_verifier: DomainVerifier | None,
306
+ verification: dict[str, Any],
307
+ ) -> None:
308
+ if "domain" in requirements:
309
+ if domain_verifier is None:
310
+ verification["domain"] = {"ok": False, "error": "required verifier unavailable"}
311
+ raise AgentContractError(
312
+ "verification_failed", "required domain verifier is unavailable", target="domain"
313
+ )
314
+ domain_report = dict(domain_verifier(candidate_data, normalized))
315
+ try:
316
+ json.dumps(domain_report, ensure_ascii=False, sort_keys=True)
317
+ except (TypeError, ValueError) as exc:
318
+ raise AgentContractError(
319
+ "verification_failed",
320
+ "domain verifier returned a non-JSON report",
321
+ target="domain",
322
+ ) from exc
323
+ verification["domain"] = domain_report
324
+ if not domain_report.get("ok"):
325
+ raise AgentContractError("verification_failed", "domain verification failed", target="domain")
326
+ else:
327
+ verification["domain"] = {"ok": None, "status": "not-requested"}
328
+
329
+
330
+ def _require_candidate_structural_safety(safety: Any, byte_report: Mapping[str, Any]) -> None:
331
+ if not safety.ok or not byte_report.get("ok"):
332
+ raise AgentContractError(
333
+ "verification_failed", "candidate failed package/reopen/openSafety verification"
334
+ )
335
+
336
+
337
+ def _apply_commands_run_save_pipeline(
338
+ candidate_data: bytes,
339
+ input_path: Path,
340
+ output_path: Path,
341
+ document: HwpxDocument,
342
+ normalized: Mapping[str, Any],
343
+ requirements: set[str],
344
+ save_pipeline: SavePipeline | None,
345
+ verification: dict[str, Any],
346
+ ) -> Any:
347
+ pipeline = save_pipeline or SavePipeline()
348
+ quality_policy = _quality_policy(normalized["quality"])
349
+ if "realHancom" in requirements:
350
+ # Make the required oracle part of the atomic gate itself. A
351
+ # post-publication provenance check would be too late to roll
352
+ # back an otherwise structurally valid file.
353
+ quality_policy = quality_policy.with_(
354
+ require_visual_complete=True,
355
+ render_check="required",
356
+ )
357
+ quality_report = pipeline.run(
358
+ candidate_data,
359
+ output_path=None if normalized["dryRun"] else output_path,
360
+ quality=quality_policy,
361
+ before=input_path,
362
+ reference_document=document,
363
+ publish="never" if normalized["dryRun"] else "on_pass",
364
+ source_label="agent.apply_document_commands",
365
+ )
366
+ verification["savePipeline"] = quality_report.to_dict()
367
+ verification["realHancom"] = {
368
+ "required": "realHancom" in requirements,
369
+ "ok": quality_report.visual_complete,
370
+ "status": quality_report.visual_complete_status,
371
+ "renderChecked": quality_report.render_checked,
372
+ }
373
+ if "realHancom" in requirements and not quality_report.visual_complete:
374
+ raise AgentContractError(
375
+ "verification_failed",
376
+ "required real-Hancom visual verification is unavailable or failed",
377
+ target="realHancom",
378
+ )
379
+ if not quality_report.ok:
380
+ raise AgentContractError(
381
+ "verification_failed", "SavePipeline rejected the candidate", target="savePipeline"
382
+ )
383
+ return quality_report