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,479 @@
1
+ """CLM Document Classifier & Structural Hierarchy Analysis.
2
+
3
+ Canonical Path: kernel/scripts/clm/layer2/classifier.py
4
+ Parity: Rust `src/layer2/storage.rs`, TypeScript `src/layer2/triDb.ts`
5
+
6
+ Classifies CLM documents by structure (Function vs Number), detects
7
+ their dialect (M, A, B, or C type), and provides full recursive hierarchy
8
+ introspection across nested CLM composition trees.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ from dataclasses import dataclass, field
15
+ from enum import Enum
16
+ from typing import Any
17
+
18
+ import yaml
19
+
20
+
21
+ def classify_clm(clm_data: Any) -> str:
22
+ """Classifies a CLM document structure to determine its high-level type.
23
+
24
+ Args:
25
+ clm_data (Any): Parsed YAML or JSON dict representing the CLM.
26
+
27
+ Returns:
28
+ str: "Function" if it has abstract_spec and concrete_impl, otherwise "Number".
29
+ """
30
+ if not isinstance(clm_data, dict):
31
+ return "Number"
32
+ inner = clm_data.get("clm", clm_data)
33
+ if not isinstance(inner, dict):
34
+ return "Number"
35
+ if "abstract_spec" in inner and "concrete_impl" in inner:
36
+ return "Function"
37
+ return "Number"
38
+
39
+
40
+ def detect_dialect(clm_data: Any, classification: str) -> str:
41
+ """Detects the dialect (M, A, B, or C type) of a CLM Function.
42
+
43
+ Args:
44
+ clm_data (Any): Parsed YAML/JSON.
45
+ classification (str): The result of classify_clm.
46
+
47
+ Returns:
48
+ str: The dialect identifier string or 'UNKNOWN'.
49
+ """
50
+ if classification != "Function":
51
+ return "UNKNOWN"
52
+ inner = clm_data.get("clm", clm_data)
53
+ concrete = inner.get("concrete_impl", {})
54
+ if not isinstance(concrete, dict):
55
+ concrete = inner.get("concrete", {})
56
+ if not isinstance(concrete, dict):
57
+ return "UNKNOWN"
58
+
59
+ process = concrete.get("process", {})
60
+ process_type = None
61
+ if isinstance(process, dict):
62
+ process_type = process.get("type")
63
+ elif isinstance(process, list) and len(process) > 0 and isinstance(process[0], dict):
64
+ process_type = process[0].get("type")
65
+
66
+ disp = None
67
+ exec_block = concrete.get("process_behavior_exec", {})
68
+ if isinstance(exec_block, dict):
69
+ disp = exec_block.get("dispatcher")
70
+
71
+ rt = concrete.get("runtime")
72
+ op = concrete.get("operation", "invoke")
73
+
74
+ if process_type:
75
+ return f"M:{process_type}"
76
+ elif disp:
77
+ return f"A:{disp}"
78
+ elif rt:
79
+ return f"B:{rt}:{op}"
80
+ else:
81
+ c_rt = concrete.get("runtime")
82
+ c_op = concrete.get("operation", "invoke")
83
+ if c_rt:
84
+ return f"C:{c_rt}:{c_op}"
85
+ return "UNKNOWN"
86
+
87
+
88
+ # ─────────────────────────────────────────────────────────────────────────────
89
+ # Recursive CLM Hierarchy Introspection
90
+ # ─────────────────────────────────────────────────────────────────────────────
91
+
92
+
93
+ class CLMType(Enum):
94
+ """Type classification for CLM nodes."""
95
+
96
+ CHAPTER = "chapter" # Has chapter metadata
97
+ PCARD = "pcard" # Pure PCard (no chapter wrapper)
98
+ COMPOSED = "composed" # Sequential composition (then)
99
+ PARALLEL = "parallel" # Tensor product (and_also)
100
+ REFERENCE = "reference" # Reference to another CLM by hash
101
+ UNKNOWN = "unknown"
102
+
103
+
104
+ class CompositionType(Enum):
105
+ """Type of composition between CLMs."""
106
+
107
+ SEQUENTIAL = "sequential" # A ; B (Coend composition)
108
+ PARALLEL = "parallel" # A ⊗ B (Tensor product)
109
+ NONE = "none" # Leaf node
110
+
111
+
112
+ @dataclass
113
+ class CLMNode:
114
+ """Represents a node in the CLM hierarchy tree.
115
+
116
+ Each node corresponds to a CLM (PCard) in the composition graph.
117
+ """
118
+
119
+ # Content hash (MCard identity)
120
+ hash: str
121
+
122
+ # Node classification
123
+ clm_type: CLMType
124
+
125
+ # CLM dimensions
126
+ abstract: dict[str, Any] | None = None
127
+ concrete: dict[str, Any] | None = None
128
+ balanced: dict[str, Any] | None = None
129
+
130
+ # Chapter metadata (if present)
131
+ chapter: dict[str, Any] | None = None
132
+
133
+ # Composition metadata
134
+ composition_type: CompositionType = CompositionType.NONE
135
+
136
+ # Children nodes (for composed CLMs)
137
+ children: list[CLMNode] = field(default_factory=list)
138
+
139
+ # Parent hash (for traversal)
140
+ parent_hash: str | None = None
141
+
142
+ # Depth in the hierarchy (root = 0)
143
+ depth: int = 0
144
+
145
+ # Declared runtime
146
+ runtime: str = "unknown"
147
+
148
+
149
+ @dataclass
150
+ class CLMIntrospectionResult:
151
+ """Result of CLM hierarchy introspection.
152
+
153
+ Contains the full tree structure and analysis metadata.
154
+ """
155
+
156
+ # Root node of the hierarchy
157
+ root: CLMNode
158
+
159
+ # All nodes indexed by hash
160
+ nodes: dict[str, CLMNode]
161
+
162
+ # Maximum depth of the hierarchy
163
+ max_depth: int
164
+
165
+ # Total node count
166
+ node_count: int
167
+
168
+ # Detected circular dependencies
169
+ cycles: list[list[str]]
170
+
171
+ # Runtime distribution
172
+ runtimes: dict[str, int]
173
+
174
+ # Composition statistics
175
+ sequential_count: int
176
+ parallel_count: int
177
+ leaf_count: int
178
+
179
+
180
+ class CLMIntrospector:
181
+ """Recursive CLM Hierarchy Introspector.
182
+
183
+ Analyzes CLM composition graphs, extracting structure and metadata
184
+ from arbitrarily nested CLM hierarchies.
185
+
186
+ Usage:
187
+ introspector = CLMIntrospector(collection)
188
+ result = await introspector.introspect("root_pcard_hash")
189
+ """
190
+
191
+ def __init__(self, collection: Any = None, max_depth: int = 100):
192
+ """Initialize the introspector.
193
+
194
+ Args:
195
+ collection: MCard collection for content retrieval.
196
+ max_depth: Maximum recursion depth to prevent infinite loops.
197
+ """
198
+ self.logger = logging.getLogger(__name__)
199
+ self._collection = collection
200
+ self._max_depth = max_depth
201
+ self._visited: set[str] = set()
202
+ self._nodes: dict[str, CLMNode] = {}
203
+ self._cycles: list[list[str]] = []
204
+
205
+ async def introspect(self, pcard_hash: str) -> CLMIntrospectionResult:
206
+ """Introspect a CLM hierarchy starting from the given root.
207
+
208
+ Args:
209
+ pcard_hash: Hash of the root PCard.
210
+
211
+ Returns:
212
+ CLMIntrospectionResult containing the full hierarchy analysis.
213
+ """
214
+ self._visited = set()
215
+ self._nodes = {}
216
+ self._cycles = []
217
+
218
+ root = await self._build_tree(pcard_hash, depth=0, path=[])
219
+
220
+ if root is None:
221
+ root = CLMNode(hash=pcard_hash, clm_type=CLMType.UNKNOWN)
222
+
223
+ # Calculate statistics
224
+ max_depth = max((n.depth for n in self._nodes.values()), default=0)
225
+ runtimes = self._count_runtimes()
226
+ seq_count, par_count, leaf_count = self._count_compositions()
227
+
228
+ return CLMIntrospectionResult(
229
+ root=root,
230
+ nodes=self._nodes,
231
+ max_depth=max_depth,
232
+ node_count=len(self._nodes),
233
+ cycles=self._cycles,
234
+ runtimes=runtimes,
235
+ sequential_count=seq_count,
236
+ parallel_count=par_count,
237
+ leaf_count=leaf_count,
238
+ )
239
+
240
+ async def _build_tree(
241
+ self,
242
+ pcard_hash: str,
243
+ depth: int,
244
+ path: list[str],
245
+ parent_hash: str | None = None,
246
+ ) -> CLMNode | None:
247
+ """Recursively build the CLM tree from a PCard hash."""
248
+ # Check for cycles
249
+ if pcard_hash in path:
250
+ cycle = path[path.index(pcard_hash) :] + [pcard_hash]
251
+ self._cycles.append(cycle)
252
+ self.logger.warning(f"Circular dependency detected: {' -> '.join(cycle)}")
253
+ return None
254
+
255
+ # Check max depth
256
+ if depth > self._max_depth:
257
+ self.logger.warning(f"Max depth {self._max_depth} exceeded at {pcard_hash}")
258
+ return None
259
+
260
+ # Check if already processed
261
+ if pcard_hash in self._nodes:
262
+ return self._nodes[pcard_hash]
263
+
264
+ # Get PCard content
265
+ clm_data = await self._get_clm_data(pcard_hash)
266
+ if clm_data is None:
267
+ return None
268
+
269
+ # Create node
270
+ node = self._create_node(pcard_hash, clm_data, depth, parent_hash)
271
+ self._nodes[pcard_hash] = node
272
+
273
+ # Process children based on composition type
274
+ new_path = path + [pcard_hash]
275
+
276
+ if node.composition_type == CompositionType.SEQUENTIAL:
277
+ # Sequential composition: process steps
278
+ steps = clm_data.get("clm", {}).get("abstract", {}).get("steps", [])
279
+ for step_hash in steps:
280
+ child = await self._build_tree(
281
+ step_hash, depth + 1, new_path, pcard_hash
282
+ )
283
+ if child:
284
+ node.children.append(child)
285
+
286
+ elif node.composition_type == CompositionType.PARALLEL:
287
+ # Parallel composition: process components
288
+ left = clm_data.get("clm", {}).get("abstract", {}).get("left")
289
+ right = clm_data.get("clm", {}).get("abstract", {}).get("right")
290
+
291
+ if left:
292
+ child = await self._build_tree(left, depth + 1, new_path, pcard_hash)
293
+ if child:
294
+ node.children.append(child)
295
+ if right:
296
+ child = await self._build_tree(right, depth + 1, new_path, pcard_hash)
297
+ if child:
298
+ node.children.append(child)
299
+
300
+ # Check for recursive runtime references
301
+ runtime = node.runtime
302
+ if runtime.endswith(".yaml") or runtime.endswith(".clm"):
303
+ pass
304
+
305
+ return node
306
+
307
+ async def _get_clm_data(self, pcard_hash: str) -> dict[str, Any] | None:
308
+ """Get CLM data from a PCard hash."""
309
+ if self._collection is None:
310
+ return None
311
+
312
+ try:
313
+ pcard = self._collection.get(pcard_hash)
314
+ if pcard is None:
315
+ return None
316
+
317
+ content = (
318
+ pcard.content
319
+ if isinstance(pcard.content, str)
320
+ else pcard.content.decode("utf-8")
321
+ )
322
+ return yaml.safe_load(content)
323
+
324
+ except (
325
+ yaml.YAMLError,
326
+ ValueError,
327
+ TypeError,
328
+ UnicodeDecodeError,
329
+ AttributeError,
330
+ ) as e:
331
+ self.logger.error(
332
+ f"Failed to get CLM data for {pcard_hash}: {e}", exc_info=True
333
+ )
334
+ return None
335
+
336
+ def _create_node(
337
+ self,
338
+ pcard_hash: str,
339
+ clm_data: dict[str, Any],
340
+ depth: int,
341
+ parent_hash: str | None,
342
+ ) -> CLMNode:
343
+ """Create a CLMNode from parsed CLM data."""
344
+ # Determine CLM type
345
+ clm_type = self._classify_clm(clm_data)
346
+
347
+ # Extract CLM dimensions
348
+ clm_section = clm_data.get("clm", {})
349
+ abstract = clm_section.get("abstract") or clm_data.get("abstract")
350
+ concrete = clm_section.get("concrete") or clm_data.get("concrete")
351
+ balanced = clm_section.get("balanced") or clm_data.get("balanced")
352
+
353
+ # Extract chapter
354
+ chapter = clm_data.get("chapter")
355
+
356
+ # Determine composition type
357
+ composition_type = CompositionType.NONE
358
+ abstract_type = (abstract or {}).get("type", "")
359
+
360
+ if abstract_type == "sequential_composition":
361
+ composition_type = CompositionType.SEQUENTIAL
362
+ elif abstract_type == "tensor_product":
363
+ composition_type = CompositionType.PARALLEL
364
+
365
+ # Extract runtime
366
+ runtime = (
367
+ (concrete or {}).get("runtime")
368
+ or clm_section.get("concrete", {}).get("runtime")
369
+ or "unknown"
370
+ )
371
+
372
+ return CLMNode(
373
+ hash=pcard_hash,
374
+ clm_type=clm_type,
375
+ abstract=abstract,
376
+ concrete=concrete,
377
+ balanced=balanced,
378
+ chapter=chapter,
379
+ composition_type=composition_type,
380
+ children=[],
381
+ parent_hash=parent_hash,
382
+ depth=depth,
383
+ runtime=runtime,
384
+ )
385
+
386
+ def _classify_clm(self, clm_data: dict[str, Any]) -> CLMType:
387
+ """Classify the CLM by its structure."""
388
+ if "chapter" in clm_data:
389
+ return CLMType.CHAPTER
390
+
391
+ abstract = clm_data.get("clm", {}).get("abstract") or clm_data.get(
392
+ "abstract", {}
393
+ )
394
+ abstract_type = abstract.get("type", "") if isinstance(abstract, dict) else ""
395
+
396
+ if abstract_type == "sequential_composition":
397
+ return CLMType.COMPOSED
398
+ elif abstract_type == "tensor_product":
399
+ return CLMType.PARALLEL
400
+ elif "clm" in clm_data or "abstract" in clm_data:
401
+ return CLMType.PCARD
402
+
403
+ return CLMType.UNKNOWN
404
+
405
+ def _count_runtimes(self) -> dict[str, int]:
406
+ """Count nodes by runtime."""
407
+ runtimes: dict[str, int] = {}
408
+ for node in self._nodes.values():
409
+ rt = node.runtime
410
+ runtimes[rt] = runtimes.get(rt, 0) + 1
411
+ return runtimes
412
+
413
+ def _count_compositions(self) -> tuple[int, int, int]:
414
+ """Count sequential, parallel, and leaf compositions."""
415
+ seq = 0
416
+ par = 0
417
+ leaf = 0
418
+
419
+ for node in self._nodes.values():
420
+ if node.composition_type == CompositionType.SEQUENTIAL:
421
+ seq += 1
422
+ elif node.composition_type == CompositionType.PARALLEL:
423
+ par += 1
424
+ else:
425
+ leaf += 1
426
+
427
+ return seq, par, leaf
428
+
429
+ def to_tree_string(self, result: CLMIntrospectionResult) -> str:
430
+ """Generate a tree visualization of the CLM hierarchy.
431
+
432
+ Returns:
433
+ ASCII tree representation.
434
+ """
435
+ lines: list[str] = []
436
+ self._tree_to_string(result.root, lines, "", True)
437
+ return "\n".join(lines)
438
+
439
+ def _tree_to_string(
440
+ self, node: CLMNode, lines: list[str], prefix: str, is_last: bool
441
+ ) -> None:
442
+ """Recursive helper for tree visualization."""
443
+ connector = "└── " if is_last else "├── "
444
+
445
+ # Build node label
446
+ label_parts = [node.hash[:8]]
447
+ if node.chapter:
448
+ title = node.chapter.get("title", "")
449
+ if title:
450
+ label_parts.append(f"({title})")
451
+ label_parts.append(f"[{node.clm_type.value}]")
452
+ if node.runtime != "unknown":
453
+ label_parts.append(f"@{node.runtime}")
454
+
455
+ lines.append(prefix + connector + " ".join(label_parts))
456
+
457
+ # Process children
458
+ new_prefix = prefix + (" " if is_last else "│ ")
459
+ for i, child in enumerate(node.children):
460
+ self._tree_to_string(child, lines, new_prefix, i == len(node.children) - 1)
461
+
462
+
463
+ __all__ = [
464
+ "classify_clm",
465
+ "detect_dialect",
466
+ "CLMType",
467
+ "CompositionType",
468
+ "CLMNode",
469
+ "CLMIntrospectionResult",
470
+ "CLMIntrospector",
471
+ ]
472
+
473
+ # Register classifier hooks with cross_cutting (Dependency Inversion)
474
+ try:
475
+ from clm.cross_cutting.pcard import register_classifier_hook
476
+
477
+ register_classifier_hook(classify_clm, detect_dialect)
478
+ except ImportError:
479
+ pass
@@ -0,0 +1 @@
1
+