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,163 @@
1
+ """
2
+ Free Variables - Analysis of free and bound variables in Lambda terms
3
+
4
+ Provides utilities for computing free variables, bound variables,
5
+ and checking if variables are captured during substitution.
6
+
7
+ This module provides Python parity with mcard-js/src/ptr/lambda/FreeVariables.ts
8
+ """
9
+
10
+ from clm.layer2.card_collection import CardCollection
11
+ from clm.layer3.satori.lambda_term import (
12
+ is_abs,
13
+ is_app,
14
+ is_var,
15
+ load_term,
16
+ )
17
+
18
+ # ─────────────────────────────────────────────────────────────────────────────
19
+ # Free Variables
20
+ # ─────────────────────────────────────────────────────────────────────────────
21
+
22
+
23
+ def free_variables(collection: CardCollection, hash_value: str) -> set[str] | None:
24
+ """
25
+ Compute the set of free variables in a Lambda term.
26
+
27
+ FV(x) = {x}
28
+ FV(λx.M) = FV(M) - {x}
29
+ FV(M N) = FV(M) ∪ FV(N)
30
+ """
31
+ term = load_term(collection, hash_value)
32
+ if term is None:
33
+ return None
34
+
35
+ if is_var(term):
36
+ return {term.name}
37
+
38
+ elif is_abs(term):
39
+ body_fv = free_variables(collection, term.body)
40
+ if body_fv is None:
41
+ return None
42
+ return body_fv - {term.param}
43
+
44
+ elif is_app(term):
45
+ func_fv = free_variables(collection, term.func)
46
+ arg_fv = free_variables(collection, term.arg)
47
+ if func_fv is None or arg_fv is None:
48
+ return None
49
+ return func_fv | arg_fv
50
+
51
+ return set()
52
+
53
+
54
+ # ─────────────────────────────────────────────────────────────────────────────
55
+ # Bound Variables
56
+ # ─────────────────────────────────────────────────────────────────────────────
57
+
58
+
59
+ def bound_variables(collection: CardCollection, hash_value: str) -> set[str] | None:
60
+ """
61
+ Compute the set of bound variables in a Lambda term.
62
+
63
+ BV(x) = {}
64
+ BV(λx.M) = BV(M) ∪ {x}
65
+ BV(M N) = BV(M) ∪ BV(N)
66
+ """
67
+ term = load_term(collection, hash_value)
68
+ if term is None:
69
+ return None
70
+
71
+ if is_var(term):
72
+ return set()
73
+
74
+ elif is_abs(term):
75
+ body_bv = bound_variables(collection, term.body)
76
+ if body_bv is None:
77
+ return None
78
+ return body_bv | {term.param}
79
+
80
+ elif is_app(term):
81
+ func_bv = bound_variables(collection, term.func)
82
+ arg_bv = bound_variables(collection, term.arg)
83
+ if func_bv is None or arg_bv is None:
84
+ return None
85
+ return func_bv | arg_bv
86
+
87
+ return set()
88
+
89
+
90
+ # ─────────────────────────────────────────────────────────────────────────────
91
+ # Variable Checks
92
+ # ─────────────────────────────────────────────────────────────────────────────
93
+
94
+
95
+ def is_free_in(
96
+ collection: CardCollection, var_name: str, hash_value: str
97
+ ) -> bool | None:
98
+ """Check if a variable is free in a term"""
99
+ fv = free_variables(collection, hash_value)
100
+ if fv is None:
101
+ return None
102
+ return var_name in fv
103
+
104
+
105
+ def is_closed(collection: CardCollection, hash_value: str) -> bool | None:
106
+ """Check if a term is closed (has no free variables)"""
107
+ fv = free_variables(collection, hash_value)
108
+ if fv is None:
109
+ return None
110
+ return len(fv) == 0
111
+
112
+
113
+ # ─────────────────────────────────────────────────────────────────────────────
114
+ # Fresh Name Generation
115
+ # ─────────────────────────────────────────────────────────────────────────────
116
+
117
+
118
+ def generate_fresh(base: str, avoid: set[str]) -> str:
119
+ """
120
+ Generate a fresh variable name that doesn't conflict with avoid set.
121
+ Uses primes: x, x', x'', x'''...
122
+ """
123
+ candidate = base
124
+ while candidate in avoid:
125
+ candidate = candidate + "'"
126
+ return candidate
127
+
128
+
129
+ def generate_fresh_for(collection: CardCollection, base: str, *hashes: str) -> str:
130
+ """
131
+ Generate a fresh variable name avoiding all variables in the given terms.
132
+ """
133
+ avoid: set[str] = set()
134
+
135
+ for h in hashes:
136
+ fv = free_variables(collection, h)
137
+ if fv:
138
+ avoid |= fv
139
+ bv = bound_variables(collection, h)
140
+ if bv:
141
+ avoid |= bv
142
+
143
+ return generate_fresh(base, avoid)
144
+
145
+
146
+ # ─────────────────────────────────────────────────────────────────────────────
147
+ # Set Operations
148
+ # ─────────────────────────────────────────────────────────────────────────────
149
+
150
+
151
+ def set_difference(a: set[str], b: set[str]) -> set[str]:
152
+ """Set difference: A - B"""
153
+ return a - b
154
+
155
+
156
+ def set_intersection(a: set[str], b: set[str]) -> set[str]:
157
+ """Set intersection: A ∩ B"""
158
+ return a & b
159
+
160
+
161
+ def set_union(a: set[str], b: set[str]) -> set[str]:
162
+ """Set union: A ∪ B"""
163
+ return a | b
@@ -0,0 +1,370 @@
1
+ """
2
+ IO Effects - Observable side-effects for Lambda Calculus computations
3
+
4
+ Implements the IO Monad pattern for CLM:
5
+ IO a = World → (a, World')
6
+
7
+ In our context:
8
+ Reduction = TermHash → (TermHash', IOEffects)
9
+
10
+ IO effects are purely observational - they do not affect computation results.
11
+ The same input will always produce the same output regardless of IO configuration.
12
+
13
+ This module provides Python parity with mcard-js/src/ptr/lambda/IOEffects.ts
14
+ """
15
+
16
+ import json
17
+ from dataclasses import dataclass, field
18
+ from datetime import datetime
19
+ from typing import Any, Literal
20
+
21
+ import requests
22
+
23
+ from clm.cross_cutting.config.config_constants import URL_FETCH_TIMEOUT_SECS
24
+
25
+ # ─────────────────────────────────────────────────────────────────────────────
26
+ # Types
27
+ # ─────────────────────────────────────────────────────────────────────────────
28
+
29
+ IOFormat = Literal["minimal", "verbose", "json"]
30
+
31
+
32
+ @dataclass
33
+ class NetworkConfig:
34
+ """Network output configuration."""
35
+
36
+ enabled: bool = False
37
+ endpoint: str | None = None
38
+ method: str = "POST"
39
+ headers: dict[str, str] = field(default_factory=dict)
40
+
41
+
42
+ @dataclass
43
+ class IOEffectsConfig:
44
+ """Configuration for IO effects."""
45
+
46
+ enabled: bool = False
47
+ console: bool = True
48
+ network: NetworkConfig | None = None
49
+ on_step: bool = False
50
+ on_complete: bool = True
51
+ on_error: bool = True
52
+ format: IOFormat = "minimal"
53
+
54
+
55
+ @dataclass
56
+ class StepEvent:
57
+ """Event emitted after each reduction step."""
58
+
59
+ type: str = "step"
60
+ step_number: int = 0
61
+ term_hash: str = ""
62
+ pretty_print: str = ""
63
+ timestamp: datetime = field(default_factory=datetime.now)
64
+
65
+
66
+ @dataclass
67
+ class CompleteEvent:
68
+ """Event emitted when normalization completes."""
69
+
70
+ type: str = "complete"
71
+ normal_form: str = ""
72
+ pretty_print: str = ""
73
+ total_steps: int = 0
74
+ reduction_path: list[str] = field(default_factory=list)
75
+ timestamp: datetime = field(default_factory=datetime.now)
76
+
77
+
78
+ @dataclass
79
+ class ErrorEvent:
80
+ """Event emitted on execution failure."""
81
+
82
+ type: str = "error"
83
+ message: str = ""
84
+ partial_steps: int = 0
85
+ last_term_hash: str | None = None
86
+ timestamp: datetime = field(default_factory=datetime.now)
87
+
88
+
89
+ IOEvent = StepEvent | CompleteEvent | ErrorEvent
90
+
91
+
92
+ # ─────────────────────────────────────────────────────────────────────────────
93
+ # ANSI Colors
94
+ # ─────────────────────────────────────────────────────────────────────────────
95
+
96
+
97
+ class Colors:
98
+ CYAN = "\033[36m"
99
+ RED = "\033[31m"
100
+ RESET = "\033[0m"
101
+
102
+
103
+ # ─────────────────────────────────────────────────────────────────────────────
104
+ # IO Effects Handler
105
+ # ─────────────────────────────────────────────────────────────────────────────
106
+
107
+
108
+ class IOEffectsHandler:
109
+ """
110
+ Handler for IO effects during lambda computation.
111
+
112
+ Dispatches events to configured outputs (console, network, file).
113
+ """
114
+
115
+ def __init__(self, config: IOEffectsConfig | None = None):
116
+ self.config = config or IOEffectsConfig()
117
+ self.events: list[IOEvent] = []
118
+
119
+ def is_enabled(self) -> bool:
120
+ """Check if IO effects are enabled."""
121
+ return self.config.enabled
122
+
123
+ def emit_step(self, step_number: int, term_hash: str, pretty_print: str) -> None:
124
+ """Emit a step event."""
125
+ if not self.config.enabled or not self.config.on_step:
126
+ return
127
+
128
+ event = StepEvent(
129
+ step_number=step_number,
130
+ term_hash=term_hash,
131
+ pretty_print=pretty_print,
132
+ timestamp=datetime.now(),
133
+ )
134
+
135
+ self.events.append(event)
136
+ self._dispatch(event)
137
+
138
+ def emit_complete(
139
+ self,
140
+ normal_form: str,
141
+ pretty_print: str,
142
+ total_steps: int,
143
+ reduction_path: list[str],
144
+ ) -> None:
145
+ """Emit a completion event."""
146
+ if not self.config.enabled or not self.config.on_complete:
147
+ return
148
+
149
+ event = CompleteEvent(
150
+ normal_form=normal_form,
151
+ pretty_print=pretty_print,
152
+ total_steps=total_steps,
153
+ reduction_path=reduction_path,
154
+ timestamp=datetime.now(),
155
+ )
156
+
157
+ self.events.append(event)
158
+ self._dispatch(event)
159
+
160
+ def emit_error(
161
+ self, message: str, partial_steps: int, last_term_hash: str | None = None
162
+ ) -> None:
163
+ """Emit an error event."""
164
+ if not self.config.enabled or not self.config.on_error:
165
+ return
166
+
167
+ event = ErrorEvent(
168
+ message=message,
169
+ partial_steps=partial_steps,
170
+ last_term_hash=last_term_hash,
171
+ timestamp=datetime.now(),
172
+ )
173
+
174
+ self.events.append(event)
175
+ self._dispatch(event)
176
+
177
+ def get_events(self) -> list[IOEvent]:
178
+ """Get all collected events."""
179
+ return list(self.events)
180
+
181
+ def clear_events(self) -> None:
182
+ """Clear collected events."""
183
+ self.events = []
184
+
185
+ def _dispatch(self, event: IOEvent) -> None:
186
+ """Dispatch event to configured outputs."""
187
+ formatted = self._format(event)
188
+
189
+ # Console output
190
+ if self.config.console:
191
+ self._log_to_console(event, formatted)
192
+
193
+ # Network output
194
+ if self.config.network and self.config.network.enabled:
195
+ self._send_to_network(event, formatted)
196
+
197
+ def _format(self, event: IOEvent) -> str:
198
+ """Format event for output."""
199
+ if self.config.format == "json":
200
+ return self._format_json(event)
201
+ elif self.config.format == "verbose":
202
+ return self._format_verbose(event)
203
+ else:
204
+ return self._format_minimal(event)
205
+
206
+ def _format_minimal(self, event: IOEvent) -> str:
207
+ """Format event as single line."""
208
+ if isinstance(event, StepEvent):
209
+ return f"[IO:step {event.step_number}] {event.pretty_print}"
210
+ elif isinstance(event, CompleteEvent):
211
+ return f"[IO:complete] {event.total_steps} steps → {event.pretty_print}"
212
+ elif isinstance(event, ErrorEvent):
213
+ return f"[IO:error] {event.message}"
214
+ return ""
215
+
216
+ def _format_verbose(self, event: IOEvent) -> str:
217
+ """Format event with full context."""
218
+ ts = event.timestamp.isoformat()
219
+
220
+ if isinstance(event, StepEvent):
221
+ return "\n".join(
222
+ [
223
+ f"┌─ IO Effect: Reduction Step {event.step_number}",
224
+ f"│ Time: {ts}",
225
+ f"│ Hash: {event.term_hash[:16]}...",
226
+ f"│ Term: {event.pretty_print}",
227
+ "└─────────────────────────────────────────",
228
+ ]
229
+ )
230
+ elif isinstance(event, CompleteEvent):
231
+ return "\n".join(
232
+ [
233
+ "╔═══════════════════════════════════════════",
234
+ "║ IO Effect: Normalization Complete",
235
+ "╠═══════════════════════════════════════════",
236
+ f"║ Time: {ts}",
237
+ f"║ Total Steps: {event.total_steps}",
238
+ f"║ Normal Form: {event.pretty_print}",
239
+ f"║ Hash: {event.normal_form[:16]}...",
240
+ "╚═══════════════════════════════════════════",
241
+ ]
242
+ )
243
+ elif isinstance(event, ErrorEvent):
244
+ return "\n".join(
245
+ [
246
+ "╔═══════════════════════════════════════════",
247
+ "║ IO Effect: ERROR",
248
+ "╠═══════════════════════════════════════════",
249
+ f"║ Time: {ts}",
250
+ f"║ Message: {event.message}",
251
+ f"║ Partial Steps: {event.partial_steps}",
252
+ "╚═══════════════════════════════════════════",
253
+ ]
254
+ )
255
+ return ""
256
+
257
+ def _format_json(self, event: IOEvent) -> str:
258
+ """Format event as JSON."""
259
+ data = {"type": event.type, "timestamp": event.timestamp.isoformat()}
260
+
261
+ if isinstance(event, StepEvent):
262
+ data.update(
263
+ {
264
+ "stepNumber": event.step_number,
265
+ "termHash": event.term_hash,
266
+ "prettyPrint": event.pretty_print,
267
+ }
268
+ )
269
+ elif isinstance(event, CompleteEvent):
270
+ data.update(
271
+ {
272
+ "normalForm": event.normal_form,
273
+ "prettyPrint": event.pretty_print,
274
+ "totalSteps": event.total_steps,
275
+ "reductionPath": event.reduction_path,
276
+ }
277
+ )
278
+ elif isinstance(event, ErrorEvent):
279
+ data.update(
280
+ {
281
+ "message": event.message,
282
+ "partialSteps": event.partial_steps,
283
+ "lastTermHash": event.last_term_hash,
284
+ }
285
+ )
286
+
287
+ return json.dumps(data)
288
+
289
+ def _log_to_console(self, event: IOEvent, formatted: str) -> None:
290
+ """Log event to console with color."""
291
+ if isinstance(event, ErrorEvent):
292
+ print(f"{Colors.RED}{formatted}{Colors.RESET}")
293
+ else:
294
+ print(f"{Colors.CYAN}{formatted}{Colors.RESET}")
295
+
296
+ def _send_to_network(self, event: IOEvent, formatted: str) -> None:
297
+ """Send event to network endpoint."""
298
+ if not self.config.network or not self.config.network.endpoint:
299
+ return
300
+
301
+ try:
302
+ payload = {
303
+ "event": event.type,
304
+ "data": self._format_json(event),
305
+ "formatted": formatted,
306
+ }
307
+
308
+ response = requests.request(
309
+ method=self.config.network.method,
310
+ url=self.config.network.endpoint,
311
+ headers={
312
+ "Content-Type": "application/json",
313
+ **self.config.network.headers,
314
+ },
315
+ json=payload,
316
+ timeout=URL_FETCH_TIMEOUT_SECS,
317
+ )
318
+
319
+ if not response.ok:
320
+ print(f"IO network effect failed: {response.status_code}")
321
+ except (OSError, ConnectionError, ValueError) as e:
322
+ print(f"IO network effect error: {e}")
323
+
324
+
325
+ # ─────────────────────────────────────────────────────────────────────────────
326
+ # Factory and Utilities
327
+ # ─────────────────────────────────────────────────────────────────────────────
328
+
329
+
330
+ def create_io_handler(config: dict[str, Any] | None = None) -> IOEffectsHandler:
331
+ """
332
+ Create an IO effects handler from CLM config.
333
+
334
+ Args:
335
+ config: Dictionary containing io_effects configuration
336
+
337
+ Returns:
338
+ IOEffectsHandler configured based on the input
339
+ """
340
+ if not config or not config.get("io_effects"):
341
+ return IOEffectsHandler(IOEffectsConfig(enabled=False))
342
+
343
+ io_config = config["io_effects"]
344
+
345
+ # Parse network config if present
346
+ network = None
347
+ if io_config.get("network"):
348
+ net_cfg = io_config["network"]
349
+ network = NetworkConfig(
350
+ enabled=net_cfg.get("enabled", False),
351
+ endpoint=net_cfg.get("endpoint"),
352
+ method=net_cfg.get("method", "POST"),
353
+ headers=net_cfg.get("headers", {}),
354
+ )
355
+
356
+ return IOEffectsHandler(
357
+ IOEffectsConfig(
358
+ enabled=io_config.get("enabled", False),
359
+ console=io_config.get("console", True),
360
+ network=network,
361
+ on_step=io_config.get("on_step", io_config.get("onStep", False)),
362
+ on_complete=io_config.get("on_complete", io_config.get("onComplete", True)),
363
+ on_error=io_config.get("on_error", io_config.get("onError", True)),
364
+ format=io_config.get("format", "minimal"),
365
+ )
366
+ )
367
+
368
+
369
+ # No-op handler for when IO effects are disabled
370
+ noop_io_handler = IOEffectsHandler(IOEffectsConfig(enabled=False))