python-hwpx 2.24.1__py3-none-any.whl → 2.29.1__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 (49) hide show
  1. hwpx/agent/__init__.py +99 -0
  2. hwpx/agent/blueprint/__init__.py +58 -0
  3. hwpx/agent/blueprint/bundle.py +282 -0
  4. hwpx/agent/blueprint/catalog.py +136 -0
  5. hwpx/agent/blueprint/dump.py +521 -0
  6. hwpx/agent/blueprint/mapping.py +312 -0
  7. hwpx/agent/blueprint/model.py +626 -0
  8. hwpx/agent/blueprint/native.py +622 -0
  9. hwpx/agent/blueprint/replay.py +452 -0
  10. hwpx/agent/catalog.py +167 -0
  11. hwpx/agent/cli.py +631 -0
  12. hwpx/agent/commands.py +1401 -0
  13. hwpx/agent/document.py +769 -0
  14. hwpx/agent/model.py +730 -0
  15. hwpx/agent/path.py +155 -0
  16. hwpx/agent/query.py +230 -0
  17. hwpx/benchmark/__init__.py +25 -0
  18. hwpx/benchmark/blind_eval.py +261 -0
  19. hwpx/document.py +17 -1
  20. hwpx/oxml/_document_impl.py +16 -14
  21. hwpx/practice/__init__.py +277 -0
  22. hwpx/practice/aggregate.py +947 -0
  23. hwpx/practice/campaign.py +296 -0
  24. hwpx/practice/domain.py +3137 -0
  25. hwpx/practice/dossier.py +78 -0
  26. hwpx/practice/evaluator.py +2359 -0
  27. hwpx/practice/forge.py +436 -0
  28. hwpx/practice/intake.py +230 -0
  29. hwpx/practice/lineage.py +113 -0
  30. hwpx/practice/mutations.py +168 -0
  31. hwpx/practice/registry.py +322 -0
  32. hwpx/practice/run.py +757 -0
  33. hwpx/practice/sanitize.py +154 -0
  34. hwpx/practice/scenario.py +146 -0
  35. hwpx/practice/split.py +249 -0
  36. hwpx/visual/__init__.py +38 -1
  37. hwpx/visual/_render_hwpx_mac.applescript +24 -1
  38. hwpx/visual/fixture_corpus.py +175 -0
  39. hwpx/visual/hancom_worker.py +288 -0
  40. hwpx/visual/page_qa.py +244 -0
  41. hwpx/visual/qa_contracts.py +293 -0
  42. hwpx/visual/qa_metrics.py +236 -0
  43. {python_hwpx-2.24.1.dist-info → python_hwpx-2.29.1.dist-info}/METADATA +3 -1
  44. {python_hwpx-2.24.1.dist-info → python_hwpx-2.29.1.dist-info}/RECORD +49 -11
  45. {python_hwpx-2.24.1.dist-info → python_hwpx-2.29.1.dist-info}/entry_points.txt +1 -0
  46. {python_hwpx-2.24.1.dist-info → python_hwpx-2.29.1.dist-info}/WHEEL +0 -0
  47. {python_hwpx-2.24.1.dist-info → python_hwpx-2.29.1.dist-info}/licenses/LICENSE +0 -0
  48. {python_hwpx-2.24.1.dist-info → python_hwpx-2.29.1.dist-info}/licenses/NOTICE +0 -0
  49. {python_hwpx-2.24.1.dist-info → python_hwpx-2.29.1.dist-info}/top_level.txt +0 -0
hwpx/agent/__init__.py ADDED
@@ -0,0 +1,99 @@
1
+ """Versioned semantic contracts for the HWPX agent document interface."""
2
+
3
+ from .model import (
4
+ AGENT_BATCH_RESULT_SCHEMA,
5
+ AGENT_BATCH_SCHEMA,
6
+ AGENT_CATALOG_SCHEMA,
7
+ AGENT_COMMAND_SCHEMA,
8
+ AGENT_ERROR_SCHEMA,
9
+ AGENT_NODE_SCHEMA,
10
+ AgentBatchResult,
11
+ AgentContractError,
12
+ AgentError,
13
+ AgentNode,
14
+ agent_contract_manifest,
15
+ validate_agent_batch,
16
+ validate_agent_command,
17
+ )
18
+ from .catalog import agent_catalog, agent_json_schemas, catalog_hash, human_help, node_help
19
+ from .commands import apply_document_commands
20
+ from .document import HwpxAgentDocument, NodeRecord
21
+ from .path import PathSegment, SemanticPath, canonicalize_path, parse_path
22
+ from .query import QueryResult, SemanticSelector, parse_selector
23
+ from .blueprint import (
24
+ BLUEPRINT_CATALOG_SCHEMA,
25
+ BLUEPRINT_REPLAY_RESULT_SCHEMA,
26
+ BLUEPRINT_REPLAY_SCHEMA,
27
+ BLUEPRINT_SCHEMA,
28
+ BlueprintBundle,
29
+ BlueprintDumpResult,
30
+ BlueprintReplayResult,
31
+ ReplayPlan,
32
+ blueprint_catalog,
33
+ blueprint_catalog_hash,
34
+ blueprint_hash,
35
+ blueprint_human_help,
36
+ blueprint_json_schemas,
37
+ blueprint_limits,
38
+ build_blueprint_bundle,
39
+ dump_document_blueprint,
40
+ read_blueprint_bundle,
41
+ repack_blueprint_bundle,
42
+ replay_document_blueprint,
43
+ validate_blueprint_manifest,
44
+ validate_replay_request,
45
+ write_blueprint_bundle,
46
+ )
47
+
48
+ __all__ = [
49
+ "AGENT_BATCH_RESULT_SCHEMA",
50
+ "AGENT_BATCH_SCHEMA",
51
+ "AGENT_CATALOG_SCHEMA",
52
+ "AGENT_COMMAND_SCHEMA",
53
+ "AGENT_ERROR_SCHEMA",
54
+ "AGENT_NODE_SCHEMA",
55
+ "BLUEPRINT_CATALOG_SCHEMA",
56
+ "BLUEPRINT_REPLAY_RESULT_SCHEMA",
57
+ "BLUEPRINT_REPLAY_SCHEMA",
58
+ "BLUEPRINT_SCHEMA",
59
+ "AgentBatchResult",
60
+ "AgentContractError",
61
+ "AgentError",
62
+ "AgentNode",
63
+ "BlueprintReplayResult",
64
+ "ReplayPlan",
65
+ "BlueprintBundle",
66
+ "BlueprintDumpResult",
67
+ "HwpxAgentDocument",
68
+ "NodeRecord",
69
+ "PathSegment",
70
+ "QueryResult",
71
+ "SemanticPath",
72
+ "SemanticSelector",
73
+ "agent_catalog",
74
+ "agent_json_schemas",
75
+ "agent_contract_manifest",
76
+ "apply_document_commands",
77
+ "blueprint_catalog",
78
+ "blueprint_catalog_hash",
79
+ "blueprint_hash",
80
+ "blueprint_human_help",
81
+ "blueprint_json_schemas",
82
+ "blueprint_limits",
83
+ "build_blueprint_bundle",
84
+ "canonicalize_path",
85
+ "catalog_hash",
86
+ "human_help",
87
+ "node_help",
88
+ "parse_path",
89
+ "parse_selector",
90
+ "dump_document_blueprint",
91
+ "read_blueprint_bundle",
92
+ "repack_blueprint_bundle",
93
+ "replay_document_blueprint",
94
+ "validate_agent_batch",
95
+ "validate_agent_command",
96
+ "validate_blueprint_manifest",
97
+ "validate_replay_request",
98
+ "write_blueprint_bundle",
99
+ ]
@@ -0,0 +1,58 @@
1
+ """Typed semantic HWPX blueprint contracts."""
2
+
3
+ from .catalog import (
4
+ blueprint_catalog,
5
+ blueprint_catalog_hash,
6
+ blueprint_human_help,
7
+ blueprint_json_schemas,
8
+ )
9
+ from .bundle import (
10
+ BlueprintBundle,
11
+ build_blueprint_bundle,
12
+ read_blueprint_bundle,
13
+ repack_blueprint_bundle,
14
+ write_blueprint_bundle,
15
+ )
16
+ from .dump import BlueprintDumpResult, dump_document_blueprint
17
+ from .mapping import ReplayPlan
18
+ from .replay import replay_document_blueprint
19
+ from .model import (
20
+ BLUEPRINT_CATALOG_SCHEMA,
21
+ BLUEPRINT_REPLAY_RESULT_SCHEMA,
22
+ BLUEPRINT_REPLAY_SCHEMA,
23
+ BLUEPRINT_SCHEMA,
24
+ BlueprintReplayResult,
25
+ blueprint_hash,
26
+ blueprint_limits,
27
+ canonical_manifest_bytes,
28
+ validate_blueprint_manifest,
29
+ validate_replay_request,
30
+ with_blueprint_hash,
31
+ )
32
+
33
+ __all__ = [
34
+ "BLUEPRINT_CATALOG_SCHEMA",
35
+ "BLUEPRINT_REPLAY_RESULT_SCHEMA",
36
+ "BLUEPRINT_REPLAY_SCHEMA",
37
+ "BLUEPRINT_SCHEMA",
38
+ "BlueprintBundle",
39
+ "BlueprintDumpResult",
40
+ "BlueprintReplayResult",
41
+ "ReplayPlan",
42
+ "blueprint_catalog",
43
+ "blueprint_catalog_hash",
44
+ "blueprint_hash",
45
+ "blueprint_human_help",
46
+ "blueprint_json_schemas",
47
+ "blueprint_limits",
48
+ "build_blueprint_bundle",
49
+ "canonical_manifest_bytes",
50
+ "dump_document_blueprint",
51
+ "read_blueprint_bundle",
52
+ "repack_blueprint_bundle",
53
+ "replay_document_blueprint",
54
+ "validate_blueprint_manifest",
55
+ "validate_replay_request",
56
+ "with_blueprint_hash",
57
+ "write_blueprint_bundle",
58
+ ]
@@ -0,0 +1,282 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Deterministic and fail-closed ``.hwpxbp`` bundle I/O."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import hashlib
7
+ import io
8
+ import json
9
+ import os
10
+ import stat
11
+ import tempfile
12
+ import zipfile
13
+ from collections.abc import Mapping
14
+ from dataclasses import dataclass
15
+ from pathlib import Path, PurePosixPath
16
+ from typing import Any
17
+
18
+ from ..model import AgentContractError
19
+ from .model import (
20
+ ASSET_PATH_PATTERN,
21
+ MAX_ASSETS,
22
+ MAX_ASSET_BYTES,
23
+ MAX_MANIFEST_BYTES,
24
+ MAX_TOTAL_ASSET_BYTES,
25
+ canonical_manifest_bytes,
26
+ validate_blueprint_manifest,
27
+ with_blueprint_hash,
28
+ )
29
+
30
+ MANIFEST_PATH = "blueprint.json"
31
+ MAX_BUNDLE_BYTES = MAX_MANIFEST_BYTES + MAX_TOTAL_ASSET_BYTES + 2 * 1024 * 1024
32
+ MAX_COMPRESSION_RATIO = 100
33
+
34
+ ALLOWED_MEDIA_TYPES: dict[str, tuple[str, ...]] = {
35
+ "image/png": ("png",),
36
+ "image/jpeg": ("jpg", "jpeg"),
37
+ "image/gif": ("gif",),
38
+ "image/bmp": ("bmp",),
39
+ "image/tiff": ("tif", "tiff"),
40
+ "image/webp": ("webp",),
41
+ }
42
+
43
+
44
+ @dataclass(frozen=True, slots=True)
45
+ class BlueprintBundle:
46
+ """A fully validated manifest and detached content-addressed assets."""
47
+
48
+ manifest: Mapping[str, Any]
49
+ assets: Mapping[str, bytes]
50
+ bundle_sha256: str
51
+ size: int
52
+
53
+
54
+ def _error(code: str, message: str, *, target: str = "bundle") -> AgentContractError:
55
+ return AgentContractError(code, message, target=target)
56
+
57
+
58
+ def _json_without_duplicate_keys(data: bytes) -> dict[str, Any]:
59
+ def pairs_hook(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
60
+ result: dict[str, Any] = {}
61
+ for key, value in pairs:
62
+ if key in result:
63
+ raise _error("invariant_violation", f"duplicate JSON field: {key}", target=MANIFEST_PATH)
64
+ result[key] = value
65
+ return result
66
+
67
+ try:
68
+ value = json.loads(data.decode("utf-8"), object_pairs_hook=pairs_hook)
69
+ except AgentContractError:
70
+ raise
71
+ except (UnicodeDecodeError, json.JSONDecodeError) as exc:
72
+ raise _error("invalid_syntax", "blueprint.json is not strict UTF-8 JSON", target=MANIFEST_PATH) from exc
73
+ if not isinstance(value, dict):
74
+ raise _error("invalid_syntax", "blueprint.json must contain one object", target=MANIFEST_PATH)
75
+ return value
76
+
77
+
78
+ def _validate_entry_name(name: str) -> None:
79
+ if not name or "\\" in name or "\x00" in name:
80
+ raise _error("verification_failed", "bundle entry path is not normalized", target=name or "bundle")
81
+ path = PurePosixPath(name)
82
+ if path.is_absolute() or name.startswith("/") or any(part in {"", ".", ".."} for part in path.parts):
83
+ raise _error("verification_failed", "bundle entry path is unsafe", target=name)
84
+ if name != MANIFEST_PATH and not ASSET_PATH_PATTERN.fullmatch(name):
85
+ raise _error("unsupported_content", "bundle contains an unknown or forbidden entry", target=name)
86
+
87
+
88
+ def _validate_zip_info(info: zipfile.ZipInfo) -> None:
89
+ _validate_entry_name(info.filename)
90
+ if info.is_dir():
91
+ raise _error("unsupported_content", "directory entries are forbidden", target=info.filename)
92
+ mode = (info.external_attr >> 16) & 0xFFFF
93
+ if mode and stat.S_ISLNK(mode):
94
+ raise _error("unsupported_content", "symlink entries are forbidden", target=info.filename)
95
+ if info.flag_bits & 0x1:
96
+ raise _error("unsupported_content", "encrypted bundle entries are forbidden", target=info.filename)
97
+ if info.compress_type not in {zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED}:
98
+ raise _error("unsupported_content", "bundle compression method is forbidden", target=info.filename)
99
+ limit = MAX_MANIFEST_BYTES if info.filename == MANIFEST_PATH else MAX_ASSET_BYTES
100
+ if info.file_size > limit:
101
+ raise _error("resource_limit", "bundle entry exceeds its byte limit", target=info.filename)
102
+ if info.file_size and info.compress_size == 0:
103
+ raise _error("resource_limit", "bundle entry has an invalid compression ratio", target=info.filename)
104
+ if info.compress_size and info.file_size / info.compress_size > MAX_COMPRESSION_RATIO:
105
+ raise _error("resource_limit", "bundle entry exceeds the decompression-ratio limit", target=info.filename)
106
+
107
+
108
+ def _sniff_media_type(data: bytes) -> str | None:
109
+ if data.startswith(b"\x89PNG\r\n\x1a\n"):
110
+ return "image/png"
111
+ if data.startswith(b"\xff\xd8\xff"):
112
+ return "image/jpeg"
113
+ if data.startswith((b"GIF87a", b"GIF89a")):
114
+ return "image/gif"
115
+ if data.startswith(b"BM"):
116
+ return "image/bmp"
117
+ if data.startswith((b"II*\x00", b"MM\x00*")):
118
+ return "image/tiff"
119
+ if len(data) >= 12 and data[:4] == b"RIFF" and data[8:12] == b"WEBP":
120
+ return "image/webp"
121
+ return None
122
+
123
+
124
+ def _validated_assets(
125
+ manifest: Mapping[str, Any], assets: Mapping[str, bytes]
126
+ ) -> dict[str, bytes]:
127
+ declared = {str(item["assetPath"]): dict(item) for item in manifest["resources"]}
128
+ if set(assets) != set(declared):
129
+ raise _error(
130
+ "invariant_violation",
131
+ "bundle assets do not exactly match manifest resources",
132
+ target="resources",
133
+ )
134
+ if len(assets) > MAX_ASSETS:
135
+ raise _error("resource_limit", "asset count exceeds limit", target="resources")
136
+ total = 0
137
+ detached: dict[str, bytes] = {}
138
+ for path in sorted(assets):
139
+ _validate_entry_name(path)
140
+ payload = assets[path]
141
+ if not isinstance(payload, bytes):
142
+ raise _error("invalid_syntax", "asset payload must be bytes", target=path)
143
+ total += len(payload)
144
+ if len(payload) > MAX_ASSET_BYTES or total > MAX_TOTAL_ASSET_BYTES:
145
+ raise _error("resource_limit", "asset bytes exceed limit", target=path)
146
+ record = declared[path]
147
+ digest = "sha256:" + hashlib.sha256(payload).hexdigest()
148
+ if digest != record["sha256"] or len(payload) != record["size"]:
149
+ raise _error("verification_failed", "asset hash or size mismatch", target=path)
150
+ media_type = str(record["mediaType"])
151
+ suffix = path.rsplit(".", 1)[-1]
152
+ if media_type not in ALLOWED_MEDIA_TYPES or suffix not in ALLOWED_MEDIA_TYPES[media_type]:
153
+ raise _error("unsupported_content", "asset extension/MIME is not allow-listed", target=path)
154
+ if _sniff_media_type(payload) != media_type:
155
+ raise _error("verification_failed", "asset bytes do not match declared MIME", target=path)
156
+ detached[path] = bytes(payload)
157
+ return detached
158
+
159
+
160
+ def _zip_info(name: str) -> zipfile.ZipInfo:
161
+ info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0))
162
+ info.compress_type = zipfile.ZIP_STORED
163
+ info.create_system = 0
164
+ info.external_attr = 0
165
+ info.internal_attr = 0
166
+ info.flag_bits = 0
167
+ return info
168
+
169
+
170
+ def build_blueprint_bundle(
171
+ manifest: Mapping[str, Any], assets: Mapping[str, bytes] | None = None
172
+ ) -> bytes:
173
+ """Return canonical bundle bytes for an already hashed manifest."""
174
+
175
+ checked = validate_blueprint_manifest(manifest)
176
+ checked_assets = _validated_assets(checked, assets or {})
177
+ manifest_bytes = canonical_manifest_bytes(checked, include_hash=True)
178
+ stream = io.BytesIO()
179
+ with zipfile.ZipFile(stream, "w", allowZip64=False) as archive:
180
+ archive.writestr(_zip_info(MANIFEST_PATH), manifest_bytes)
181
+ for name, payload in checked_assets.items():
182
+ archive.writestr(_zip_info(name), payload)
183
+ result = stream.getvalue()
184
+ if len(result) > MAX_BUNDLE_BYTES:
185
+ raise _error("resource_limit", "blueprint bundle exceeds total byte limit")
186
+ return result
187
+
188
+
189
+ def write_blueprint_bundle(
190
+ destination: str | os.PathLike[str],
191
+ manifest: Mapping[str, Any],
192
+ assets: Mapping[str, bytes] | None = None,
193
+ *,
194
+ overwrite: bool = False,
195
+ ) -> BlueprintBundle:
196
+ """Atomically write one deterministic bundle and return its validated view."""
197
+
198
+ path = Path(destination)
199
+ if path.suffix.casefold() != ".hwpxbp":
200
+ raise _error("invalid_syntax", "blueprint output must use .hwpxbp", target="output")
201
+ if path.exists() and not overwrite:
202
+ raise _error("identity_collision", "blueprint output already exists", target=str(path))
203
+ data = build_blueprint_bundle(manifest, assets)
204
+ path.parent.mkdir(parents=True, exist_ok=True)
205
+ descriptor, temp_name = tempfile.mkstemp(prefix=f".{path.name}.", suffix=".tmp", dir=path.parent)
206
+ try:
207
+ with os.fdopen(descriptor, "wb") as handle:
208
+ handle.write(data)
209
+ handle.flush()
210
+ os.fsync(handle.fileno())
211
+ if path.exists() and not overwrite:
212
+ raise _error("identity_collision", "blueprint output already exists", target=str(path))
213
+ os.replace(temp_name, path)
214
+ except Exception:
215
+ try:
216
+ os.unlink(temp_name)
217
+ except FileNotFoundError:
218
+ pass
219
+ raise
220
+ return read_blueprint_bundle(data)
221
+
222
+
223
+ def read_blueprint_bundle(source: str | os.PathLike[str] | bytes) -> BlueprintBundle:
224
+ """Validate a complete bundle in memory before returning any materialized data."""
225
+
226
+ data = bytes(source) if isinstance(source, bytes) else Path(source).read_bytes()
227
+ if len(data) > MAX_BUNDLE_BYTES:
228
+ raise _error("resource_limit", "blueprint bundle exceeds total byte limit")
229
+ try:
230
+ with zipfile.ZipFile(io.BytesIO(data), "r") as archive:
231
+ infos = archive.infolist()
232
+ names = [info.filename for info in infos]
233
+ if len(names) != len(set(names)):
234
+ raise _error("invariant_violation", "bundle contains duplicate entry paths")
235
+ if names.count(MANIFEST_PATH) != 1:
236
+ raise _error("invariant_violation", "bundle must contain exactly one blueprint.json")
237
+ if len(infos) > MAX_ASSETS + 1:
238
+ raise _error("resource_limit", "bundle entry count exceeds limit")
239
+ for info in infos:
240
+ _validate_zip_info(info)
241
+ if sum(info.file_size for info in infos if info.filename != MANIFEST_PATH) > MAX_TOTAL_ASSET_BYTES:
242
+ raise _error("resource_limit", "bundle assets exceed total byte limit", target="resources")
243
+ manifest_data = archive.read(MANIFEST_PATH)
244
+ manifest = validate_blueprint_manifest(_json_without_duplicate_keys(manifest_data))
245
+ asset_names = sorted(name for name in names if name != MANIFEST_PATH)
246
+ assets = {name: archive.read(name) for name in asset_names}
247
+ except AgentContractError:
248
+ raise
249
+ except (OSError, zipfile.BadZipFile, zipfile.LargeZipFile, RuntimeError) as exc:
250
+ raise _error("invalid_syntax", "invalid .hwpxbp ZIP container") from exc
251
+ checked_assets = _validated_assets(manifest, assets)
252
+ return BlueprintBundle(
253
+ manifest=manifest,
254
+ assets=checked_assets,
255
+ bundle_sha256="sha256:" + hashlib.sha256(data).hexdigest(),
256
+ size=len(data),
257
+ )
258
+
259
+
260
+ def repack_blueprint_bundle(
261
+ source: str | os.PathLike[str] | bytes,
262
+ destination: str | os.PathLike[str],
263
+ manifest: Mapping[str, Any],
264
+ *,
265
+ overwrite: bool = False,
266
+ ) -> BlueprintBundle:
267
+ """Safely repack edited typed JSON while preserving only validated assets."""
268
+
269
+ original = read_blueprint_bundle(source)
270
+ rehashed = with_blueprint_hash(manifest)
271
+ return write_blueprint_bundle(destination, rehashed, original.assets, overwrite=overwrite)
272
+
273
+
274
+ __all__ = [
275
+ "ALLOWED_MEDIA_TYPES",
276
+ "BlueprintBundle",
277
+ "MANIFEST_PATH",
278
+ "build_blueprint_bundle",
279
+ "read_blueprint_bundle",
280
+ "repack_blueprint_bundle",
281
+ "write_blueprint_bundle",
282
+ ]
@@ -0,0 +1,136 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Shared blueprint capability, limit, schema, and help catalog."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import hashlib
7
+ import json
8
+ from typing import Any
9
+
10
+ from ..model import AGENT_CATALOG_SCHEMA, NODE_KINDS
11
+ from .model import (
12
+ BLUEPRINT_CATALOG_SCHEMA,
13
+ BLUEPRINT_MODES,
14
+ BLUEPRINT_REPLAY_RESULT_SCHEMA,
15
+ BLUEPRINT_REPLAY_SCHEMA,
16
+ BLUEPRINT_SCHEMA,
17
+ FIDELITY_LEVELS,
18
+ blueprint_limits,
19
+ )
20
+
21
+
22
+ def blueprint_catalog() -> dict[str, Any]:
23
+ return {
24
+ "schemaVersion": BLUEPRINT_CATALOG_SCHEMA,
25
+ "agentCatalogVersion": AGENT_CATALOG_SCHEMA,
26
+ "schemas": {
27
+ "blueprint": BLUEPRINT_SCHEMA,
28
+ "replay": BLUEPRINT_REPLAY_SCHEMA,
29
+ "replayResult": BLUEPRINT_REPLAY_RESULT_SCHEMA,
30
+ },
31
+ "modes": list(BLUEPRINT_MODES),
32
+ "fidelity": list(FIDELITY_LEVELS),
33
+ "kinds": [kind for kind in NODE_KINDS if kind != "unsupported"],
34
+ "bundle": {
35
+ "extension": ".hwpxbp",
36
+ "manifest": "blueprint.json",
37
+ "assetPattern": "assets/<sha256>.<safe-extension>",
38
+ "allowedMediaPrefixes": ["image/"],
39
+ "forbidden": ["xml", "script", "symlink", "nested-archive", "absolute-path", "parent-path"],
40
+ },
41
+ "limits": blueprint_limits(),
42
+ "surfaces": {"cli": ["dump", "replay"], "mcpMaximumTools": 2},
43
+ }
44
+
45
+
46
+ def blueprint_catalog_hash() -> str:
47
+ encoded = json.dumps(
48
+ blueprint_catalog(), ensure_ascii=False, sort_keys=True, separators=(",", ":")
49
+ ).encode("utf-8")
50
+ return "sha256:" + hashlib.sha256(encoded).hexdigest()
51
+
52
+
53
+ def blueprint_json_schemas() -> dict[str, Any]:
54
+ catalog = blueprint_catalog()
55
+ return {
56
+ "blueprint": {
57
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
58
+ "title": "HwpxBlueprint v1",
59
+ "type": "object",
60
+ "required": [
61
+ "schemaVersion",
62
+ "catalogVersion",
63
+ "catalogHash",
64
+ "source",
65
+ "mode",
66
+ "root",
67
+ "nodes",
68
+ "styles",
69
+ "numbering",
70
+ "resources",
71
+ "references",
72
+ "unsupported",
73
+ "capabilities",
74
+ "limits",
75
+ "fidelity",
76
+ "blueprintHash",
77
+ ],
78
+ "additionalProperties": False,
79
+ "properties": {
80
+ "schemaVersion": {"const": BLUEPRINT_SCHEMA},
81
+ "catalogVersion": {"const": AGENT_CATALOG_SCHEMA},
82
+ "catalogHash": {"type": "string", "pattern": "^sha256:[a-f0-9]{64}$"},
83
+ "mode": {"enum": list(BLUEPRINT_MODES)},
84
+ "nodes": {"type": "array", "minItems": 1, "maxItems": catalog["limits"]["maxNodes"]},
85
+ "resources": {"type": "array", "maxItems": catalog["limits"]["maxAssets"]},
86
+ "blueprintHash": {"type": "string", "pattern": "^sha256:[a-f0-9]{64}$"},
87
+ },
88
+ },
89
+ "replay": {
90
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
91
+ "title": "HwpxBlueprintReplay v1",
92
+ "type": "object",
93
+ "required": [
94
+ "schemaVersion",
95
+ "bundle",
96
+ "target",
97
+ "targetParent",
98
+ "position",
99
+ "mode",
100
+ "mappingPolicy",
101
+ "expectedRevision",
102
+ "idempotencyKey",
103
+ "dryRun",
104
+ "quality",
105
+ "verificationRequirements",
106
+ ],
107
+ "additionalProperties": False,
108
+ "properties": {
109
+ "schemaVersion": {"const": BLUEPRINT_REPLAY_SCHEMA},
110
+ "mode": {"enum": list(BLUEPRINT_MODES)},
111
+ "targetParent": {"type": "string"},
112
+ "dryRun": {"type": "boolean"},
113
+ },
114
+ },
115
+ }
116
+
117
+
118
+ def blueprint_human_help() -> str:
119
+ limits = blueprint_limits()
120
+ return (
121
+ "HWPX typed blueprint v1\n"
122
+ " dump: revision-bound document/subtree -> deterministic .hwpxbp\n"
123
+ " replay: source-bound|portable -> one atomic SavePipeline commit\n"
124
+ " fidelity: exact, mapped; strict replay rejects degraded/unsupported\n"
125
+ f" limits: nodes={limits['maxNodes']}, assets={limits['maxAssets']}, "
126
+ f"manifest={limits['maxManifestBytes']} bytes\n"
127
+ " raw XML, package parts, resident sessions, watch, and OfficeCLI are unavailable\n"
128
+ )
129
+
130
+
131
+ __all__ = [
132
+ "blueprint_catalog",
133
+ "blueprint_catalog_hash",
134
+ "blueprint_human_help",
135
+ "blueprint_json_schemas",
136
+ ]