clm-kernel 0.0.0__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 (333) hide show
  1. clm/__init__.py +230 -0
  2. clm/__main__.py +25 -0
  3. clm/cross_cutting/__init__.py +50 -0
  4. clm/cross_cutting/_test_runner.py +36 -0
  5. clm/cross_cutting/async_utils.py +25 -0
  6. clm/cross_cutting/cel_builder.py +123 -0
  7. clm/cross_cutting/cel_converter.py +697 -0
  8. clm/cross_cutting/cel_engine.py +252 -0
  9. clm/cross_cutting/cel_ops.py +72 -0
  10. clm/cross_cutting/cel_ops_db.py +157 -0
  11. clm/cross_cutting/cel_ops_files.py +99 -0
  12. clm/cross_cutting/cel_ops_state.py +71 -0
  13. clm/cross_cutting/cel_ops_strings.py +64 -0
  14. clm/cross_cutting/cel_resolver.py +63 -0
  15. clm/cross_cutting/cel_slices.py +56 -0
  16. clm/cross_cutting/cel_store.py +330 -0
  17. clm/cross_cutting/clm_logger.py +140 -0
  18. clm/cross_cutting/config/__init__.py +155 -0
  19. clm/cross_cutting/config/config_constants.py +392 -0
  20. clm/cross_cutting/config/env_parameters.py +90 -0
  21. clm/cross_cutting/config/logging.py +378 -0
  22. clm/cross_cutting/config/settings.py +504 -0
  23. clm/cross_cutting/context.py +5 -0
  24. clm/cross_cutting/domain_models.py +55 -0
  25. clm/cross_cutting/domain_types.py +61 -0
  26. clm/cross_cutting/effects.py +67 -0
  27. clm/cross_cutting/errors.py +153 -0
  28. clm/cross_cutting/governance.py +331 -0
  29. clm/cross_cutting/guards.py +41 -0
  30. clm/cross_cutting/mcard.py +321 -0
  31. clm/cross_cutting/native.py +185 -0
  32. clm/cross_cutting/observability.py +289 -0
  33. clm/cross_cutting/pcard.py +434 -0
  34. clm/cross_cutting/registry.py +71 -0
  35. clm/cross_cutting/resources.py +84 -0
  36. clm/cross_cutting/secrets.py +44 -0
  37. clm/cross_cutting/subprocess_utils.py +118 -0
  38. clm/cross_cutting/telemetry.py +164 -0
  39. clm/cross_cutting/testing/__init__.py +8 -0
  40. clm/cross_cutting/testing/comparator.py +187 -0
  41. clm/cross_cutting/testing/datasets.py +87 -0
  42. clm/cross_cutting/timeutil.py +20 -0
  43. clm/cross_cutting/types/new_type_check.py +0 -0
  44. clm/cross_cutting/utils/__init__.py +1 -0
  45. clm/cross_cutting/utils/url_safety.py +50 -0
  46. clm/cross_cutting/vcard.py +560 -0
  47. clm/cross_cutting/writer.py +118 -0
  48. clm/layer0/README.md +50 -0
  49. clm/layer0/__init__.py +223 -0
  50. clm/layer0/algebra.py +60 -0
  51. clm/layer0/algebra_baldwin.py +133 -0
  52. clm/layer0/algebra_baldwin_generators.py +36 -0
  53. clm/layer0/algebra_equivalence.py +262 -0
  54. clm/layer0/algebra_ingest.py +306 -0
  55. clm/layer0/algebra_manifest_io.py +156 -0
  56. clm/layer0/assembler.py +481 -0
  57. clm/layer0/cel/__init__.py +23 -0
  58. clm/layer0/cel_eval.py +491 -0
  59. clm/layer0/codec.py +106 -0
  60. clm/layer0/context.py +122 -0
  61. clm/layer0/db.py +160 -0
  62. clm/layer0/exceptions.py +73 -0
  63. clm/layer0/fibration.py +106 -0
  64. clm/layer0/gates.py +50 -0
  65. clm/layer0/hash.py +80 -0
  66. clm/layer0/loader.py +826 -0
  67. clm/layer0/math_utils.py +163 -0
  68. clm/layer0/mcard.py +96 -0
  69. clm/layer0/model/__init__.py +0 -0
  70. clm/layer0/model/action.py +223 -0
  71. clm/layer0/model/card.py +280 -0
  72. clm/layer0/model/card_triad.py +427 -0
  73. clm/layer0/model/compatibility.py +60 -0
  74. clm/layer0/model/dictionary.py +46 -0
  75. clm/layer0/model/dots.py +331 -0
  76. clm/layer0/model/event_producer.py +88 -0
  77. clm/layer0/model/g_time.py +89 -0
  78. clm/layer0/model/handle.py +163 -0
  79. clm/layer0/model/hash/__init__.py +6 -0
  80. clm/layer0/model/hash/algorithms/__init__.py +3 -0
  81. clm/layer0/model/hash/algorithms/custom_hash.py +7 -0
  82. clm/layer0/model/hash/algorithms/local_sha256.py +178 -0
  83. clm/layer0/model/hash/constants.py +53 -0
  84. clm/layer0/model/hash/enums.py +60 -0
  85. clm/layer0/model/hash/validator.py +307 -0
  86. clm/layer0/model/interpreter.py +275 -0
  87. clm/layer0/model/pagination.py +45 -0
  88. clm/layer0/model/pcard.py +716 -0
  89. clm/layer0/model/schema.py +44 -0
  90. clm/layer0/model/utils/__init__.py +1 -0
  91. clm/layer0/model/utils/content_analyzer.py +152 -0
  92. clm/layer0/model/validators/__init__.py +1 -0
  93. clm/layer0/model/validators/base_validator.py +44 -0
  94. clm/layer0/model/validators/binary_validator.py +66 -0
  95. clm/layer0/model/validators/text_validator.py +82 -0
  96. clm/layer0/model/validators/validation_registry.py +49 -0
  97. clm/layer0/model/vcard.py +823 -0
  98. clm/layer0/model/vcard_ext/__init__.py +39 -0
  99. clm/layer0/model/vcard_ext/core.py +60 -0
  100. clm/layer0/model/vcard_ext/network.py +23 -0
  101. clm/layer0/model/vcard_ext/observability.py +63 -0
  102. clm/layer0/model/vcard_ext/storage.py +60 -0
  103. clm/layer0/model/vcard_ext/vendors.py +211 -0
  104. clm/layer0/model/vcard_sandwich.py +119 -0
  105. clm/layer0/model/vcard_vocabulary.py +463 -0
  106. clm/layer0/model/workflow.py +274 -0
  107. clm/layer0/narrative.py +431 -0
  108. clm/layer0/ontology/collection.py +125 -0
  109. clm/layer0/parser.py +429 -0
  110. clm/layer0/relations.py +73 -0
  111. clm/layer0/rule_evaluator.py +251 -0
  112. clm/layer0/schema/__init__.py +38 -0
  113. clm/layer0/schema/ddl.py +97 -0
  114. clm/layer0/templates/abstract.yaml +99 -0
  115. clm/layer0/templates/balanced.yaml +253 -0
  116. clm/layer0/templates/concrete.yaml +172 -0
  117. clm/layer0/type_registry.py +223 -0
  118. clm/layer0/types.py +204 -0
  119. clm/layer0/utils.py +886 -0
  120. clm/layer0/verifier_core.py +262 -0
  121. clm/layer1/__init__.py +31 -0
  122. clm/layer1/action_dispatcher.py +275 -0
  123. clm/layer1/adapter_registry.py +233 -0
  124. clm/layer1/algebra_baldwin_generators.py +192 -0
  125. clm/layer1/arrow_compiler.py +525 -0
  126. clm/layer1/batch_processor.py +152 -0
  127. clm/layer1/cel/__init__.py +27 -0
  128. clm/layer1/cel/builder.py +173 -0
  129. clm/layer1/engine.py +759 -0
  130. clm/layer1/engine_async.py +89 -0
  131. clm/layer1/eval_callback.py +49 -0
  132. clm/layer1/gate_composer.py +229 -0
  133. clm/layer1/hoare_sandwich.py +298 -0
  134. clm/layer1/identity_guard.py +348 -0
  135. clm/layer1/matrix.py +138 -0
  136. clm/layer1/neural_runtime.py +221 -0
  137. clm/layer1/operations/__init__.py +123 -0
  138. clm/layer1/operations/augment.py +156 -0
  139. clm/layer1/operations/base.py +109 -0
  140. clm/layer1/operations/builtins.py +338 -0
  141. clm/layer1/operations/dispatch.py +144 -0
  142. clm/layer1/operations/exclude.py +107 -0
  143. clm/layer1/operations/handle.py +197 -0
  144. clm/layer1/operations/identity.py +139 -0
  145. clm/layer1/operations/invert.py +66 -0
  146. clm/layer1/operations/loader.py +116 -0
  147. clm/layer1/operations/manipulators/__init__.py +23 -0
  148. clm/layer1/operations/manipulators/document.py +211 -0
  149. clm/layer1/operations/manipulators/media.py +134 -0
  150. clm/layer1/operations/manipulators/structured.py +159 -0
  151. clm/layer1/operations/manipulators/tabular.py +334 -0
  152. clm/layer1/operations/port.py +65 -0
  153. clm/layer1/operations/services.py +529 -0
  154. clm/layer1/operations/split.py +117 -0
  155. clm/layer1/operations/substitute.py +75 -0
  156. clm/layer1/petri.py +1052 -0
  157. clm/layer1/pocketflow_orchestrator.py +408 -0
  158. clm/layer1/pocketflow_scheduler.py +519 -0
  159. clm/layer1/protocols.py +197 -0
  160. clm/layer1/runtime_adapter.py +718 -0
  161. clm/layer1/runtimes/__init__.py +71 -0
  162. clm/layer1/runtimes/_generated_assertions.py +125 -0
  163. clm/layer1/runtimes/_generated_types.py +42 -0
  164. clm/layer1/runtimes/base.py +265 -0
  165. clm/layer1/runtimes/binary.py +101 -0
  166. clm/layer1/runtimes/factory.py +203 -0
  167. clm/layer1/runtimes/javascript.py +321 -0
  168. clm/layer1/runtimes/javascript_runtime.js +33 -0
  169. clm/layer1/runtimes/js_scripts/isomorphism.mjs +22 -0
  170. clm/layer1/runtimes/js_scripts/loader.mjs +52 -0
  171. clm/layer1/runtimes/lambda_calc.py +126 -0
  172. clm/layer1/runtimes/python.py +288 -0
  173. clm/layer1/runtimes/python_runtime.py +41 -0
  174. clm/layer1/runtimes/script.py +114 -0
  175. clm/layer1/sandbox.py +334 -0
  176. clm/layer1/savepoint_guard.py +136 -0
  177. clm/layer1/sparse_net.py +122 -0
  178. clm/layer1/spec_tester.py +165 -0
  179. clm/layer1/store.py +295 -0
  180. clm/layer1/test_runner.py +498 -0
  181. clm/layer1/type_inspector.py +166 -0
  182. clm/layer1/verifier.py +772 -0
  183. clm/layer1/vm.py +91 -0
  184. clm/layer2/__init__.py +78 -0
  185. clm/layer2/card_collection.py +678 -0
  186. clm/layer2/classifier.py +479 -0
  187. clm/layer2/engine/__init__.py +1 -0
  188. clm/layer2/engine/abstract_sql_engine.py +320 -0
  189. clm/layer2/engine/base.py +193 -0
  190. clm/layer2/engine/duckdb_engine.py +576 -0
  191. clm/layer2/engine/sqlite_engine.py +518 -0
  192. clm/layer2/fiber/__init__.py +31 -0
  193. clm/layer2/fiber/context.py +89 -0
  194. clm/layer2/fiber/disposable.py +163 -0
  195. clm/layer2/fiber/fiber.py +125 -0
  196. clm/layer2/fiber/hoare.py +191 -0
  197. clm/layer2/file_io.py +376 -0
  198. clm/layer2/file_registrar.py +77 -0
  199. clm/layer2/ingest.py +202 -0
  200. clm/layer2/lineage.py +322 -0
  201. clm/layer2/mcard_fs.py +262 -0
  202. clm/layer2/merkle.py +11 -0
  203. clm/layer2/mime.py +244 -0
  204. clm/layer2/mime_detector.py +245 -0
  205. clm/layer2/private_collection.py +118 -0
  206. clm/layer2/shared_store.py +11 -0
  207. clm/layer2/storage/__init__.py +190 -0
  208. clm/layer2/storage/db_bitemporal.py +141 -0
  209. clm/layer2/storage/db_driver.py +331 -0
  210. clm/layer2/storage/db_hyperlinks.py +214 -0
  211. clm/layer2/storage/db_pool.py +139 -0
  212. clm/layer2/storage/db_repository.py +229 -0
  213. clm/layer2/storage/savepoint.py +78 -0
  214. clm/layer2/storage/sqlite_wal.py +74 -0
  215. clm/layer2/tridb.py +245 -0
  216. clm/layer2/vcard_receipt.py +71 -0
  217. clm/layer2/vfs.py +17 -0
  218. clm/layer3/__init__.py +141 -0
  219. clm/layer3/agency/__init__.py +19 -0
  220. clm/layer3/agency/config.py +220 -0
  221. clm/layer3/agency/providers/__init__.py +15 -0
  222. clm/layer3/agency/providers/base.py +91 -0
  223. clm/layer3/agency/providers/mlc_llm.py +169 -0
  224. clm/layer3/agency/providers/ollama.py +203 -0
  225. clm/layer3/agency/router.py +71 -0
  226. clm/layer3/agency/runtime.py +524 -0
  227. clm/layer3/gateway.py +353 -0
  228. clm/layer3/open_interpreter_adapter.py +159 -0
  229. clm/layer3/rag/__init__.py +70 -0
  230. clm/layer3/rag/cli.py +228 -0
  231. clm/layer3/rag/config.py +153 -0
  232. clm/layer3/rag/embeddings/__init__.py +16 -0
  233. clm/layer3/rag/embeddings/base.py +79 -0
  234. clm/layer3/rag/embeddings/ollama.py +211 -0
  235. clm/layer3/rag/embeddings/vision.py +290 -0
  236. clm/layer3/rag/engine.py +276 -0
  237. clm/layer3/rag/graph/__init__.py +22 -0
  238. clm/layer3/rag/graph/community.py +198 -0
  239. clm/layer3/rag/graph/engine.py +433 -0
  240. clm/layer3/rag/graph/extractor.py +255 -0
  241. clm/layer3/rag/graph/schema.py +115 -0
  242. clm/layer3/rag/graph/store.py +641 -0
  243. clm/layer3/rag/indexer.py +333 -0
  244. clm/layer3/rag/llm_providers.json +52 -0
  245. clm/layer3/rag/semantic_versioning.py +313 -0
  246. clm/layer3/rag/vector/__init__.py +28 -0
  247. clm/layer3/rag/vector/handle_vector_store.py +749 -0
  248. clm/layer3/rag/vector/schema.py +151 -0
  249. clm/layer3/rag/vector/store.py +632 -0
  250. clm/layer3/reticulum/__init__.py +55 -0
  251. clm/layer3/reticulum/bridge.py +74 -0
  252. clm/layer3/reticulum/daemon.py +88 -0
  253. clm/layer3/reticulum/discovery.py +164 -0
  254. clm/layer3/reticulum/identity.py +107 -0
  255. clm/layer3/reticulum/media.py +148 -0
  256. clm/layer3/reticulum/transport.py +133 -0
  257. clm/layer3/satori/__init__.py +209 -0
  258. clm/layer3/satori/alpha_conversion.py +168 -0
  259. clm/layer3/satori/beta_reduction.py +359 -0
  260. clm/layer3/satori/continuation.py +38 -0
  261. clm/layer3/satori/eta_conversion.py +205 -0
  262. clm/layer3/satori/free_variables.py +163 -0
  263. clm/layer3/satori/io_effects.py +370 -0
  264. clm/layer3/satori/lambda_runtime.py +726 -0
  265. clm/layer3/satori/lambda_term.py +230 -0
  266. clm/layer3/satori/sheaf_audit.py +261 -0
  267. clm/layer3/satori/speech_act.py +279 -0
  268. clm/layer4/README.md +9 -0
  269. clm/layer4/__init__.py +67 -0
  270. clm/layer4/_sovereign_executor.py +580 -0
  271. clm/layer4/action_commands.py +719 -0
  272. clm/layer4/api.py +236 -0
  273. clm/layer4/bootstrap/__init__.py +13 -0
  274. clm/layer4/bootstrap/bootstrapper.py +118 -0
  275. clm/layer4/bootstrap/cleanup.py +44 -0
  276. clm/layer4/bootstrap/clm_hooks.py +49 -0
  277. clm/layer4/bootstrap/genesis.py +77 -0
  278. clm/layer4/bootstrap/injection.py +125 -0
  279. clm/layer4/cli.py +549 -0
  280. clm/layer4/collection_manager.py +553 -0
  281. clm/layer4/commands/__init__.py +81 -0
  282. clm/layer4/commands/action_cmd.py +42 -0
  283. clm/layer4/commands/check_cmd.py +82 -0
  284. clm/layer4/commands/envelope.py +73 -0
  285. clm/layer4/commands/evaluate_cmd.py +224 -0
  286. clm/layer4/commands/mcard_cmd.py +24 -0
  287. clm/layer4/commands/petri_cmd.py +92 -0
  288. clm/layer4/commands/tridb_cmd.py +319 -0
  289. clm/layer4/commands/version_cmd.py +27 -0
  290. clm/layer4/diff_commands.py +167 -0
  291. clm/layer4/eoa_sponsor.py +232 -0
  292. clm/layer4/events.py +69 -0
  293. clm/layer4/flux_gateway.py +133 -0
  294. clm/layer4/gateway.py +91 -0
  295. clm/layer4/loader.py +307 -0
  296. clm/layer4/mcard_commands.py +166 -0
  297. clm/layer4/membrane.py +273 -0
  298. clm/layer4/navigation.py +50 -0
  299. clm/layer4/protocol/protocol_entry_python.py +108 -0
  300. clm/layer4/protocol/python_sync.py +126 -0
  301. clm/layer4/runner.py +150 -0
  302. clm/layer4/satori.py +262 -0
  303. clm/layer4/server/python_sync_client.py +150 -0
  304. clm/layer4/server/python_sync_server.py +139 -0
  305. clm/layer4/server/service_api.py +315 -0
  306. clm/layer4/server/start_servers.py +107 -0
  307. clm/layer4/server/stop_servers.py +24 -0
  308. clm/layer4/simulated_optimization_fix.py +0 -0
  309. clm/layer4/storage.py +463 -0
  310. clm/layer4/tridb_admin.py +268 -0
  311. clm/layer4/websocket/test_ws_client.py +104 -0
  312. clm/layer4/websocket/test_ws_server.py +138 -0
  313. clm/layer4/websocket/ws_server_local.py +160 -0
  314. clm/layer5/__init__.py +51 -0
  315. clm/layer5/async_batch_writer.py +133 -0
  316. clm/layer5/learning.py +276 -0
  317. clm/layer5/telemetry_hook.py +107 -0
  318. clm/layer5/type_lattice.py +238 -0
  319. clm/py.typed +0 -0
  320. clm/schemas/__init__.py +652 -0
  321. clm/schemas/abstract_face_schema.yaml +77 -0
  322. clm/schemas/cel_schema.yaml +416 -0
  323. clm/schemas/lifecycle/__init__.py +45 -0
  324. clm/schemas/lifecycle/models.py +123 -0
  325. clm/schemas/lifecycle/service.py +364 -0
  326. clm/schemas/mcard_schema.sql +192 -0
  327. clm/schemas/mcard_vector_schema.sql +283 -0
  328. clm/schemas/mlp-context-v1.json +91 -0
  329. clm_kernel-0.0.0.dist-info/METADATA +121 -0
  330. clm_kernel-0.0.0.dist-info/RECORD +333 -0
  331. clm_kernel-0.0.0.dist-info/WHEEL +4 -0
  332. clm_kernel-0.0.0.dist-info/entry_points.txt +3 -0
  333. clm_kernel-0.0.0.dist-info/licenses/LICENSE +21 -0
clm/layer1/verifier.py ADDED
@@ -0,0 +1,772 @@
1
+ """CLM Verifier — Pre/post condition verification, identity bootstrapping, and type validation.
2
+
3
+ Handles the execution lifecycle gates:
4
+ - verify_precondition: integrity check + agent bootstrap + type validation
5
+ - validate_postcondition: seal result as VCard execution MCard
6
+ - emit_exec_mcard_py: emit execution MCard to Tri-Database
7
+ - resolve_active_hashes_py: resolve A-hash / C-hash pair from manifest
8
+ - check_precondition_py: runtime requirement guard (e.g. interpreter:python>=3.9)
9
+ - normalize_output_py: output normalization helper
10
+ """
11
+
12
+ import asyncio
13
+ import json
14
+ import logging
15
+ import os
16
+ import subprocess
17
+ import sys
18
+ from collections.abc import Callable
19
+ from dataclasses import dataclass
20
+ from typing import Any
21
+
22
+ import base58
23
+ import yaml
24
+ from cryptography.hazmat.primitives import serialization
25
+ from cryptography.hazmat.primitives.asymmetric import ed25519
26
+
27
+ try:
28
+ from clm.layer0.model.card import MCard
29
+ except ImportError:
30
+ from clm.layer0.mcard import MCard
31
+
32
+ from clm.cross_cutting.clm_logger import get_logger
33
+ from clm.layer0.narrative import Writer
34
+ from clm.layer0.utils import (
35
+ clm_hash_file,
36
+ clm_hash_string,
37
+ get_bash_command,
38
+ prefixed_hash_raw,
39
+ resolve_clm_path,
40
+ )
41
+ from clm.layer0.verifier_core import ( # re-export for backward compat
42
+ emit_exec_mcard_py as _emit_exec_mcard_py_core,
43
+ )
44
+ from clm.layer0.verifier_core import (
45
+ verify_precondition as _verify_precondition_core,
46
+ )
47
+
48
+ _logger = get_logger("clm.layer1.verifier")
49
+
50
+
51
+ # Lazy load algebra helpers to avoid circular dependencies
52
+ def get_load_manifest_str() -> Callable:
53
+ """
54
+ Lazy loads the manifest string loader from algebra.py to avoid circular dependencies.
55
+ """
56
+ from clm.layer0.algebra import load_normalized_manifest_str
57
+ return load_normalized_manifest_str
58
+
59
+ def get_load_manifest() -> Callable:
60
+ """
61
+ Lazy loads the manifest parser from algebra.py.
62
+ """
63
+ from clm.layer0.algebra import load_normalized_manifest
64
+ return load_normalized_manifest
65
+
66
+
67
+ def verify_precondition(clm_root: str, clm_file: str) -> bool:
68
+ """
69
+ Validates the precondition bounds and zero-trust identity before executing a PCard.
70
+
71
+ Delegates to clm.layer0.verifier_core.verify_precondition (Sprint 143 V-1 fix).
72
+ """
73
+ return _verify_precondition_core(clm_root, clm_file)
74
+
75
+ def emit_exec_mcard_py(clm_root: str, operation: str, content_hash: str, extra_yaml: str = "", a_hash: str = "", c_hash: str = "") -> str:
76
+ """
77
+ Emits an Execution MCard to the Tri-Database to track state changes.
78
+
79
+ Delegates to clm.layer0.verifier_core.emit_exec_mcard_py (Sprint 143 V-1 fix).
80
+ """
81
+ return _emit_exec_mcard_py_core(clm_root, operation, content_hash, extra_yaml, a_hash, c_hash)
82
+
83
+ def resolve_active_hashes_py(clm_file: str) -> tuple[str | None, str | None]:
84
+ """
85
+ Resolves the abstract and concrete hashes from a CLM manifest.
86
+
87
+ Args:
88
+ clm_file (str): The CLM manifest file.
89
+
90
+ Returns:
91
+ Tuple[Optional[str], Optional[str]]: A tuple containing the a_hash and c_hash, or (None, None) if parsing fails.
92
+ """
93
+ load_normalized_manifest_fn = get_load_manifest()
94
+ try:
95
+ clm_root = os.environ.get("CLM_ROOT") or os.getcwd()
96
+ yaml_data = load_normalized_manifest_fn(clm_root, clm_file)
97
+ inner = yaml_data.get("clm", yaml_data)
98
+ abstract_spec = inner.get("abstract_spec")
99
+ concrete_impl = inner.get("concrete_impl")
100
+ if not abstract_spec or not concrete_impl:
101
+ return None, None
102
+
103
+ import copy
104
+ c_clean = copy.deepcopy(concrete_impl)
105
+ if "precondition" in c_clean and isinstance(c_clean["precondition"], dict):
106
+ c_clean["precondition"].pop("vcard_pre", None)
107
+ if "postcondition" in c_clean and isinstance(c_clean["postcondition"], dict):
108
+ c_clean["postcondition"].pop("vcard_post", None)
109
+
110
+ a_json = json.dumps(abstract_spec, separators=(',', ':'))
111
+ a_hash = clm_hash_string(a_json)
112
+
113
+ c_clean["a_hash"] = prefixed_hash_raw(a_hash)
114
+ c_json = json.dumps(c_clean, separators=(',', ':'))
115
+ c_hash = clm_hash_string(c_json)
116
+ return prefixed_hash_raw(a_hash), prefixed_hash_raw(c_hash)
117
+ except Exception as e:
118
+ _logger.info(f"[RESOLVE] Failed to resolve active hashes: {e}")
119
+ return None, None
120
+
121
+ def validate_postcondition(clm_root: str, clm_file: str, eval_rc: int) -> bool:
122
+ """
123
+ Validates the postcondition invariants and emits a signed VCard to seal the result.
124
+
125
+ Args:
126
+ clm_root (str): The root directory of the CLM workspace.
127
+ clm_file (str): The PCard file.
128
+ eval_rc (int): The exit code from the execution layer.
129
+
130
+ Returns:
131
+ bool: True if the postcondition was validated, False otherwise.
132
+ """
133
+ load_normalized_manifest_fn = get_load_manifest()
134
+ clm_file = resolve_clm_path(clm_root, clm_file)
135
+ content = ""
136
+ exec_out = os.environ.get("_EXEC_OUT")
137
+ if exec_out and os.path.exists(exec_out) and os.path.getsize(exec_out) > 0:
138
+ with open(exec_out, encoding="utf-8") as f:
139
+ lines = f.readlines()
140
+ if len(lines) < 5:
141
+ content = " ".join([ln.strip() for ln in lines]).replace('"', '').replace('\\', '')
142
+ else:
143
+ content = prefixed_hash_raw(clm_hash_file(exec_out))
144
+ else:
145
+ content = f"exit code: {eval_rc}"
146
+
147
+ content_hash = clm_hash_string(content)
148
+ extra_yaml = f"content: \"{content}\""
149
+ operation = "validate_postcondition"
150
+
151
+ if clm_file and os.path.exists(clm_file):
152
+ try:
153
+ yaml_data = load_normalized_manifest_fn(clm_root, clm_file)
154
+ inner = yaml_data.get("clm", yaml_data)
155
+ operation = inner.get("concrete_impl", {}).get("operation", "unknown")
156
+ except Exception:
157
+ operation = "unknown"
158
+ impl_hash = clm_hash_file(clm_file)
159
+ extra_yaml += f"\noperation: \"{operation}\"\nimpl_hash: \"{impl_hash}\""
160
+
161
+ a_hash, c_hash = None, None
162
+ if clm_file and os.path.exists(clm_file):
163
+ a_hash, c_hash = resolve_active_hashes_py(clm_file)
164
+
165
+ mcard_file = emit_exec_mcard_py(clm_root, operation, content_hash, extra_yaml, a_hash or "", c_hash or "")
166
+ os.environ["CLM_LAST_MCARD_FILE"] = mcard_file
167
+ os.environ["CLM_LAST_MCARD_HASH"] = content_hash
168
+ _logger.info(f"[VCARD POST] ✅ Execution MCard generated: {mcard_file}")
169
+ _logger.info(f"[VCARD POST] content_hash: {content_hash}")
170
+ return True
171
+
172
+ def check_precondition_py(req_string: str) -> bool:
173
+ """
174
+ Evaluates a runtime requirement string (e.g. interpreter:python>=3.9) against the current host environment.
175
+
176
+ Args:
177
+ req_string (str): The requirement string.
178
+
179
+ Returns:
180
+ bool: True if the requirement is satisfied, False otherwise.
181
+ """
182
+ import re
183
+ m = re.match(r"^interpreter:([a-zA-Z0-9_-]+)(>=|<=|>|<|==|=)([0-9.]+)$", req_string)
184
+ if not m:
185
+ return True
186
+ interpreter, op, req_ver = m.groups()
187
+
188
+ current_ver = ""
189
+ if interpreter == "python":
190
+ current_ver = ".".join(map(str, sys.version_info[:3]))
191
+ elif interpreter in ("shell", "bash"):
192
+ try:
193
+ proc = subprocess.Popen([get_bash_command(), "-c", "echo $BASH_VERSION"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8")
194
+ stdout_data, _ = proc.communicate()
195
+ current_ver = stdout_data.strip().split("(")[0]
196
+ except Exception:
197
+ return False
198
+ elif interpreter == "node":
199
+ try:
200
+ proc = subprocess.Popen(["node", "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8")
201
+ stdout_data, _ = proc.communicate()
202
+ current_ver = stdout_data.strip().lstrip("v")
203
+ except Exception:
204
+ return False
205
+ elif interpreter == "rustc":
206
+ try:
207
+ proc = subprocess.Popen(["rustc", "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8")
208
+ stdout_data, _ = proc.communicate()
209
+ parts = stdout_data.strip().split()
210
+ if len(parts) >= 2:
211
+ current_ver = parts[1]
212
+ import tempfile
213
+ with tempfile.TemporaryDirectory() as tmpdir:
214
+ test_src = os.path.join(tmpdir, "chk.rs")
215
+ test_bin = os.path.join(tmpdir, "chk.exe" if sys.platform == "win32" else "chk")
216
+ with open(test_src, "w") as f:
217
+ f.write('fn main(){println!("1");}')
218
+ c_proc = subprocess.Popen(["rustc", "-O", "-o", test_bin, test_src], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
219
+ c_proc.communicate()
220
+ if c_proc.returncode != 0:
221
+ return False
222
+ except Exception:
223
+ return False
224
+ else:
225
+ import shutil
226
+ return shutil.which(interpreter) is not None
227
+
228
+ def parse_version(v_str):
229
+ m_v = re.search(r"(\d+)\.(\d+)(?:\.(\d+))?", v_str)
230
+ if not m_v:
231
+ return (0, 0, 0)
232
+ return (int(m_v.group(1)), int(m_v.group(2)), int(m_v.group(3)) if m_v.group(3) else 0)
233
+
234
+ try:
235
+ v1 = parse_version(current_ver)
236
+ v2 = parse_version(req_ver)
237
+ ops = {">=": v1 >= v2, "<=": v1 <= v2, ">": v1 > v2, "<": v1 < v2}
238
+ return ops.get(op, v1 == v2)
239
+ except Exception:
240
+ return False
241
+
242
+ def normalize_output_py(raw_out: str) -> str:
243
+ """
244
+ Normalizes command output by stripping returns and sorting JSON keys.
245
+
246
+ Args:
247
+ raw_out (str): The raw standard output string.
248
+
249
+ Returns:
250
+ str: The normalized string.
251
+ """
252
+ stripped = raw_out.replace("\r", "").rstrip()
253
+ try:
254
+ parsed = json.loads(stripped)
255
+ return json.dumps(parsed, sort_keys=True, separators=(",", ":"))
256
+ except Exception:
257
+ return stripped
258
+
259
+
260
+ def pem_to_did(pem_bytes: bytes) -> str:
261
+ pubkey = serialization.load_pem_public_key(pem_bytes)
262
+ raw_bytes = pubkey.public_bytes(
263
+ encoding=serialization.Encoding.Raw,
264
+ format=serialization.PublicFormat.Raw
265
+ )
266
+ multicodec = b'\xed\x01' + raw_bytes
267
+ return 'did:key:z' + base58.b58encode(multicodec).decode('utf-8')
268
+
269
+
270
+ def did_to_pubkey(did: str) -> ed25519.Ed25519PublicKey:
271
+ if not did.startswith('did:key:z'):
272
+ raise ValueError('Unsupported DID method: ' + did)
273
+ b58_part = did[9:]
274
+ decoded = base58.b58decode(b58_part)
275
+ if decoded[0] != 0xed or decoded[1] != 0x01:
276
+ raise ValueError('Unsupported multicodec identifier in did:key')
277
+ raw_key = decoded[2:]
278
+ return ed25519.Ed25519PublicKey.from_public_bytes(raw_key)
279
+
280
+
281
+ def clm_derive_session_key() -> dict:
282
+ private_key = ed25519.Ed25519PrivateKey.generate()
283
+ public_key = private_key.public_key()
284
+
285
+ private_pem = private_key.private_bytes(
286
+ encoding=serialization.Encoding.PEM,
287
+ format=serialization.PrivateFormat.PKCS8,
288
+ encryption_algorithm=serialization.NoEncryption()
289
+ ).decode('utf-8')
290
+
291
+ public_pem = public_key.public_bytes(
292
+ encoding=serialization.Encoding.PEM,
293
+ format=serialization.PublicFormat.SubjectPublicKeyInfo
294
+ ).decode('utf-8')
295
+
296
+ did = pem_to_did(public_pem.encode('utf-8'))
297
+
298
+ return {
299
+ "private_key": private_pem,
300
+ "public_key": public_pem,
301
+ "did": did
302
+ }
303
+
304
+
305
+ def clm_issue_delegation(parent_private_key_path: str, session_did: str, ttl: int = 3600, capabilities: str = "exec") -> str:
306
+ with open(parent_private_key_path, "rb") as f:
307
+ parent_priv = serialization.load_pem_private_key(f.read(), password=None)
308
+
309
+ parent_pub = parent_priv.public_key()
310
+ parent_pub_pem = parent_pub.public_bytes(
311
+ encoding=serialization.Encoding.PEM,
312
+ format=serialization.PublicFormat.SubjectPublicKeyInfo
313
+ )
314
+ parent_did = pem_to_did(parent_pub_pem)
315
+
316
+ import datetime
317
+ expires_at = (datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=ttl)).isoformat()
318
+ if expires_at.endswith('+00:00'):
319
+ expires_at = expires_at[:-6] + 'Z'
320
+ elif '+' not in expires_at and '-' not in expires_at[-6:] and not expires_at.endswith('Z'):
321
+ expires_at = expires_at + 'Z'
322
+
323
+ payload = f"{parent_did}|{session_did}|{expires_at}|{capabilities}"
324
+ signature = parent_priv.sign(payload.encode('utf-8'))
325
+ signature_hex = signature.hex()
326
+
327
+ caps_list = [c.strip() for c in capabilities.split(",") if c.strip()]
328
+
329
+ delegation_data = {
330
+ "session_delegation": {
331
+ "type": "meta/session_delegation",
332
+ "parent_did": parent_did,
333
+ "session_did": session_did,
334
+ "expires_at": expires_at,
335
+ "capabilities": caps_list,
336
+ "signature": f"ed25519:{signature_hex}"
337
+ }
338
+ }
339
+ return yaml.dump(delegation_data, default_flow_style=False)
340
+
341
+
342
+ def clm_verify_delegation(cert_yaml: str, expected_session_did: str = None, expected_cap: str = None) -> bool:
343
+ try:
344
+ data = yaml.safe_load(cert_yaml)
345
+ sd = data.get("session_delegation", {})
346
+ parent_did = sd.get("parent_did")
347
+ session_did = sd.get("session_did")
348
+ expires_at = sd.get("expires_at")
349
+ signature = sd.get("signature")
350
+ capabilities = sd.get("capabilities", [])
351
+ except Exception:
352
+ return False
353
+
354
+ if not all([parent_did, session_did, expires_at, signature]):
355
+ return False
356
+
357
+ if expected_session_did and session_did != expected_session_did:
358
+ return False
359
+
360
+ if expected_cap and expected_cap not in capabilities:
361
+ return False
362
+
363
+ import datetime
364
+ try:
365
+ exp_str = expires_at
366
+ if exp_str.endswith('Z'):
367
+ exp_str = exp_str[:-1] + '+00:00'
368
+ exp_dt = datetime.datetime.fromisoformat(exp_str)
369
+ now_dt = datetime.datetime.now(datetime.timezone.utc)
370
+ if now_dt > exp_dt:
371
+ return False
372
+ except Exception:
373
+ return False
374
+
375
+ try:
376
+ caps_comma = ",".join(capabilities)
377
+ payload = f"{parent_did}|{session_did}|{expires_at}|{caps_comma}"
378
+
379
+ signature_hex = signature.split("ed25519:")[1]
380
+ signature_bytes = bytes.fromhex(signature_hex)
381
+
382
+ parent_pub = did_to_pubkey(parent_did)
383
+ parent_pub.verify(signature_bytes, payload.encode('utf-8'))
384
+
385
+ os.environ["CLM_DELEGATED_PARENT_DID"] = parent_did
386
+ return True
387
+ except Exception:
388
+ return False
389
+
390
+
391
+ @dataclass
392
+ class VerificationResult:
393
+ """Result of CLM verification"""
394
+
395
+ is_valid: bool
396
+ abstract_valid: bool
397
+ concrete_valid: bool
398
+ balanced_valid: bool
399
+ errors: list[str]
400
+ warnings: list[str]
401
+ verification_details: dict[str, Any]
402
+
403
+
404
+ class CLMVerifier:
405
+ """
406
+ Verifier for the Cubical Logic Model.
407
+
408
+ Ensures that every PCard maintains consistency between:
409
+ 1. Abstract Specification (WHAT - the specification)
410
+ 2. Concrete Implementation (HOW - the implementation)
411
+ 3. Balanced Expectations (WHY - the verification proofs)
412
+ """
413
+
414
+ def __init__(self, engine):
415
+ self.engine = engine
416
+ self.logger = logging.getLogger(__name__)
417
+ self.verification_cache = {}
418
+
419
+ def verify_clm_consistency(
420
+ self, pcard: MCard, target: MCard, context: dict[str, Any] | None = None
421
+ ) -> VerificationResult:
422
+ """
423
+ Verify CLM consistency for a PCard-target pair.
424
+
425
+ This implements the core verification workflow:
426
+ 1. Load PCard and extract CLM dimensions
427
+ 2. Verify Abstract Specification
428
+ 3. Verify Concrete Implementation
429
+ 4. Verify Balanced Expectations
430
+ 5. Check cross-dimensional consistency
431
+ """
432
+ context = context or {}
433
+ errors = []
434
+ warnings = []
435
+ details = {}
436
+
437
+ try:
438
+ # Step 1: Parse PCard content
439
+ pcard_content = pcard.get_content().decode("utf-8")
440
+ pcard_data = yaml.safe_load(pcard_content)
441
+
442
+ if not isinstance(pcard_data, dict):
443
+ raise ValueError("PCard content must be a YAML dictionary")
444
+
445
+ # Extract CLM base (it might be nested under a 'clm' key)
446
+ clm_base = pcard_data.get("clm", pcard_data)
447
+
448
+ # Extract CLM dimensions (with aliases)
449
+ abstract = clm_base.get("abstract_spec", clm_base.get("abstract", {}))
450
+ concrete = clm_base.get("concrete_impl", clm_base.get("concrete", {}))
451
+ balanced = clm_base.get("balanced_exp", clm_base.get("balanced", {}))
452
+
453
+ self.logger.info(f"Verifying CLM dimensions for PCard {pcard.hash}")
454
+ self.logger.info(f"PCard keys: {list(pcard_data.keys())}")
455
+ self.logger.info(f"Abstract keys: {list(abstract.keys())}")
456
+
457
+ # Step 2: Verify Abstract Specification (Writer Monad)
458
+ abstract_writer = self._verify_abstract(abstract, target, context)
459
+ abstract_details, abstract_errors = abstract_writer.run()
460
+
461
+ errors.extend(abstract_errors)
462
+ details["abstract"] = abstract_details
463
+ abstract_valid = len(abstract_errors) == 0
464
+
465
+ # Step 3: Verify Concrete Implementation (Writer Monad)
466
+ concrete_writer = self._verify_concrete(concrete, abstract, target, context)
467
+ concrete_details, concrete_errors = concrete_writer.run()
468
+
469
+ errors.extend(concrete_errors)
470
+ details["concrete"] = concrete_details
471
+ concrete_valid = len(concrete_errors) == 0
472
+
473
+ # Step 4: Verify Balanced Expectations (Writer Monad)
474
+ balanced_writer = self._verify_balanced(
475
+ balanced, abstract, concrete, target, context
476
+ )
477
+ balanced_details, balanced_errors = balanced_writer.run()
478
+
479
+ errors.extend(balanced_errors)
480
+ details["balanced"] = balanced_details
481
+ balanced_valid = len(balanced_errors) == 0
482
+
483
+ # Step 5: Check cross-dimensional consistency (Writer Monad)
484
+ consistency_writer = self._verify_cross_consistency(
485
+ abstract, concrete, balanced
486
+ )
487
+ consistency_valid_flag, consistency_errors = consistency_writer.run()
488
+
489
+ errors.extend(consistency_errors)
490
+ consistency_valid = consistency_valid_flag and len(consistency_errors) == 0
491
+
492
+ details["cross_consistency"] = {
493
+ "valid": consistency_valid,
494
+ "errors": consistency_errors,
495
+ }
496
+
497
+ # Overall validity
498
+ is_valid = (
499
+ abstract_valid
500
+ and concrete_valid
501
+ and balanced_valid
502
+ and consistency_valid
503
+ )
504
+
505
+ if is_valid:
506
+ self.logger.info(f"CLM verification PASSED for PCard {pcard.hash}")
507
+ else:
508
+ self.logger.warning(
509
+ f"CLM verification FAILED for PCard {pcard.hash}: {errors}"
510
+ )
511
+
512
+ return VerificationResult(
513
+ is_valid=is_valid,
514
+ abstract_valid=abstract_valid,
515
+ concrete_valid=concrete_valid,
516
+ balanced_valid=balanced_valid,
517
+ errors=errors,
518
+ warnings=warnings,
519
+ verification_details=details,
520
+ )
521
+
522
+ except (
523
+ yaml.YAMLError,
524
+ UnicodeDecodeError,
525
+ ValueError,
526
+ TypeError,
527
+ KeyError,
528
+ AttributeError,
529
+ ) as e:
530
+ self.logger.error(f"CLM verification error: {str(e)}", exc_info=True)
531
+ return VerificationResult(
532
+ is_valid=False,
533
+ abstract_valid=False,
534
+ concrete_valid=False,
535
+ balanced_valid=False,
536
+ errors=[f"Verification error: {str(e)}"],
537
+ warnings=[],
538
+ verification_details={},
539
+ )
540
+
541
+ async def averify_clm_consistency(
542
+ self, pcard: MCard, target: MCard, context: dict[str, Any] | None = None
543
+ ) -> VerificationResult:
544
+ """Async wrapper for verify_clm_consistency using a thread pool."""
545
+ return await asyncio.to_thread(
546
+ self.verify_clm_consistency, pcard, target, context
547
+ )
548
+
549
+ def _verify_abstract(
550
+ self, abstract: dict[str, Any], target: MCard, context: dict[str, Any]
551
+ ) -> Writer[str, dict[str, Any]]:
552
+ """
553
+ Verify the Abstract Specification dimension using Writer Monad.
554
+ Returns Writer(details, errors).
555
+ """
556
+ errors = []
557
+ details = {}
558
+
559
+ # Check required abstract fields
560
+ required_fields = [
561
+ "purpose",
562
+ "inputs",
563
+ "outputs",
564
+ "preconditions",
565
+ "postconditions",
566
+ ]
567
+ for field in required_fields:
568
+ if field not in abstract:
569
+ errors.append(f"Missing required abstract field: {field}")
570
+
571
+ # Validate input/output specifications
572
+ inputs = abstract.get("inputs", {})
573
+ outputs = abstract.get("outputs", {})
574
+
575
+ if not isinstance(inputs, dict):
576
+ errors.append("Abstract inputs must be a dictionary")
577
+ else:
578
+ details["input_count"] = len(inputs)
579
+
580
+ if not isinstance(outputs, dict):
581
+ errors.append("Abstract outputs must be a dictionary")
582
+ else:
583
+ details["output_count"] = len(outputs)
584
+
585
+ # Validate preconditions and postconditions
586
+ preconditions = abstract.get("preconditions", [])
587
+ postconditions = abstract.get("postconditions", [])
588
+
589
+ if not isinstance(preconditions, list):
590
+ errors.append("Abstract preconditions must be a list")
591
+ else:
592
+ details["precondition_count"] = len(preconditions)
593
+
594
+ if not isinstance(postconditions, list):
595
+ errors.append("Abstract postconditions must be a list")
596
+ else:
597
+ details["postcondition_count"] = len(postconditions)
598
+
599
+ # Check semantic consistency
600
+ purpose = abstract.get("purpose", "")
601
+ if len(purpose) < 10:
602
+ errors.append("Abstract purpose should be descriptive (min 10 characters)")
603
+
604
+ details["purpose_length"] = len(purpose)
605
+
606
+ return Writer(lambda: (details, errors))
607
+
608
+ def _verify_concrete(
609
+ self,
610
+ concrete: dict[str, Any],
611
+ abstract: dict[str, Any],
612
+ target: MCard,
613
+ context: dict[str, Any],
614
+ ) -> Writer[str, dict[str, Any]]:
615
+ """
616
+ Verify the Concrete Implementation dimension using Writer Monad.
617
+ Returns Writer(details, errors).
618
+ """
619
+ errors = []
620
+ details = {}
621
+
622
+ # Check required concrete fields
623
+ if "operation" not in concrete:
624
+ errors.append("Missing required concrete field: operation")
625
+
626
+ # Validate operation matches abstract purpose
627
+ operation = concrete.get("operation", "")
628
+ abstract_purpose = abstract.get("purpose", "").lower()
629
+
630
+ if operation and abstract_purpose:
631
+ # Simple semantic check - in production would use NLP
632
+ if "transform" in abstract_purpose and "transform" not in operation:
633
+ errors.append("Concrete operation doesn't match abstract purpose")
634
+
635
+ details["operation"] = operation
636
+
637
+ # Check implementation details
638
+ implementation = concrete.get("implementation", {})
639
+ if not isinstance(implementation, dict):
640
+ errors.append("Concrete implementation must be a dictionary")
641
+ else:
642
+ details["implementation_keys"] = list(implementation.keys())
643
+
644
+ # Validate resource requirements
645
+ resources = concrete.get("resources", {})
646
+ if resources:
647
+ if not isinstance(resources, dict):
648
+ errors.append("Concrete resources must be a dictionary")
649
+ else:
650
+ details["resource_requirements"] = resources
651
+
652
+ # Check error handling
653
+ error_handling = concrete.get("error_handling", {})
654
+ if error_handling and not isinstance(error_handling, dict):
655
+ errors.append("Concrete error_handling must be a dictionary")
656
+
657
+ return Writer(lambda: (details, errors))
658
+
659
+ def _verify_balanced(
660
+ self,
661
+ balanced: dict[str, Any],
662
+ abstract: dict[str, Any],
663
+ concrete: dict[str, Any],
664
+ target: MCard,
665
+ context: dict[str, Any],
666
+ ) -> Writer[str, dict[str, Any]]:
667
+ """
668
+ Verify the Balanced Expectations dimension using Writer Monad.
669
+ Returns Writer(details, errors).
670
+ """
671
+ errors = []
672
+ details = {}
673
+
674
+ # Check required balanced fields
675
+ required_fields = ["test_cases", "expectations"]
676
+ for field in required_fields:
677
+ if field not in balanced:
678
+ errors.append(f"Missing required balanced field: {field}")
679
+
680
+ # Validate test cases
681
+ test_cases = balanced.get("test_cases", [])
682
+ if not isinstance(test_cases, list):
683
+ errors.append("Balanced test_cases must be a list")
684
+ else:
685
+ details["test_case_count"] = len(test_cases)
686
+
687
+ # Validate each test case structure
688
+ for i, test_case in enumerate(test_cases):
689
+ if not isinstance(test_case, dict):
690
+ errors.append(f"Test case {i} must be a dictionary")
691
+ continue
692
+
693
+ # Check for either BDD ('given', 'when', 'then') or ABC ('assumption', 'behavior', 'confirmation')
694
+ has_bdd = all(field in test_case for field in ["given", "when", "then"])
695
+ has_abc = all(
696
+ field in test_case
697
+ for field in ["assumption", "behavior", "confirmation"]
698
+ )
699
+
700
+ if not (has_bdd or has_abc):
701
+ errors.append(
702
+ f"Test case {i} missing required routing fields (BDD given/when/then or ABC assumption/behavior/confirmation)"
703
+ )
704
+
705
+ # Validate expectations
706
+ expectations = balanced.get("expectations", {})
707
+ if not isinstance(expectations, dict):
708
+ errors.append("Balanced expectations must be a dictionary")
709
+ else:
710
+ details["expectation_keys"] = list(expectations.keys())
711
+
712
+ # Check performance expectations
713
+ performance = expectations.get("performance", {})
714
+ if performance:
715
+ if not isinstance(performance, dict):
716
+ errors.append("Performance expectations must be a dictionary")
717
+ else:
718
+ details["performance_expectations"] = performance
719
+
720
+ # Check quality metrics
721
+ quality = expectations.get("quality", {})
722
+ if quality:
723
+ if not isinstance(quality, dict):
724
+ errors.append("Quality expectations must be a dictionary")
725
+ else:
726
+ details["quality_expectations"] = quality
727
+
728
+ return Writer(lambda: (details, errors))
729
+
730
+ def _verify_cross_consistency(
731
+ self,
732
+ abstract: dict[str, Any],
733
+ concrete: dict[str, Any],
734
+ balanced: dict[str, Any],
735
+ ) -> Writer[str, bool]:
736
+ """
737
+ Verify cross-dimensional consistency between CLM dimensions using Writer Monad.
738
+ Returns Writer(valid_flag, errors).
739
+
740
+ Note: This is intentionally lenient to match JavaScript CLMRunner behavior,
741
+ which doesn't perform strict abstract/concrete input matching.
742
+ """
743
+ errors = []
744
+ valid = len(errors) == 0
745
+ return Writer(lambda: (valid, errors))
746
+
747
+
748
+ __all__ = [
749
+ "verify_precondition",
750
+ "emit_exec_mcard_py",
751
+ "resolve_active_hashes_py",
752
+ "validate_postcondition",
753
+ "check_precondition_py",
754
+ "normalize_output_py",
755
+ "pem_to_did",
756
+ "did_to_pubkey",
757
+ "clm_derive_session_key",
758
+ "clm_issue_delegation",
759
+ "clm_verify_delegation",
760
+ "VerificationResult",
761
+ "CLMVerifier",
762
+ ]
763
+
764
+ # Register manifest evaluator with native layer (Dependency Inversion for layer stratification)
765
+ try:
766
+ from clm.cross_cutting.native import register_manifest_evaluator
767
+ from clm.layer1.gate_composer import run_nested_evaluate
768
+
769
+ register_manifest_evaluator(run_nested_evaluate)
770
+ except ImportError:
771
+ pass
772
+