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/layer2/lineage.py ADDED
@@ -0,0 +1,322 @@
1
+ """Merkle Lineage Graph Engine (Sprint 395 / DEL-395-03).
2
+
3
+ Tracks causal ancestry and Merkle provenance across multi-step Action workflows:
4
+ (Parent MCards, Action Hash, VCard Receipt) ──> Output MCard
5
+
6
+ Guarantees:
7
+ - AC-395-03-01: Every output MCard embeds immutable Merkle hashes of ancestor cards and actions.
8
+ - AC-395-03-02: get_mcard_lineage() reconstructs complete provenance tree in <5ms.
9
+ - AC-395-03-03: Visual export produces valid Mermaid flowchart and Graphviz DOT markdown.
10
+
11
+ Layer 2 module (can import Layer 0, Layer 1, Layer 2).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import datetime
17
+ import time
18
+ from dataclasses import dataclass, field
19
+ from typing import Any
20
+
21
+ from clm.cross_cutting.domain_models import MCard
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class LineageNode:
26
+ """A node in the Merkle lineage graph."""
27
+
28
+ id: str
29
+ type: str # "mcard", "action", "receipt"
30
+ label: str
31
+ hash: str
32
+ metadata: dict[str, Any] = field(default_factory=dict)
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class LineageEdge:
37
+ """A causal directed edge in the lineage graph."""
38
+
39
+ source: str
40
+ target: str
41
+ relation: str = "derives" # "derives", "transformed_by", "certified_by"
42
+
43
+
44
+ @dataclass
45
+ class LineageDAG:
46
+ """Reconstructed directed acyclic graph of MCard provenance."""
47
+
48
+ target_uri: str
49
+ nodes: dict[str, LineageNode] = field(default_factory=dict)
50
+ edges: list[LineageEdge] = field(default_factory=list)
51
+ reconstruction_time_ms: float = 0.0
52
+
53
+ def add_node(self, node: LineageNode) -> None:
54
+ self.nodes[node.id] = node
55
+
56
+ def add_edge(self, edge: LineageEdge) -> None:
57
+ self.edges.append(edge)
58
+
59
+ def get_ancestors(self, uri: str) -> list[str]:
60
+ """Compute all ancestor URIs that contributed to the given node."""
61
+ parents_map: dict[str, list[str]] = {}
62
+ for e in self.edges:
63
+ parents_map.setdefault(e.target, []).append(e.source)
64
+
65
+ visited: set[str] = set()
66
+ queue = list(parents_map.get(uri, []))
67
+ while queue:
68
+ curr = queue.pop(0)
69
+ if curr not in visited:
70
+ visited.add(curr)
71
+ queue.extend(parents_map.get(curr, []))
72
+ return sorted(list(visited))
73
+
74
+ def get_root_inputs(self) -> list[str]:
75
+ """Return all root inputs (nodes with no incoming causal edges)."""
76
+ has_incoming = {e.target for e in self.edges}
77
+ return [nid for nid, node in self.nodes.items() if nid not in has_incoming and node.type == "mcard"]
78
+
79
+ def to_mermaid(self) -> str:
80
+ """Export lineage DAG to valid Mermaid flowchart markdown."""
81
+ lines = ["flowchart TD"]
82
+
83
+ # Define node styles
84
+ for nid, node in self.nodes.items():
85
+ safe_id = nid.replace(":", "_").replace("/", "_").replace(".", "_").replace("-", "_")
86
+ short_hash = node.hash[:8] if node.hash else ""
87
+ if node.type == "action":
88
+ label = f"⚡ Action: {node.label}\\n[{short_hash}]"
89
+ lines.append(f' {safe_id}["{label}"]:::actionClass')
90
+ elif node.type == "receipt":
91
+ label = f"📜 Receipt: {node.label}\\n[{short_hash}]"
92
+ lines.append(f' {safe_id}["{label}"]:::receiptClass')
93
+ else:
94
+ label = f"📦 {node.label}\\n[{short_hash}]"
95
+ lines.append(f' {safe_id}["{label}"]:::mcardClass')
96
+
97
+ # Define edges
98
+ for e in self.edges:
99
+ src = e.source.replace(":", "_").replace("/", "_").replace(".", "_").replace("-", "_")
100
+ tgt = e.target.replace(":", "_").replace("/", "_").replace(".", "_").replace("-", "_")
101
+ if e.relation == "transformed_by":
102
+ lines.append(f" {src} -->|transformed_by| {tgt}")
103
+ elif e.relation == "certified_by":
104
+ lines.append(f" {tgt} -.->|certified_by| {src}")
105
+ else:
106
+ lines.append(f" {src} -->|derives| {tgt}")
107
+
108
+ # Styles
109
+ lines.append(" classDef mcardClass fill:#e3f2fd,stroke:#1565c0,stroke-width:2px;")
110
+ lines.append(" classDef actionClass fill:#fff3e0,stroke:#e65100,stroke-width:2px;")
111
+ lines.append(" classDef receiptClass fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px;")
112
+
113
+ return "\n".join(lines)
114
+
115
+ def to_dot(self) -> str:
116
+ """Export lineage DAG to Graphviz DOT format."""
117
+ lines = ['digraph LineageDAG {', ' rankdir=TB;', ' node [shape=box, style=rounded, fontname="Helvetica"];']
118
+
119
+ for nid, node in self.nodes.items():
120
+ short_hash = node.hash[:8] if node.hash else ""
121
+ lines.append(f' "{nid}" [label="{node.label}\\n({short_hash})"];')
122
+
123
+ for e in self.edges:
124
+ lines.append(f' "{e.source}" -> "{e.target}" [label="{e.relation}"];')
125
+
126
+ lines.append("}")
127
+ return "\n".join(lines)
128
+
129
+
130
+ @dataclass
131
+ class LineageRecord:
132
+ """Immutable causal record of a single step derivation."""
133
+
134
+ output_uri: str
135
+ output_hash: str
136
+ parent_uris: list[str]
137
+ parent_hashes: list[str]
138
+ action_name: str
139
+ action_hash: str
140
+ receipt_uri: str | None
141
+ receipt_hash: str | None
142
+ step_id: str
143
+ timestamp: str = field(default_factory=lambda: datetime.datetime.now(datetime.timezone.utc).isoformat())
144
+
145
+
146
+ class LineageRegistry:
147
+ """In-memory and persistent causal registry for MCard provenance."""
148
+
149
+ def __init__(self):
150
+ self._records: dict[str, LineageRecord] = {} # output_uri -> record
151
+ self._hash_to_uri: dict[str, str] = {} # output_hash -> uri
152
+ self._card_meta: dict[str, dict[str, Any]] = {}
153
+
154
+ def record_step_lineage(
155
+ self,
156
+ step_id: str,
157
+ action_name: str,
158
+ action_hash: str,
159
+ parent_cards: list[MCard] | dict[str, MCard],
160
+ output_cards: list[MCard] | dict[str, MCard],
161
+ receipt: MCard | None = None,
162
+ ) -> list[LineageRecord]:
163
+ """Record causal derivation edges for all outputs produced in a step."""
164
+ now = datetime.datetime.now(datetime.timezone.utc).isoformat()
165
+
166
+ parents = list(parent_cards.values()) if isinstance(parent_cards, dict) else list(parent_cards)
167
+ outputs = list(output_cards.values()) if isinstance(output_cards, dict) else list(output_cards)
168
+
169
+ def _extract_hash(card: Any) -> str:
170
+ if card is None:
171
+ return ""
172
+ h = getattr(card, "hash", None) or getattr(card, "content_hash", None)
173
+ return str(h) if h else ""
174
+
175
+ parent_uris = [p.uri for p in parents if p and getattr(p, "uri", None)]
176
+ parent_hashes = [_extract_hash(p) for p in parents if p]
177
+
178
+ receipt_uri = receipt.uri if receipt and getattr(receipt, "uri", None) else None
179
+ receipt_hash = _extract_hash(receipt) if receipt else None
180
+
181
+ records: list[LineageRecord] = []
182
+ for out in outputs:
183
+ out_hash = _extract_hash(out)
184
+ out_uri = out.uri if getattr(out, "uri", None) else f"mcard://outputs/{out_hash[:16]}"
185
+
186
+ # Embed Merkle lineage into MCard metadata (AC-395-03-01)
187
+ lineage_block = {
188
+ "parents": parent_uris,
189
+ "parent_hashes": parent_hashes,
190
+ "action": {
191
+ "name": action_name,
192
+ "hash": action_hash,
193
+ },
194
+ "receipt": {
195
+ "uri": receipt_uri,
196
+ "hash": receipt_hash,
197
+ },
198
+ "step_id": step_id,
199
+ "timestamp": now,
200
+ }
201
+ if out.metadata is None:
202
+ out.metadata = {}
203
+ out.metadata["lineage"] = lineage_block
204
+
205
+ rec = LineageRecord(
206
+ output_uri=out_uri,
207
+ output_hash=out_hash,
208
+ parent_uris=parent_uris,
209
+ parent_hashes=parent_hashes,
210
+ action_name=action_name,
211
+ action_hash=action_hash,
212
+ receipt_uri=receipt_uri,
213
+ receipt_hash=receipt_hash,
214
+ step_id=step_id,
215
+ timestamp=now,
216
+ )
217
+ self._records[out_uri] = rec
218
+ norm_uri = self._normalize_uri(out_uri)
219
+ if norm_uri != out_uri:
220
+ self._records[norm_uri] = rec
221
+ if out_hash:
222
+ self._hash_to_uri[out_hash] = out_uri
223
+ records.append(rec)
224
+
225
+ # Record parents metadata
226
+ for p in parents:
227
+ if p.uri and p.uri not in self._card_meta:
228
+ self._card_meta[p.uri] = {
229
+ "label": p.uri.split("/")[-1],
230
+ "hash": _extract_hash(p),
231
+ "type": "mcard",
232
+ }
233
+ self._card_meta[self._normalize_uri(p.uri)] = self._card_meta[p.uri]
234
+ self._card_meta[out_uri] = {
235
+ "label": out_uri.split("/")[-1],
236
+ "hash": out_hash,
237
+ "type": "mcard",
238
+ }
239
+ self._card_meta[norm_uri] = self._card_meta[out_uri]
240
+
241
+ return records
242
+
243
+ @staticmethod
244
+ def _normalize_uri(uri: str) -> str:
245
+ if not uri:
246
+ return ""
247
+ if uri.startswith("mcard://"):
248
+ return f"mcard:{uri[8:].lstrip('/')}"
249
+ if uri.startswith("mcard:"):
250
+ return f"mcard://{uri[6:].lstrip('/')}"
251
+ return f"mcard:{uri.lstrip('/')}"
252
+
253
+ def get_mcard_lineage(self, mcard_uri_or_hash: str) -> LineageDAG:
254
+ """Reconstruct complete provenance tree in <5ms (AC-395-03-02)."""
255
+ t_start = time.perf_counter()
256
+
257
+ target_uri = self._hash_to_uri.get(mcard_uri_or_hash, mcard_uri_or_hash)
258
+ dag = LineageDAG(target_uri=target_uri)
259
+
260
+ visited: set[str] = set()
261
+ queue = [target_uri]
262
+
263
+ while queue:
264
+ curr_uri = queue.pop(0)
265
+ if curr_uri in visited:
266
+ continue
267
+ visited.add(curr_uri)
268
+
269
+ # Node for this card
270
+ meta = self._card_meta.get(curr_uri) or self._card_meta.get(self._normalize_uri(curr_uri), {})
271
+ node_label = meta.get("label", curr_uri.split("/")[-1])
272
+ node_hash = meta.get("hash", "")
273
+ dag.add_node(LineageNode(id=curr_uri, type="mcard", label=node_label, hash=node_hash))
274
+
275
+ # Find if this card has derivation records
276
+ rec = self._records.get(curr_uri) or self._records.get(self._normalize_uri(curr_uri))
277
+ if rec:
278
+ # Add Action node and edges
279
+ action_id = f"action:{rec.action_name}:{rec.step_id}"
280
+ dag.add_node(LineageNode(id=action_id, type="action", label=rec.action_name, hash=rec.action_hash))
281
+
282
+ # Edges: Parents -> Action
283
+ for p_uri in rec.parent_uris:
284
+ dag.add_edge(LineageEdge(source=p_uri, target=action_id, relation="derives"))
285
+ if p_uri not in visited:
286
+ queue.append(p_uri)
287
+
288
+ # Edge: Action -> Output
289
+ dag.add_edge(LineageEdge(source=action_id, target=curr_uri, relation="transformed_by"))
290
+
291
+ # Receipt node and edge if present
292
+ if rec.receipt_uri:
293
+ dag.add_node(LineageNode(
294
+ id=rec.receipt_uri,
295
+ type="receipt",
296
+ label="VCardReceipt",
297
+ hash=rec.receipt_hash or "",
298
+ ))
299
+ dag.add_edge(LineageEdge(source=rec.receipt_uri, target=curr_uri, relation="certified_by"))
300
+
301
+ t_elapsed = (time.perf_counter() - t_start) * 1000.0
302
+ dag.reconstruction_time_ms = round(t_elapsed, 3)
303
+ return dag
304
+
305
+ def reset(self) -> None:
306
+ """Reset the registry state."""
307
+ self._records.clear()
308
+ self._hash_to_uri.clear()
309
+ self._card_meta.clear()
310
+
311
+
312
+ # Global default registry
313
+ _default_lineage_registry = LineageRegistry()
314
+
315
+
316
+ def get_default_lineage_registry() -> LineageRegistry:
317
+ return _default_lineage_registry
318
+
319
+
320
+ def get_mcard_lineage(mcard_uri_or_hash: str) -> LineageDAG:
321
+ """Public helper to query MCard provenance using the default registry."""
322
+ return _default_lineage_registry.get_mcard_lineage(mcard_uri_or_hash)
clm/layer2/mcard_fs.py ADDED
@@ -0,0 +1,262 @@
1
+ """MCard Universal Virtual Filesystem Engine (Sprint 191/193/383).
2
+
3
+ Python parity with Rust `clm_rust_core::mcard_fs::MCardFileSystem`.
4
+ Relocated to Layer 2 (INV-00, INV-09, DEL-383-05).
5
+ Enforces:
6
+ 1. Zero-FS Hermeticity (INV-09): Works with in-memory SQLite without host disk I/O.
7
+ 2. Handle Registry Singularity (INV-04) & Tri-Database Separation (INV-05):
8
+ Wraps canonical `card`, `handle_registry`, and `handle_history` tables.
9
+ 3. Content-Addressable Identity (INV-02): BLAKE3 verification on all reads.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import datetime
15
+ import json
16
+ import sqlite3
17
+ from typing import Any
18
+
19
+ from pydantic import BaseModel, ConfigDict
20
+
21
+ from clm.cross_cutting.domain_models import MCard
22
+ from clm.cross_cutting.domain_types import AgentDid, ContentHash, make_content_hash
23
+
24
+
25
+ class MCardStat(BaseModel):
26
+ """Metadata stat for a virtual MCard file."""
27
+
28
+ model_config = ConfigDict(frozen=True)
29
+
30
+ uri: str
31
+ hash: ContentHash
32
+ size_bytes: int
33
+ created_at: str
34
+ updated_at: str
35
+
36
+
37
+ class MCardFileSystem:
38
+ """Universal immutable filesystem engine mapping URIs to content-addressed MCards."""
39
+
40
+ def __init__(self, conn: sqlite3.Connection | None = None, context_prefix: str = "") -> None:
41
+ self.conn = conn or sqlite3.connect(":memory:")
42
+ self.context_prefix = context_prefix.strip("/")
43
+ self._init_schema()
44
+
45
+ def with_prefix(self, prefix: str) -> MCardFileSystem:
46
+ """Returns a new MCardFileSystem handle isolated to the given context prefix."""
47
+ return MCardFileSystem(conn=self.conn, context_prefix=prefix)
48
+
49
+ def canonicalize_uri(self, path: str) -> str:
50
+ """Resolves canonical URI with context prefix scoping (DOD-191-04)."""
51
+ clean = path.lstrip("/")
52
+ if clean.startswith("mcard:"):
53
+ rest_clean = clean[len("mcard:"):].lstrip("/")
54
+ if not self.context_prefix or rest_clean.startswith(self.context_prefix):
55
+ return f"mcard:{rest_clean}"
56
+ else:
57
+ return f"mcard:{self.context_prefix}/{rest_clean}"
58
+ elif not self.context_prefix:
59
+ return f"mcard:{clean}"
60
+ else:
61
+ return f"mcard:{self.context_prefix}/{clean}"
62
+
63
+ def _init_schema(self) -> None:
64
+ with self.conn:
65
+ self.conn.executescript(
66
+ """
67
+ CREATE TABLE IF NOT EXISTS card (
68
+ hash TEXT PRIMARY KEY,
69
+ content TEXT NOT NULL,
70
+ g_time TEXT
71
+ );
72
+
73
+ CREATE TABLE IF NOT EXISTS handle_registry (
74
+ handle TEXT PRIMARY KEY,
75
+ current_hash TEXT NOT NULL,
76
+ created_at TEXT NOT NULL,
77
+ updated_at TEXT NOT NULL
78
+ );
79
+
80
+ CREATE TABLE IF NOT EXISTS handle_history (
81
+ handle TEXT NOT NULL,
82
+ previous_hash TEXT NOT NULL,
83
+ changed_at TEXT NOT NULL
84
+ );
85
+
86
+ CREATE INDEX IF NOT EXISTS idx_handle_reg_prefix ON handle_registry(handle);
87
+ """
88
+ )
89
+
90
+ def write_file(
91
+ self,
92
+ uri: str,
93
+ payload: Any,
94
+ author: AgentDid | str,
95
+ metadata: dict[str, Any] | None = None,
96
+ ) -> MCard:
97
+ """Writes an MCard into the filesystem and updates the handle registry."""
98
+ canonical_uri = self.canonicalize_uri(uri)
99
+ now = datetime.datetime.now(datetime.timezone.utc).isoformat()
100
+
101
+ def _do_write() -> MCard:
102
+ # 1. Determine existing handle state
103
+ cur = self.conn.execute(
104
+ "SELECT current_hash, created_at FROM handle_registry WHERE handle = ?",
105
+ (canonical_uri,),
106
+ )
107
+ existing = cur.fetchone()
108
+
109
+ if existing:
110
+ prev_hash, created_at = existing
111
+ hist_cur = self.conn.execute(
112
+ "SELECT count(*) FROM handle_history WHERE handle = ?", (canonical_uri,)
113
+ )
114
+ count = hist_cur.fetchone()[0]
115
+ seq = count + 2
116
+ else:
117
+ prev_hash = None
118
+ created_at = now
119
+ seq = 1
120
+
121
+ # 2. Construct canonical MCard
122
+ mcard = MCard.create(
123
+ uri=canonical_uri,
124
+ payload=payload,
125
+ author=author,
126
+ sequence=seq,
127
+ metadata=metadata,
128
+ )
129
+ serialized = json.dumps(mcard.model_dump(), sort_keys=True)
130
+ hash_str = str(mcard.hash)
131
+
132
+ # 3. Insert CAS card blob
133
+ self.conn.execute(
134
+ "INSERT OR IGNORE INTO card (hash, content, g_time) VALUES (?, ?, ?)",
135
+ (hash_str, serialized, now),
136
+ )
137
+
138
+ # 4. Update history if hash changed
139
+ if prev_hash and prev_hash != hash_str:
140
+ self.conn.execute(
141
+ "INSERT INTO handle_history (handle, previous_hash, changed_at) VALUES (?, ?, ?)",
142
+ (canonical_uri, prev_hash, now),
143
+ )
144
+
145
+ # 5. Insert or replace handle entry
146
+ self.conn.execute(
147
+ "INSERT OR REPLACE INTO handle_registry (handle, current_hash, created_at, updated_at) "
148
+ "VALUES (?, ?, ?, ?)",
149
+ (canonical_uri, hash_str, created_at, now),
150
+ )
151
+
152
+ return mcard
153
+
154
+ if self.conn.in_transaction:
155
+ return _do_write()
156
+ else:
157
+ with self.conn:
158
+ return _do_write()
159
+
160
+ def read_file(self, uri: str) -> MCard:
161
+ """Reads and cryptographically verifies an MCard by handle URI."""
162
+ canonical_uri = self.canonicalize_uri(uri)
163
+ cur = self.conn.execute(
164
+ "SELECT current_hash FROM handle_registry WHERE handle = ?", (canonical_uri,)
165
+ )
166
+ row = cur.fetchone()
167
+ if not row:
168
+ raise KeyError(f"Handle '{canonical_uri}' not found in MCardFs")
169
+
170
+ current_hash = row[0]
171
+ card_cur = self.conn.execute(
172
+ "SELECT content FROM card WHERE hash = ?", (current_hash,)
173
+ )
174
+ card_row = card_cur.fetchone()
175
+ if not card_row:
176
+ raise ValueError(f"Card content for hash '{current_hash}' corrupted or missing")
177
+
178
+ data = json.loads(card_row[0])
179
+ mcard = MCard(**data)
180
+
181
+ # Cryptographic integrity check (INV-02)
182
+ from clm.layer0.utils import clm_hash_string
183
+ canonical_serialized = json.dumps(mcard.payload, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
184
+ expected_hash = clm_hash_string(canonical_serialized)
185
+
186
+ if expected_hash != current_hash:
187
+ raise ValueError(
188
+ f"Cryptographic hash mismatch for '{canonical_uri}': registry={current_hash} computed={expected_hash}"
189
+ )
190
+
191
+ return mcard
192
+
193
+ def exists(self, uri: str) -> bool:
194
+ """Checks if a handle exists in the filesystem."""
195
+ canonical_uri = self.canonicalize_uri(uri)
196
+ cur = self.conn.execute(
197
+ "SELECT count(*) FROM handle_registry WHERE handle = ?", (canonical_uri,)
198
+ )
199
+ return cur.fetchone()[0] > 0
200
+
201
+ def stat_file(self, uri: str) -> MCardStat:
202
+ """Returns stat metadata for a handle URI."""
203
+ canonical_uri = self.canonicalize_uri(uri)
204
+ cur = self.conn.execute(
205
+ "SELECT current_hash, created_at, updated_at FROM handle_registry WHERE handle = ?",
206
+ (canonical_uri,),
207
+ )
208
+ row = cur.fetchone()
209
+ if not row:
210
+ raise KeyError(f"Handle '{canonical_uri}' not found")
211
+
212
+ current_hash, created_at, updated_at = row
213
+ card_cur = self.conn.execute(
214
+ "SELECT length(content) FROM card WHERE hash = ?", (current_hash,)
215
+ )
216
+ card_row = card_cur.fetchone()
217
+ size_bytes = card_row[0] if card_row else 0
218
+
219
+ return MCardStat(
220
+ uri=canonical_uri,
221
+ hash=make_content_hash(current_hash),
222
+ size_bytes=size_bytes,
223
+ created_at=created_at,
224
+ updated_at=updated_at,
225
+ )
226
+
227
+ def list_dir(self, prefix: str) -> list[str]:
228
+ """Lists all handles matching the specified prefix."""
229
+ canonical_prefix = self.canonicalize_uri(prefix)
230
+ if canonical_prefix.endswith("/"):
231
+ pattern = f"{canonical_prefix}%"
232
+ elif canonical_prefix == "mcard:":
233
+ pattern = "mcard:%"
234
+ else:
235
+ pattern = f"{canonical_prefix}/%"
236
+
237
+ cur = self.conn.execute(
238
+ "SELECT handle FROM handle_registry WHERE handle LIKE ? ORDER BY handle ASC",
239
+ (pattern,),
240
+ )
241
+ return [r[0] for r in cur.fetchall()]
242
+
243
+ def delete_file(self, uri: str) -> None:
244
+ """Deregisters a handle URI while preserving the CAS content blob."""
245
+ canonical_uri = self.canonicalize_uri(uri)
246
+ now = datetime.datetime.now(datetime.timezone.utc).isoformat()
247
+ with self.conn:
248
+ cur = self.conn.execute(
249
+ "SELECT current_hash FROM handle_registry WHERE handle = ?", (canonical_uri,)
250
+ )
251
+ row = cur.fetchone()
252
+ if not row:
253
+ raise KeyError(f"Handle '{canonical_uri}' not found for deletion")
254
+
255
+ self.conn.execute(
256
+ "INSERT INTO handle_history (handle, previous_hash, changed_at) VALUES (?, ?, ?)",
257
+ (canonical_uri, row[0], now),
258
+ )
259
+ self.conn.execute("DELETE FROM handle_registry WHERE handle = ?", (canonical_uri,))
260
+
261
+
262
+ __all__ = ["MCardStat", "MCardFileSystem"]
clm/layer2/merkle.py ADDED
@@ -0,0 +1,11 @@
1
+ """Layer 2: Merkle Tree & Directory Ingestion.
2
+
3
+ Canonical Path: kernel/scripts/clm/layer2/merkle.py
4
+ Parity: Rust `src/layer2/merkle.rs`, TypeScript `src/layer2/merkleTree.ts`
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from clm.layer0.algebra_ingest import directory_fs_scan_py, directory_to_db_py
10
+
11
+ __all__ = ["directory_fs_scan_py", "directory_to_db_py"]