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
@@ -0,0 +1,718 @@
1
+ """CLM Runtime Adapter — Polyglot runtime execution and cross-runtime parity verification.
2
+
3
+ Handles the dispatch of CLM payloads to different runtimes (shell, python, node, rust),
4
+ cross-runtime parity verification, composite evaluation, and internal reference tests.
5
+ """
6
+
7
+ import json
8
+ import os
9
+ import subprocess as _sp
10
+ import sys
11
+ from typing import Any
12
+
13
+ import yaml
14
+
15
+ from clm.cross_cutting.clm_logger import get_logger
16
+ from clm.cross_cutting.vcard import vcard_sandwich
17
+ from clm.layer0.context import CLMContext
18
+ from clm.layer0.utils import (
19
+ clm_hash_file,
20
+ clm_hash_string,
21
+ detect_host_os,
22
+ get_bash_command,
23
+ resolve_clm_path,
24
+ )
25
+
26
+ from .verifier import (
27
+ check_precondition_py,
28
+ emit_exec_mcard_py,
29
+ get_load_manifest,
30
+ normalize_output_py,
31
+ resolve_active_hashes_py,
32
+ )
33
+
34
+ _logger = get_logger("clm.layer1.runtime_adapter")
35
+
36
+
37
+ def _resolve_rust_binary(clm_root: str) -> str | None:
38
+ """Locate the compiled clm_rust_core binary.
39
+
40
+ Sprint 144 (GAP-15): Try manifest-based resolution first via
41
+ `clm_rust_core resolve-binary --handle runtime:clm_rust_core`.
42
+ Fall back to hardcoded path list for first-boot / no-database scenarios.
43
+ """
44
+ base = os.path.join(clm_root, "kernel", "clm_rust_core", "target")
45
+ candidates = []
46
+ for profile in ("release", "debug"):
47
+ candidate = os.path.join(base, profile, "clm_rust_core")
48
+ candidates.append(candidate)
49
+
50
+ # Find any available Rust binary to call resolve-binary
51
+ rust_bin_for_resolve = None
52
+ for path in candidates:
53
+ if os.path.exists(path):
54
+ rust_bin_for_resolve = path
55
+ break
56
+
57
+ # Try manifest-based resolution first (GAP-15)
58
+ if rust_bin_for_resolve:
59
+ try:
60
+ proc = _sp.Popen(
61
+ [rust_bin_for_resolve, "resolve-binary", "--handle", "runtime:clm_rust_core"],
62
+ stdout=_sp.PIPE, stderr=_sp.PIPE, text=True
63
+ )
64
+ stdout, _ = proc.communicate(timeout=5)
65
+ if proc.returncode == 0 and stdout.strip():
66
+ resolved = stdout.strip()
67
+ if os.path.exists(resolved):
68
+ return resolved
69
+ except Exception:
70
+ pass
71
+
72
+ # Fallback: hardcoded path list
73
+ for path in candidates:
74
+ if os.path.exists(path):
75
+ return path
76
+ return None
77
+
78
+
79
+ def _call_rust_binary(
80
+ exe: str,
81
+ args: list[str],
82
+ env: dict[str, str] | None = None,
83
+ stdin_data: str | None = None,
84
+ ) -> tuple[int, str, str]:
85
+ """Invoke the Rust binary with optional stdin and return (exit_code, stdout, stderr)."""
86
+ cmd = [exe] + args
87
+ proc = _sp.Popen(
88
+ cmd,
89
+ stdout=_sp.PIPE,
90
+ stderr=_sp.PIPE,
91
+ stdin=_sp.PIPE if stdin_data is not None else None,
92
+ text=True,
93
+ encoding="utf-8",
94
+ env=env or os.environ.copy(),
95
+ )
96
+ stdout_data, stderr_data = proc.communicate(input=stdin_data)
97
+ return proc.returncode, stdout_data, stderr_data
98
+
99
+
100
+ def run_internal_reference_test(clm_root: str, clm_file: str, module_name: str) -> int:
101
+ """
102
+ Runs an internal reference test for a specific module, simulating execution context.
103
+
104
+ Args:
105
+ clm_root (str): The root directory of the CLM workspace.
106
+ clm_file (str): The path to the CLM PCard.
107
+ module_name (str): The name of the internal module/test to run.
108
+
109
+ Returns:
110
+ int: The exit code of the test.
111
+ """
112
+ if module_name == "clm_rust_core::integration::test_mdns_peer_discovery":
113
+ _logger.warning("⚠️ STUB: mDNS peer discovery test not implemented in Python runtime; requires Rust binary (clm_rust_core)")
114
+ return 1
115
+
116
+ test_name = module_name.split("::")[-1]
117
+ overrides = {
118
+ "test_petri_net_suite": "petri_net_cases.sh",
119
+ "test_petri_binding_suite": "petri_binding_cases.sh",
120
+ "test_petri_invariants": "petri_invariant_cases.sh",
121
+ "test_monadic_pcard_resolve_handle": "test_monadic_pcards.sh",
122
+ "test_monadic_pcard_check_precondition": "test_monadic_pcards.sh",
123
+ "test_monadic_pcard_execute_payload": "test_monadic_pcards.sh",
124
+ "test_monadic_pcard_validate_postcondition": "test_monadic_pcards.sh",
125
+ "test_vcard_pre": "test_monadic_pcards.sh",
126
+ }
127
+ target_filename = overrides.get(test_name, f"{test_name}.sh")
128
+ script_rel = None
129
+ for layer in ("00_microkernel", "01_cpn_vm", "02_storage", "03_network"):
130
+ candidate = f"tests/{layer}/{target_filename}"
131
+ if os.path.exists(os.path.join(clm_root, candidate)):
132
+ script_rel = candidate
133
+ break
134
+
135
+ if not script_rel:
136
+ for root, _dirs, files in os.walk(os.path.join(clm_root, "tests")):
137
+ if target_filename in files:
138
+ script_rel = os.path.relpath(os.path.join(root, target_filename), clm_root)
139
+ break
140
+
141
+ if not script_rel:
142
+ _logger.info(f"ERROR: Unknown internal reference module '{module_name}' (script not found)")
143
+ return 1
144
+
145
+ test_func = test_name
146
+ if test_name == "test_petri_math":
147
+ test_func = "test_petri_math_suite"
148
+ elif test_name == "test_petri_invariants":
149
+ test_func = "test_petri_invariant_suite"
150
+ elif test_name == "test_vcard_pre":
151
+ test_func = "test_monadic_pcard_check_precondition"
152
+ script_path = os.path.join(clm_root, script_rel)
153
+
154
+ host_os = detect_host_os()
155
+ import platform as _plat
156
+ arch = "amd64" if _plat.machine() in ("x86_64", "AMD64") else "arm64"
157
+ script_content = f"""
158
+ export CLM_ROOT="{clm_root}"
159
+ export CLM_RUNNER="rust"
160
+ export PATH="$CLM_ROOT/.venv/Scripts:$CLM_ROOT/.venv/bin:$CLM_ROOT/kernel/bin/{host_os}-{arch}:$CLM_ROOT/kernel/scripts:$PATH"
161
+ clm_root() {{ echo "$CLM_ROOT"; }}
162
+ export -f clm_root
163
+
164
+ # Intercept exit to prevent bootstrap scripts from terminating the shell
165
+ exit() {{ return "${{1:-0}}"; }}
166
+
167
+ while IFS= read -r f; do
168
+ if [[ "$f" != *"clmEval.sh"* && "$f" != *"main.sh"* && "$f" != *"evaluator.sh"* && "$f" != *"install_vs_buildtools.sh"* && "$f" != *"patch_mcard_venv.sh"* ]]; then
169
+ source "$f" 2>/dev/null || true
170
+ fi
171
+ done < <(find "$CLM_ROOT/computable_knowledge/Concrete/layer5_system/bootstrap" -name "node_modules" -prune -o -name "*.sh" -print)
172
+
173
+ unset -f exit
174
+
175
+ source "{script_path}" && {test_func}
176
+ """
177
+ rust_exe = _resolve_rust_binary(clm_root)
178
+ if rust_exe:
179
+ import json as _json
180
+ script_input = {
181
+ "runtime": "shell",
182
+ "script": script_content,
183
+ "env": dict(os.environ),
184
+ "entry_point": "",
185
+ "cargo_test": "",
186
+ "args": [],
187
+ "clm_root": clm_root,
188
+ }
189
+ result = _call_rust_binary(rust_exe, ["exec-runtime-script"], env=None, stdin_data=_json.dumps(script_input))
190
+ if result[0] == 0:
191
+ try:
192
+ output = _json.loads(result[1])
193
+ return output.get("exit_code", 1)
194
+ except Exception:
195
+ pass
196
+ return result[0]
197
+ proc = _sp.Popen([get_bash_command(), "-c", script_content])
198
+ proc.wait()
199
+ return proc.returncode
200
+
201
+ def _extract_ctx(clm_root: str, runtime: str, script_content: str, inputs: dict[str, Any], entry_point: str, cargo_test: str, a_hash: str, c_hash: str) -> CLMContext:
202
+ return CLMContext.create_default(clm_root)
203
+
204
+ def _extract_meta(clm_root: str, runtime: str, script_content: str, inputs: dict[str, Any], entry_point: str, cargo_test: str, a_hash: str, c_hash: str) -> dict[str, Any]:
205
+ from clm.layer0.utils import clm_hash_string
206
+ return {
207
+ "operation": f"polyglot_execution_{runtime}",
208
+ "runtime": runtime,
209
+ "script_hash": clm_hash_string(script_content),
210
+ "input_place_hashes": [],
211
+ "agent_did": os.environ.get("ACTIVE_AGENT_DID", "did:clm:bootstrap"),
212
+ "sponsor_did": ""
213
+ }
214
+
215
+ @vcard_sandwich(extract_ctx_func=_extract_ctx, extract_meta_func=_extract_meta)
216
+ def _polyglot_runtime_execute(clm_root: str, runtime: str, script_content: str, inputs: dict[str, Any], entry_point: str, cargo_test: str, a_hash: str, c_hash: str) -> tuple[int, str, str]:
217
+ run_status = 0
218
+ stdout_data = ""
219
+ stderr_data = ""
220
+
221
+ env = os.environ.copy()
222
+ for k, v in inputs.items():
223
+ env[k] = str(v)
224
+
225
+ args = [str(v) for v in inputs.values()]
226
+
227
+ rust_exe = _resolve_rust_binary(clm_root)
228
+ if rust_exe and runtime in ("shell", "python", "node", "rust"):
229
+ import json as _json
230
+ script_input = {
231
+ "runtime": runtime,
232
+ "script": script_content,
233
+ "env": {k: str(v) for k, v in env.items()},
234
+ "entry_point": entry_point or "",
235
+ "cargo_test": cargo_test or "",
236
+ "args": args,
237
+ "clm_root": clm_root,
238
+ }
239
+ result = _call_rust_binary(rust_exe, ["exec-runtime-script"], env=None, stdin_data=_json.dumps(script_input))
240
+ if result[0] == 0:
241
+ try:
242
+ output = _json.loads(result[1])
243
+ return output.get("exit_code", 1), output.get("stdout", ""), output.get("stderr", "")
244
+ except Exception:
245
+ pass
246
+ return result[0], result[1], result[2]
247
+
248
+ if runtime == "shell":
249
+ body = script_content
250
+ if entry_point:
251
+ body += f"\n\nif type {entry_point} &>/dev/null; then\n {entry_point} " + " ".join([f'"{a}"' for a in args]) + "\nfi\n"
252
+
253
+ proc = _sp.Popen([get_bash_command(), "-c", body], stdout=_sp.PIPE, stderr=_sp.PIPE, text=True, encoding="utf-8", env=env)
254
+ stdout_data, stderr_data = proc.communicate()
255
+ run_status = proc.returncode
256
+ elif runtime == "python":
257
+ proc = _sp.Popen([sys.executable, "-c", script_content] + args, stdout=_sp.PIPE, stderr=_sp.PIPE, text=True, encoding="utf-8", env=env)
258
+ stdout_data, stderr_data = proc.communicate()
259
+ run_status = proc.returncode
260
+ elif runtime == "node":
261
+ proc = _sp.Popen(["node", "-e", script_content] + args, stdout=_sp.PIPE, stderr=_sp.PIPE, text=True, encoding="utf-8", env=env)
262
+ stdout_data, stderr_data = proc.communicate()
263
+ run_status = proc.returncode
264
+ elif runtime == "rust":
265
+ if cargo_test:
266
+ pwd_orig = os.getcwd()
267
+ try:
268
+ os.chdir(os.path.join(clm_root, "kernel/clm_rust_core"))
269
+ proc = _sp.Popen(["./target/release/clm_rust_core", "run-test", cargo_test], stdout=_sp.PIPE, stderr=_sp.PIPE, text=True, encoding="utf-8", env=env)
270
+ stdout_data, stderr_data = proc.communicate()
271
+ run_status = proc.returncode
272
+ except Exception:
273
+ cmd = ["./target/debug/clm_rust_core", "run-test", cargo_test]
274
+ if not os.path.exists(cmd[0]):
275
+ cmd = ["cargo", "test", "--manifest-path", "Cargo.toml", "--", cargo_test]
276
+ proc = _sp.Popen(cmd, stdout=_sp.PIPE, stderr=_sp.PIPE, text=True, encoding="utf-8", env=env)
277
+ stdout_data, stderr_data = proc.communicate()
278
+ run_status = proc.returncode
279
+ finally:
280
+ os.chdir(pwd_orig)
281
+ else:
282
+ import tempfile
283
+ temp_dir = tempfile.mkdtemp()
284
+ src_file = os.path.join(temp_dir, "main.rs")
285
+ bin_file = os.path.join(temp_dir, "app.exe" if sys.platform == "win32" else "app")
286
+ if "fn main" not in script_content:
287
+ wrapped_content = "use std::io::{self, Read};\nfn main() {\n" + script_content + "\n}\n"
288
+ else:
289
+ wrapped_content = script_content
290
+ with open(src_file, "w", encoding="utf-8") as fh:
291
+ fh.write(wrapped_content)
292
+ try:
293
+ compile_proc = _sp.Popen(["rustc", "-O", "-o", bin_file, src_file], stdout=_sp.PIPE, stderr=_sp.PIPE, text=True, encoding="utf-8")
294
+ compile_out, compile_err = compile_proc.communicate()
295
+ if compile_proc.returncode != 0:
296
+ if "linking with `link.exe` failed" in compile_err or "link: extra operand" in compile_err:
297
+ _logger.warning(f"[PAL:polyglot] ⚠️ Rust compilation skipped (linker unavailable): {compile_err.strip()}")
298
+ return 0, "", "skipped: rustc linker unavailable"
299
+ _logger.error(f"[PAL:polyglot] ❌ Rust compilation failed:\n{compile_err}")
300
+ return 1, "", compile_err
301
+ proc = _sp.Popen([bin_file] + args, stdout=_sp.PIPE, stderr=_sp.PIPE, text=True, encoding="utf-8", env=env)
302
+ stdout_data, stderr_data = proc.communicate()
303
+ run_status = proc.returncode
304
+ finally:
305
+ import shutil
306
+ shutil.rmtree(temp_dir, ignore_errors=True)
307
+
308
+ return run_status, stdout_data, stderr_data
309
+ def evaluate_polyglot(clm_root: str, clm_file: str, yaml_data: dict[str, Any], extra_args: list[str] | None) -> int:
310
+ """
311
+ Evaluates a polyglot CLM process, executing it in the specified target runtimes (e.g. Python, Rust, Node).
312
+
313
+ Args:
314
+ clm_root (str): The root directory of the CLM workspace.
315
+ clm_file (str): The path to the CLM PCard.
316
+ yaml_data (Dict[str, Any]): The parsed manifest data.
317
+ extra_args (Optional[List[str]]): Additional command-line arguments.
318
+
319
+ Returns:
320
+ int: The overall exit code from execution.
321
+ """
322
+ inner = yaml_data.get("clm", yaml_data)
323
+ concrete = inner.get("concrete_impl", {})
324
+ process = concrete.get("process", {})
325
+ entry_point = process.get("entry_point") or concrete.get("operation")
326
+
327
+ balanced_exp = inner.get("balanced_exp", {})
328
+ polyglot_cases = balanced_exp.get("polyglot_cases", [])
329
+
330
+ case_name = None
331
+ if extra_args:
332
+ for arg in extra_args:
333
+ for case in polyglot_cases:
334
+ if case.get("name") == arg:
335
+ case_name = arg
336
+ break
337
+ if case_name:
338
+ break
339
+
340
+ if not case_name and polyglot_cases:
341
+ case_name = polyglot_cases[0].get("name")
342
+
343
+ case_dict = None
344
+ for case in polyglot_cases:
345
+ if case.get("name") == case_name:
346
+ case_dict = case
347
+ break
348
+
349
+ if not case_dict:
350
+ _logger.error(f"[PAL:polyglot] ❌ No case found for name '{case_name}' in: {clm_file}")
351
+ return 1
352
+
353
+ given = case_dict.get("given", {})
354
+ runtimes = given.get("runtimes", [])
355
+ if not runtimes:
356
+ _logger.error("[PAL:polyglot] ❌ No runtimes specified in Gherkin cover")
357
+ return 1
358
+
359
+ inputs = {k: v for k, v in given.items() if k not in ("runtimes", "requires")}
360
+ a_hash, c_hash = resolve_active_hashes_py(clm_file)
361
+
362
+ env_runtime = os.environ.get("CLM_POLYGLOT_RUNTIME")
363
+ active_runtimes = [env_runtime] if env_runtime else runtimes
364
+
365
+ executed_runtimes = []
366
+ runtime_outputs = []
367
+ runtime_exit_codes = []
368
+ runtime_exec_hashes = []
369
+
370
+ for runtime in active_runtimes:
371
+ if runtime not in runtimes:
372
+ _logger.info(f"[PAL:polyglot] ⏭️ Target runtime '{runtime}' not requested in Gherkin cover: {runtimes}")
373
+ continue
374
+
375
+ if runtime not in ("shell", "python", "rust", "node", "channel", "wasm", "javascript-browser", "llm"):
376
+ if env_runtime:
377
+ _logger.error(f"[PAL:polyglot] ❌ Target runtime '{runtime}' is not supported")
378
+ return 1
379
+ else:
380
+ _logger.info(f"[PAL:polyglot] ⏭️ Skipping unsupported runtime '{runtime}' in cover")
381
+ continue
382
+
383
+ requires = given.get("requires", {})
384
+ req_guard = requires.get(runtime)
385
+ if req_guard and not check_precondition_py(req_guard):
386
+ _logger.info(f"[PAL:polyglot] ⏭️ Pruning runtime '{runtime}' (precondition not met: {req_guard})")
387
+ continue
388
+
389
+ impls = process.get("implementations", {})
390
+ rt_impl = impls.get(runtime, {})
391
+ script_content = rt_impl.get("script", "")
392
+ script_handle = rt_impl.get("script_handle", "")
393
+ cargo_test = rt_impl.get("cargo_test", "")
394
+
395
+ if not script_content:
396
+ if script_handle and script_handle.startswith("script:"):
397
+ target = "clm.layer2.storage"
398
+ mod = sys.modules.get(target)
399
+ if mod is None:
400
+ __import__(target)
401
+ mod = sys.modules.get(target)
402
+ script_content = mod.resolve_handle_content(clm_root, script_handle) if mod else ""
403
+ elif cargo_test and runtime == "rust":
404
+ script_content = "CARGO_TEST_EXECUTION"
405
+
406
+ if not script_content:
407
+ if script_handle:
408
+ print(yaml.dump({
409
+ "type": "meta/content_missing",
410
+ "handle": script_handle,
411
+ "medium_hint": "db",
412
+ "uri": f"db://content_memory/{script_handle}"
413
+ }))
414
+ return 2
415
+ else:
416
+ _logger.error(f"[PAL:polyglot] ❌ No script or cargo_test implementation found for runtime '{runtime}' in: {clm_file}")
417
+ return 1
418
+
419
+ run_status, stdout_data, stderr_data = _polyglot_runtime_execute(clm_root, runtime, script_content, inputs, entry_point, cargo_test, a_hash or "", c_hash or "")
420
+
421
+ if stderr_data:
422
+ sys.stderr.write(stderr_data)
423
+
424
+ inputs_json_str = json.dumps(inputs, indent=2)
425
+ exec_hash_input = f"inputs: {inputs_json_str} | outputs: {stdout_data} | exit_code: {run_status} | runtime_facts: {os.environ.get('CLM_RUNTIME_FACTS', '')}"
426
+ exec_hash = clm_hash_string(exec_hash_input)
427
+
428
+ executed_runtimes.append(runtime)
429
+ runtime_outputs.append(stdout_data)
430
+ runtime_exit_codes.append(run_status)
431
+ runtime_exec_hashes.append(exec_hash)
432
+
433
+ if not executed_runtimes:
434
+
435
+ _logger.error("[PAL:polyglot] ❌ No runtimes were successfully executed.")
436
+ return 1
437
+
438
+ first_out = runtime_outputs[0]
439
+ first_code = runtime_exit_codes[0]
440
+ first_norm = normalize_output_py(first_out)
441
+
442
+ parity_failed = False
443
+ mismatch_rt = ""
444
+ mismatch_out = ""
445
+
446
+ for i in range(1, len(executed_runtimes)):
447
+ r_name = executed_runtimes[i]
448
+ r_out = runtime_outputs[i]
449
+ r_code = runtime_exit_codes[i]
450
+ r_norm = normalize_output_py(r_out)
451
+
452
+ if r_code != first_code:
453
+ parity_failed = True
454
+ mismatch_rt = r_name
455
+ mismatch_out = f"Exit code mismatch: {executed_runtimes[0]}={first_code} vs {r_name}={r_code}"
456
+ break
457
+ if r_norm != first_norm:
458
+ parity_failed = True
459
+ mismatch_rt = r_name
460
+ mismatch_out = r_out
461
+ break
462
+
463
+ if parity_failed:
464
+ _logger.info("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓")
465
+ _logger.info("┃ COHOMOLOGICAL OBSTRUCTION: Cross-Runtime Parity Fail ┃")
466
+ _logger.info("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛")
467
+ _logger.info(f" Divergence detected between '{executed_runtimes[0]}' and '{mismatch_rt}'!")
468
+ _logger.info(" -------------------------------------------------")
469
+ _logger.info(f" Output of '{executed_runtimes[0]}':")
470
+ _logger.info(first_out)
471
+ _logger.info(f" Output of '{mismatch_rt}':")
472
+ _logger.info(mismatch_out)
473
+ _logger.info(" -------------------------------------------------")
474
+
475
+ import difflib
476
+ diff_lines = list(difflib.unified_diff(
477
+ first_norm.splitlines(keepends=True),
478
+ normalize_output_py(mismatch_out).splitlines(keepends=True),
479
+ fromfile=f"norm_{executed_runtimes[0]}",
480
+ tofile=f"norm_{mismatch_rt}"
481
+ ))
482
+ sys.stderr.writelines(diff_lines)
483
+ return 1
484
+
485
+ if len(executed_runtimes) > 1:
486
+ sorted_hashes = sorted(runtime_exec_hashes)
487
+ fold_input = "".join(sorted_hashes)
488
+ parity_hash = clm_hash_string(fold_input)
489
+
490
+ runtimes_str = ", ".join([f'"{r}"' for r in executed_runtimes])
491
+ extra_parity = f'parity_hash: "{parity_hash}"\nruntimes: [{runtimes_str}]'
492
+ parity_file = emit_exec_mcard_py(clm_root, "polyglot_parity", parity_hash, extra_parity, a_hash or "", c_hash or "")
493
+ if parity_file:
494
+ _logger.info(f"[PAL:polyglot] ✅ Parity verified. parity_hash = {parity_hash} ({parity_file})")
495
+
496
+ print(first_out, end="")
497
+ return first_code
498
+
499
+ def evaluate_compose(clm_root: str, clm_file: str, block: dict[str, Any], extra_args: list[str] | None) -> int:
500
+ """
501
+ Evaluates a composite operation, evaluating step by step sequentially or otherwise.
502
+
503
+ Args:
504
+ clm_root (str): The root directory of the CLM workspace.
505
+ clm_file (str): The path to the parent CLM PCard.
506
+ block (Dict[str, Any]): The process block declaring steps.
507
+ extra_args (Optional[List[str]]): Additional execution arguments.
508
+
509
+ Returns:
510
+ int: The exit code (0 if all steps succeed).
511
+ """
512
+ load_normalized_manifest_fn = get_load_manifest()
513
+ _logger.info("[PAL] Dispatching to compose runtime adapter...")
514
+ compose_mode = block.get("compose_mode", "sequential")
515
+ steps = block.get("steps", [])
516
+
517
+ if not steps:
518
+ print("[COMPOSE_FN] No steps declared — identity (no-op)")
519
+ return 0
520
+
521
+ print(f"[COMPOSE_FN] Mode: {compose_mode} | Steps: {len(steps)}")
522
+
523
+ # Lazy import evaluate_cmd dynamically to prevent circular dependencies
524
+ from clm.layer1.eval_callback import evaluate_or_fail
525
+
526
+ for i, step_ref in enumerate(steps):
527
+ step_file = resolve_clm_path(clm_root, step_ref)
528
+ if not step_file or not os.path.exists(step_file):
529
+ _logger.error(f"[COMPOSE_FN] Step {i+1}: failed to load '{step_ref}'")
530
+ return 1
531
+
532
+ try:
533
+ step_data = load_normalized_manifest_fn(clm_root, step_file)
534
+ inner_step = step_data.get("clm", step_data)
535
+ step_op = inner_step.get("concrete_impl", {}).get("operation", "unknown")
536
+ except Exception:
537
+ step_op = "unknown"
538
+
539
+ print(f"[COMPOSE_FN] Step {i+1}: {step_op} (from {step_ref})")
540
+
541
+ rc = evaluate_or_fail(clm_root, step_file, extra_args)
542
+ if rc != 0:
543
+ return rc
544
+
545
+ return 0
546
+
547
+
548
+ # ---------------------------------------------------------------------------
549
+ # Sprint 130.1 — Dynamic Triad Runner
550
+ # ---------------------------------------------------------------------------
551
+
552
+ def evaluate_triad(clm_root: str, clm_file: str, block: dict[str, Any], extra_args: list[str] | None) -> int:
553
+ """Evaluate a Model Card Triad (Abstract, Concrete, Balanced) sequentially.
554
+
555
+ 1. Loads the Abstract face (Face A) -> computes content hash
556
+ 2. Loads the Concrete face (Face C) -> computes content hash
557
+ 3. Injects A and C hashes into env vars for CelStore to capture
558
+ 4. Evaluates the Balanced face (Face B) under this enriched context
559
+ 5. Cleans up triad env vars to prevent cross-stage contamination
560
+
561
+ Uses CLMLogger with structured fields for cross-runtime traceability.
562
+ """
563
+ # Lazy import via callback registry to avoid Layer 1→3 upward dependency
564
+ from clm.layer1.eval_callback import evaluate_or_fail
565
+
566
+ stage = block.get("stage")
567
+ name = block.get("name")
568
+ operation = f"stage{stage}_{name}"
569
+
570
+ _logger.info("Triad validation starting", op=operation)
571
+
572
+ # 1. Resolve paths to A, C, and B faces
573
+ import yaml
574
+ layer = "0"
575
+ try:
576
+ with open(clm_file, "r") as f:
577
+ data = yaml.safe_load(f)
578
+ layer = str(data.get("clm", {}).get("layer", "0"))
579
+ except Exception:
580
+ # fallback to parsing path
581
+ for l in ("0", "1", "2", "3", "4"):
582
+ if f"layer{l}" in clm_file:
583
+ layer = l
584
+ break
585
+
586
+ abstract_path = resolve_clm_path(clm_root, f"computable_knowledge/Abstract/layer{layer}/stage{stage}_{name}.yaml")
587
+ concrete_path = resolve_clm_path(clm_root, f"computable_knowledge/Concrete/layer{layer}/stage{stage}_{name}.yaml")
588
+ balanced_path = resolve_clm_path(clm_root, f"computable_knowledge/Balanced/layer{layer}/stage{stage}_{name}.yaml")
589
+
590
+ if not abstract_path or not os.path.exists(abstract_path):
591
+ _logger.error(f"Triad Face A not found: stage{stage}_{name}", op=operation)
592
+ return 1
593
+ if not concrete_path or not os.path.exists(concrete_path):
594
+ _logger.error(f"Triad Face C not found: stage{stage}_{name}", op=operation)
595
+ return 1
596
+ if not balanced_path or not os.path.exists(balanced_path):
597
+ _logger.error(f"Triad Face B not found: stage{stage}_{name}", op=operation)
598
+ return 1
599
+
600
+ # 2. Evaluate Abstract and Concrete (computes hashes and registers in DB)
601
+ rc = evaluate_or_fail(clm_root, abstract_path, extra_args)
602
+ if rc != 0:
603
+ _logger.error("Triad Face A (Abstract) failed", op=operation)
604
+ return rc
605
+
606
+ rc = evaluate_or_fail(clm_root, concrete_path, extra_args)
607
+ if rc != 0:
608
+ _logger.error("Triad Face C (Concrete) failed", op=operation)
609
+ return rc
610
+
611
+ # 3. Compute hashes for Triadic Coherence
612
+ a_hash = clm_hash_file(abstract_path)
613
+ c_hash = clm_hash_file(concrete_path)
614
+
615
+ _logger.info("Triad hashes computed", op=operation, pcard_hash=a_hash)
616
+
617
+ # 4. Inject hashes into env vars for CelStore to capture during Face B
618
+ os.environ["CLM_TRIAD_ABSTRACT_HASH"] = a_hash
619
+ os.environ["CLM_TRIAD_CONCRETE_HASH"] = c_hash
620
+
621
+ # 5. Evaluate Balanced Face
622
+ rc = evaluate_or_fail(clm_root, balanced_path, extra_args)
623
+ if rc == 0:
624
+ _logger.info("Triad validation passed", op=operation)
625
+ else:
626
+ _logger.error("Triad Face B (Balanced) failed", op=operation)
627
+
628
+ # 6. Clean up triad env vars to prevent cross-stage contamination
629
+ os.environ.pop("CLM_TRIAD_ABSTRACT_HASH", None)
630
+ os.environ.pop("CLM_TRIAD_CONCRETE_HASH", None)
631
+
632
+ return rc
633
+
634
+
635
+ # ---------------------------------------------------------------------------
636
+ # Sprint 130.4 — Monadic Pipeline Chaining
637
+ # ---------------------------------------------------------------------------
638
+
639
+ def evaluate_triad_pipeline(clm_root: str, clm_file: str, block: dict[str, Any], extra_args: list[str] | None) -> int:
640
+ """Chain multiple triad stages via CelStore.bind() (monadic >>=).
641
+
642
+ Each stage's output state token becomes the next stage's precondition
643
+ input. After each stage, flush_action_log() persists the action_log
644
+ to execution_log.db, making the Flux event store durable.
645
+ """
646
+ from types import SimpleNamespace
647
+
648
+ from clm.cross_cutting.cel_store import CelStore, EnvSlice, StateSlice
649
+ from clm.layer0.utils import clm_hash_string
650
+
651
+ stages = block.get("stages", [])
652
+ if not stages:
653
+ _logger.error("No stages defined in triad_pipeline")
654
+ return 1
655
+
656
+ # Detect layer name dynamically
657
+ layer_name = "layer0"
658
+ try:
659
+ import yaml
660
+ with open(clm_file, "r") as f:
661
+ data = yaml.safe_load(f)
662
+ layer_val = data.get("clm", {}).get("layer", "0")
663
+ layer_name = f"layer{layer_val}"
664
+ except Exception:
665
+ for l in ("0", "1", "2", "3", "4"):
666
+ if f"layer{l}" in clm_file:
667
+ layer_name = f"layer{l}"
668
+ break
669
+
670
+ # Initialize monadic state with unit() — the "seed" store
671
+ pipeline_store = CelStore(
672
+ state=StateSlice(inputs={"pipeline": layer_name}),
673
+ env=EnvSlice(clm_root=clm_root, os=os),
674
+ )
675
+ pipeline_store = pipeline_store.unit({"pipeline": layer_name, "stage": 0})
676
+
677
+ overall_rc = 0
678
+ flush_ctx = SimpleNamespace(root=clm_root)
679
+
680
+ for stage_block in stages:
681
+ stage = stage_block["stage"]
682
+ name = stage_block["name"]
683
+ operation = f"stage{stage}_{name}"
684
+
685
+ _logger.info(f"Pipeline stage {stage} starting", op=operation)
686
+
687
+ def _run_stage(store: CelStore, prev_result: Any, _stage: str = stage, _name: str = name, _op: str = operation) -> tuple[CelStore, int]:
688
+ rc = evaluate_triad(clm_root, clm_file,
689
+ {"stage": _stage, "name": _name}, extra_args)
690
+ if rc != 0:
691
+ _logger.error(f"Pipeline stage {_stage} failed", op=_op)
692
+ return store, rc
693
+
694
+ # Capture this stage's output hash as the next stage's precondition
695
+ stage_output_hash = clm_hash_string(
696
+ f"{_op}:{prev_result}"
697
+ )
698
+ store.state.inputs[f"stage{_stage}_output_hash"] = stage_output_hash
699
+
700
+ # Flush action_log for this stage to execution_log.db
701
+ store.flush_action_log(flush_ctx)
702
+
703
+ _logger.info(f"Pipeline stage {_stage} completed", op=_op,
704
+ pcard_hash=stage_output_hash)
705
+ return store, 0
706
+
707
+ pipeline_store, stage_rc = pipeline_store.bind(_run_stage)
708
+
709
+ if stage_rc != 0:
710
+ overall_rc = stage_rc
711
+ break
712
+
713
+ if overall_rc == 0:
714
+ _logger.info("Layer 0 pipeline completed successfully")
715
+ else:
716
+ _logger.error(f"Layer 0 pipeline failed at stage {stage}")
717
+
718
+ return overall_rc