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,252 @@
1
+ """CEL Engine — CLM Expression Language evaluator with registry-based dispatch.
2
+
3
+ Extracted from ``pcard.py`` in Sprint 127.1 and converted to registry-based
4
+ dispatch in Sprint 127.2. This module is the canonical home for:
5
+
6
+ - ``CelOpRegistry`` — maps operation names to handler functions
7
+ - ``CelOpHandler`` — protocol for operation handlers
8
+ - ``_evaluate_cel()`` — delegates to the registry (6 lines)
9
+ - ``_smart_evaluate()`` — auto-detects CEL dict vs legacy string
10
+ - ``_resolve_arg()`` / ``_resolve_ref()`` — argument/reference resolution
11
+
12
+ Each CEL operation (``string_concat``, ``compare``, ``db_query``, etc.) is
13
+ implemented as a standalone function in :mod:`cel_ops` and registered at
14
+ import time via ``_register_defaults()``.
15
+
16
+ Adding a new operation requires exactly:
17
+ 1. One function in ``cel_ops.py``
18
+ 2. One ``register()`` call in ``_register_defaults()``
19
+ 3. One entry in ``schema/cel_schema.yaml``
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import logging
25
+ import re
26
+ from typing import Any
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Compiled regexes (module-level, compiled once)
30
+ # ---------------------------------------------------------------------------
31
+
32
+ _RE_SIMPLE_IDENTIFIER = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
33
+ _RE_PYTHON_SYNTAX = re.compile(r"[()[\]{}.\"']")
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Deprecation logging (Sprint 126.1, loaded from schema in Sprint 127.4)
37
+ # ---------------------------------------------------------------------------
38
+
39
+ _DEP_LOGGER = logging.getLogger("clm.cel")
40
+
41
+
42
+ _FALLBACK_DEPRECATED_FUNCS: set[str] = {
43
+ "operator.add", "operator.sub", "operator.mul",
44
+ "operator.truediv", "operator.mod", "str.join", "os.system",
45
+ }
46
+
47
+
48
+ def _load_deprecated_funcs() -> set[str]:
49
+ """Load the canonical deprecated-function list from cel_schema.yaml.
50
+
51
+ Sprint 132.5: only ImportError (bootstrap) falls back silently;
52
+ other failures log loudly instead of silently using the fallback.
53
+ """
54
+ try:
55
+ from clm.cross_cutting.cel_converter import load_schema
56
+ schema = load_schema()
57
+ call_def = schema.get("call", {})
58
+ funcs = set(call_def.get("deprecated_functions", []))
59
+ if not funcs:
60
+ _DEP_LOGGER.warning("[CEL_DEPRECATED] schema loaded but deprecated_functions is empty")
61
+ return funcs
62
+ except ImportError:
63
+ return _FALLBACK_DEPRECATED_FUNCS
64
+ except Exception as e:
65
+ _DEP_LOGGER.error("[CEL_DEPRECATED] schema load failed: %s — using fallback", e)
66
+ return _FALLBACK_DEPRECATED_FUNCS
67
+
68
+
69
+ _DEPRECATED_FUNCS = _load_deprecated_funcs()
70
+
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # CEL Operation Registry (Sprint 127.2)
74
+ # ---------------------------------------------------------------------------
75
+
76
+ class CelOpRegistry:
77
+ """Maps CEL operation names to handler functions.
78
+
79
+ Replaces the elif chain. Each handler receives the full CEL dict and
80
+ the evaluation context, and returns the result.
81
+ """
82
+
83
+ def __init__(self) -> None:
84
+ self._handlers: dict[str, Any] = {}
85
+
86
+ def register(self, op_name: str, handler: Any) -> None:
87
+ if op_name in self._handlers:
88
+ raise ValueError(f"CEL op '{op_name}' already registered")
89
+ self._handlers[op_name] = handler
90
+
91
+ def dispatch(self, cel: dict[str, Any], context: dict[str, Any]) -> Any:
92
+ op = cel.get("op")
93
+ if op is None:
94
+ raise ValueError(f"CEL dict missing 'op' field: {cel}")
95
+ handler = self._handlers.get(op)
96
+ if handler is None:
97
+ raise ValueError(f"Unknown CEL operation: {op}")
98
+ return handler(cel, context)
99
+
100
+ @property
101
+ def registered_ops(self) -> set[str]:
102
+ return set(self._handlers.keys())
103
+
104
+
105
+ _DEFAULT_REGISTRY = CelOpRegistry()
106
+
107
+
108
+ # ---------------------------------------------------------------------------
109
+ # Argument / Reference Resolution
110
+ # ---------------------------------------------------------------------------
111
+
112
+ def _resolve_ref(ref_str: str, context: dict[str, Any]) -> Any:
113
+ """Resolve a dot-notation reference against the evaluation context.
114
+
115
+ Sprint 133 §7.4: delegates to ``cel_resolver.resolve_ref`` for a single
116
+ implementation. The legacy ``dict``-based context path is preserved.
117
+ """
118
+ from clm.cross_cutting.cel_resolver import resolve_ref
119
+ return resolve_ref(ref_str, context)
120
+
121
+
122
+ def _resolve_arg(arg: Any, context: dict[str, Any]) -> Any:
123
+ """Resolve a single CEL argument — literal, ref, nested op, or list."""
124
+ if isinstance(arg, list):
125
+ return [_resolve_arg(a, context) for a in arg]
126
+ if isinstance(arg, dict):
127
+ if "ref" in arg:
128
+ return _resolve_ref(arg["ref"], context)
129
+ elif "op" in arg:
130
+ return _evaluate_cel(arg, context)
131
+ else:
132
+ return arg
133
+ return arg
134
+
135
+
136
+ # ---------------------------------------------------------------------------
137
+ # CEL Evaluator (delegates to registry — Sprint 127.2)
138
+ # ---------------------------------------------------------------------------
139
+
140
+ def _evaluate_cel(cel: dict[str, Any], context: dict[str, Any]) -> Any:
141
+ """Evaluate a CEL operation descriptor via the default registry."""
142
+ if not isinstance(cel, dict):
143
+ return cel
144
+ return _DEFAULT_REGISTRY.dispatch(cel, context)
145
+
146
+
147
+ def _evaluate_expr(expr: Any, context: dict[str, Any]) -> Any:
148
+ """Safely evaluate a Python expression string against *context*.
149
+
150
+ Sprint 132.5: simple identifiers and no-syntax strings pass through
151
+ without eval(); pure literals use ``ast.literal_eval``; full Python
152
+ expressions use ``eval()`` unless ``CLM_DISABLE_EVAL=1`` is set
153
+ (hardening knob for untrusted YAML).
154
+ """
155
+ import os as _os
156
+ expr_str = str(expr).strip()
157
+ if not expr_str:
158
+ return expr
159
+ # Handle simple cases without eval()
160
+ if _RE_SIMPLE_IDENTIFIER.match(expr_str):
161
+ # Python builtins
162
+ if expr_str == "True":
163
+ return True
164
+ if expr_str == "False":
165
+ return False
166
+ if expr_str == "None":
167
+ return None
168
+ # Could be a variable name in context, or just a plain string
169
+ if expr_str in context:
170
+ return context[expr_str]
171
+ return expr_str
172
+ if not _RE_PYTHON_SYNTAX.search(expr_str):
173
+ # No Python syntax characters — return as-is
174
+ return expr_str
175
+ # Try safe literal eval for quoted strings/numbers before falling back to eval()
176
+ try:
177
+ import ast
178
+ return ast.literal_eval(expr_str)
179
+ except (ValueError, SyntaxError):
180
+ pass
181
+ # Full Python expression — opt-out hardening via CLM_DISABLE_EVAL=1
182
+ if _os.environ.get("CLM_DISABLE_EVAL", "").lower() == "1":
183
+ raise ValueError(
184
+ f"Python eval() is disabled via CLM_DISABLE_EVAL=1. "
185
+ f"Migrate to CLM expression ops or unset the flag: {expr_str[:80]!r}"
186
+ )
187
+ # Sprint 133.4: escalate eval() usage to DeprecationWarning (was logger.warning)
188
+ import warnings
189
+ warnings.warn(
190
+ "eval() in CEL expressions is deprecated; migrate to CLM expression ops. "
191
+ f"Set CLM_DISABLE_EVAL=1 to hard-fail. Expression: {expr_str[:80]!r}",
192
+ DeprecationWarning,
193
+ stacklevel=2,
194
+ )
195
+ _DEP_LOGGER.warning("[CEL_EVAL] eval() invoked: %r", expr_str[:80])
196
+ return eval(expr_str, context) # noqa: S307
197
+
198
+
199
+ def _smart_evaluate(expr: Any, context: dict[str, Any]) -> Any:
200
+ """Auto-detect CEL dict vs legacy Python string and evaluate accordingly."""
201
+ if isinstance(expr, dict) and "op" in expr:
202
+ return _evaluate_cel(expr, context)
203
+ return _evaluate_expr(expr, context)
204
+
205
+
206
+ # ---------------------------------------------------------------------------
207
+ # Default Registry Bootstrap
208
+ # ---------------------------------------------------------------------------
209
+
210
+ def _register_defaults() -> None:
211
+ """Register all built-in CEL operations from :mod:`cel_ops`."""
212
+ from clm.cross_cutting import cel_ops
213
+
214
+ _DEFAULT_REGISTRY.register("value_ref", cel_ops.op_value_ref)
215
+ _DEFAULT_REGISTRY.register("db_query", cel_ops.op_db_query)
216
+ _DEFAULT_REGISTRY.register("call", cel_ops.op_call)
217
+ _DEFAULT_REGISTRY.register("store", cel_ops.op_store)
218
+ _DEFAULT_REGISTRY.register("retrieve", cel_ops.op_retrieve)
219
+ _DEFAULT_REGISTRY.register("write_file", cel_ops.op_write_file)
220
+ _DEFAULT_REGISTRY.register("set_env", cel_ops.op_set_env)
221
+ _DEFAULT_REGISTRY.register("unset_env", cel_ops.op_unset_env)
222
+ _DEFAULT_REGISTRY.register("sleep", cel_ops.op_sleep)
223
+ _DEFAULT_REGISTRY.register("sequence", cel_ops.op_sequence)
224
+ _DEFAULT_REGISTRY.register("path_join", cel_ops.op_path_join)
225
+ _DEFAULT_REGISTRY.register("len", cel_ops.op_len)
226
+ _DEFAULT_REGISTRY.register("compare", cel_ops.op_compare)
227
+ _DEFAULT_REGISTRY.register("int_cast", cel_ops.op_int_cast)
228
+ _DEFAULT_REGISTRY.register("or_fallback", cel_ops.op_or_fallback)
229
+ _DEFAULT_REGISTRY.register("contains", cel_ops.op_contains)
230
+ _DEFAULT_REGISTRY.register("make_mcard_content", cel_ops.op_make_mcard_content)
231
+ _DEFAULT_REGISTRY.register("copy_file", cel_ops.op_copy_file)
232
+ _DEFAULT_REGISTRY.register("not_null", cel_ops.op_not_null)
233
+ _DEFAULT_REGISTRY.register("callable_check", cel_ops.op_callable_check)
234
+ _DEFAULT_REGISTRY.register("json_parse", cel_ops.op_json_parse)
235
+ _DEFAULT_REGISTRY.register("dict_get", cel_ops.op_dict_get)
236
+ _DEFAULT_REGISTRY.register("index", cel_ops.op_index)
237
+ _DEFAULT_REGISTRY.register("replace", cel_ops.op_replace)
238
+ _DEFAULT_REGISTRY.register("read_file", cel_ops.op_read_file)
239
+ _DEFAULT_REGISTRY.register("capture_output", cel_ops.op_capture_output)
240
+ _DEFAULT_REGISTRY.register("run_test_suite", cel_ops.op_run_test_suite)
241
+ _DEFAULT_REGISTRY.register("string_concat", cel_ops.op_string_concat)
242
+ _DEFAULT_REGISTRY.register("path_basename", cel_ops.op_path_basename)
243
+ _DEFAULT_REGISTRY.register("string_join", cel_ops.op_string_join)
244
+ # Sprint 131.4 — CLM expression sub-system: card & handle ops
245
+ _DEFAULT_REGISTRY.register("card:create", cel_ops.op_card_create)
246
+ _DEFAULT_REGISTRY.register("card:save", cel_ops.op_card_save)
247
+ _DEFAULT_REGISTRY.register("card:load", cel_ops.op_card_load)
248
+ _DEFAULT_REGISTRY.register("handle:resolve", cel_ops.op_handle_resolve)
249
+ _DEFAULT_REGISTRY.register("handle:get_history", cel_ops.op_handle_get_history)
250
+
251
+
252
+ _register_defaults()
@@ -0,0 +1,72 @@
1
+ """CEL Operation Handlers — re-export shim (Sprint 133.2).
2
+
3
+ Sprint 133.2 decomposed this 432-line module into 4 domain-specific modules:
4
+ - ``cel_ops_state.py`` — value_ref, store, retrieve, dict_get, index, len,
5
+ not_null, callable_check, json_parse, int_cast, or_fallback, sequence
6
+ - ``cel_ops_files.py`` — write_file, set_env, unset_env, sleep, read_file,
7
+ copy_file, run_test_suite, capture_output
8
+ - ``cel_ops_strings.py`` — path_join, path_basename, string_concat,
9
+ string_join, replace, compare, contains, _COMPARE_OPS
10
+ - ``cel_ops_db.py`` — db_query, call, make_mcard_content, card:create,
11
+ card:save, card:load, handle:resolve, handle:get_history
12
+
13
+ This file remains as a re-export shim so ``cel_engine._register_defaults()``
14
+ can import all ops via ``cel_ops.op_*`` attribute access.
15
+ """
16
+ from clm.cross_cutting.cel_ops_db import (
17
+ op_call,
18
+ op_card_create,
19
+ op_card_load,
20
+ op_card_save,
21
+ op_db_query,
22
+ op_handle_get_history,
23
+ op_handle_resolve,
24
+ op_make_mcard_content,
25
+ )
26
+ from clm.cross_cutting.cel_ops_files import (
27
+ op_capture_output,
28
+ op_copy_file,
29
+ op_read_file,
30
+ op_run_test_suite,
31
+ op_set_env,
32
+ op_sleep,
33
+ op_unset_env,
34
+ op_write_file,
35
+ )
36
+ from clm.cross_cutting.cel_ops_state import (
37
+ op_callable_check,
38
+ op_dict_get,
39
+ op_index,
40
+ op_int_cast,
41
+ op_json_parse,
42
+ op_len,
43
+ op_not_null,
44
+ op_or_fallback,
45
+ op_retrieve,
46
+ op_sequence,
47
+ op_store,
48
+ op_value_ref,
49
+ )
50
+ from clm.cross_cutting.cel_ops_strings import (
51
+ _COMPARE_OPS,
52
+ op_compare,
53
+ op_contains,
54
+ op_path_basename,
55
+ op_path_join,
56
+ op_replace,
57
+ op_string_concat,
58
+ op_string_join,
59
+ )
60
+
61
+ __all__ = [
62
+ "op_value_ref", "op_db_query", "op_call", "op_store", "op_retrieve",
63
+ "op_write_file", "op_set_env", "op_unset_env", "op_sleep", "op_sequence",
64
+ "op_path_join", "op_len", "op_compare", "op_int_cast", "op_or_fallback",
65
+ "op_contains", "op_make_mcard_content", "op_copy_file", "op_not_null",
66
+ "op_callable_check", "op_json_parse", "op_dict_get", "op_index",
67
+ "op_replace", "op_read_file", "op_capture_output", "op_run_test_suite",
68
+ "op_string_concat", "op_path_basename", "op_string_join",
69
+ "op_card_create", "op_card_save", "op_card_load",
70
+ "op_handle_resolve", "op_handle_get_history",
71
+ "_COMPARE_OPS",
72
+ ]
@@ -0,0 +1,157 @@
1
+ """CEL database, call, and card/handle ops (Sprint 133.2).
2
+
3
+ Extracted from ``cel_ops.py``. Operations for DB queries, function calls
4
+ (including deprecated Python-bound passthroughs), MCard generation, and
5
+ CLM expression sub-system card/handle operations (Sprint 131.4).
6
+ """
7
+ from clm.cross_cutting.cel_engine import (
8
+ _DEP_LOGGER,
9
+ _DEPRECATED_FUNCS,
10
+ _resolve_arg,
11
+ )
12
+
13
+
14
+ def _resolve_card_content(ctx: dict, card_hash: str, pillar: str = "execution_log") -> str | None:
15
+ """Single source of truth for fetching card content by hash (Sprint 132.3 §7.4).
16
+
17
+ Uses ``repo.get_card_content`` when a repo is available, else falls
18
+ back to raw SQL via ``ctx["db"]``.
19
+ """
20
+ repo = ctx.get("repo")
21
+ if repo is not None:
22
+ return repo.get_card_content(card_hash, pillar)
23
+ raw = ctx["db"].execute(pillar, "SELECT content FROM card WHERE hash = ?;", [card_hash])
24
+ return raw.strip() if raw else None
25
+
26
+
27
+ def _resolve_handle_hash(ctx: dict, handle: str, pillar: str = "execution_log") -> str:
28
+ """Single source of truth for resolving a handle to its current hash (Sprint 132.3 §7.4).
29
+
30
+ Uses ``repo.resolve_handle_hash`` when a repo is available, else falls
31
+ back to raw SQL via ``ctx["db"]``.
32
+ """
33
+ repo = ctx.get("repo")
34
+ if repo is not None:
35
+ return repo.resolve_handle_hash(handle, pillar)
36
+ raw = ctx["db"].execute(pillar, "SELECT current_hash FROM handle_registry WHERE handle = ?;", [handle])
37
+ return raw.strip() if raw else ""
38
+
39
+
40
+ def op_db_query(cel: dict, ctx: dict):
41
+ pillar = _resolve_arg(cel["pillar"], ctx)
42
+ sql = _resolve_arg(cel["sql"], ctx)
43
+ params = cel.get("params")
44
+ if params is not None:
45
+ params = _resolve_arg(params, ctx)
46
+ result = ctx["db"].execute(pillar, sql, params)
47
+ else:
48
+ result = ctx["db"].execute(pillar, sql)
49
+ transform = cel.get("transform", "strip")
50
+ if transform == "strip" and isinstance(result, str):
51
+ result = result.strip()
52
+ if isinstance(result, str) and not result.strip() and not sql.strip().lower().startswith("select"):
53
+ return True
54
+ return result
55
+
56
+
57
+ def op_call(cel: dict, ctx: dict):
58
+ func_name = cel["function"]
59
+ args = [_resolve_arg(a, ctx) for a in cel.get("args", [])]
60
+ kwargs = {k: _resolve_arg(v, ctx) for k, v in cel.get("kwargs", {}).items()}
61
+ if func_name in _DEPRECATED_FUNCS:
62
+ _DEP_LOGGER.warning(
63
+ "[CEL_DEPRECATED] %s is a Python-bound call; migrate to "
64
+ "string_concat/string_join/run_test_suite. Lint gate (sprint-126.5) "
65
+ "will fail CI on these.", func_name,
66
+ )
67
+ if func_name == "operator.add":
68
+ return args[0] + args[1]
69
+ if func_name == "operator.sub":
70
+ return args[0] - args[1]
71
+ if func_name == "operator.mul":
72
+ return args[0] * args[1]
73
+ if func_name == "operator.truediv":
74
+ return args[0] / args[1]
75
+ if func_name == "operator.mod":
76
+ return args[0] % args[1]
77
+ func = ctx
78
+ for part in func_name.split("."):
79
+ func = func[part] if isinstance(func, dict) else getattr(func, part)
80
+ result = func(*args, **kwargs)
81
+ if cel.get("default") is not None and result is None:
82
+ result = _resolve_arg(cel["default"], ctx)
83
+ return result
84
+
85
+
86
+ def op_make_mcard_content(cel: dict, ctx: dict):
87
+ content_hash = _resolve_arg(cel["content_hash"], ctx)
88
+ content = _resolve_arg(cel["content"], ctx)
89
+ filename = _resolve_arg(cel.get("filename", "test.txt"), ctx)
90
+ mime_type = _resolve_arg(cel.get("mime_type", "text/plain"), ctx)
91
+ from clm.cross_cutting.mcard import make_mcard_content
92
+ return make_mcard_content(content_hash, content, filename, mime_type)
93
+
94
+
95
+ def op_card_create(cel: dict, ctx: dict):
96
+ """CEL: card:create — instantiate a Card from properties (no DB write)."""
97
+ from clm.cross_cutting.mcard import Card
98
+ props = {k: _resolve_arg(v, ctx) for k, v in cel.items() if k != "op"}
99
+ return Card(**props)
100
+
101
+
102
+ def op_card_save(cel: dict, ctx: dict):
103
+ """CEL: card:save — persist a Card (or value) under a handle (versioned)."""
104
+ from clm.cross_cutting.mcard import Card
105
+ repo = ctx.get("repo")
106
+ if repo is None:
107
+ raise RuntimeError("card:save requires repo in context")
108
+ card = _resolve_arg(cel["card"], ctx)
109
+ if not isinstance(card, Card):
110
+ card = Card(token_type="mcard", content=str(card))
111
+ handle = _resolve_arg(cel.get("handle", f"cel:card:{card.compute_hash()[:16]}"), ctx)
112
+ pillar = _resolve_arg(cel.get("pillar", "execution_log"), ctx)
113
+ repo.save_card_and_handle(handle, card.compute_hash(), card.to_yaml(), pillar,
114
+ timestamp=card.g_time, replace=True)
115
+ return card.compute_hash()
116
+
117
+
118
+ def op_card_load(cel: dict, ctx: dict):
119
+ """CEL: card:load — retrieve a card by hash."""
120
+ from clm.cross_cutting.mcard import Card
121
+ card_hash = _resolve_arg(cel["hash"], ctx)
122
+ pillar = _resolve_arg(cel.get("pillar", "execution_log"), ctx)
123
+ content = _resolve_card_content(ctx, card_hash, pillar)
124
+ if not content:
125
+ raise ValueError(f"Card not found for hash: {card_hash}")
126
+ return Card.from_yaml(content)
127
+
128
+
129
+ def op_handle_resolve(cel: dict, ctx: dict):
130
+ """CEL: handle:resolve — logical handle -> current Card."""
131
+ from clm.cross_cutting.mcard import Card
132
+ handle = _resolve_arg(cel["handle"], ctx)
133
+ pillar = _resolve_arg(cel.get("pillar", "execution_log"), ctx)
134
+ h = _resolve_handle_hash(ctx, handle, pillar)
135
+ if not h:
136
+ raise ValueError(f"Handle not registered: {handle}")
137
+ content = _resolve_card_content(ctx, h, pillar)
138
+ return Card.from_yaml(content) if content else None
139
+
140
+
141
+ def op_handle_get_history(cel: dict, ctx: dict):
142
+ """CEL: handle:get_history — bitemporal version list of a handle."""
143
+ repo = ctx.get("repo")
144
+ handle = _resolve_arg(cel["handle"], ctx)
145
+ pillar = _resolve_arg(cel.get("pillar", "execution_log"), ctx)
146
+ if repo is not None and hasattr(repo, "get_handle_versions"):
147
+ return repo.get_handle_versions(handle, pillar)
148
+ raw = ctx["db"].execute(pillar,
149
+ "SELECT previous_hash, changed_at FROM handle_history WHERE handle = ? ORDER BY changed_at ASC;",
150
+ [handle])
151
+ history = []
152
+ for line in (raw or "").strip().split("\n"):
153
+ if not line.strip():
154
+ continue
155
+ prev, changed = line.split("|", 1)
156
+ history.append({"hash": prev, "changed_at": changed})
157
+ return history
@@ -0,0 +1,99 @@
1
+ """CEL environment and file ops (Sprint 133.2).
2
+
3
+ Extracted from ``cel_ops.py``. Operations for env vars, file I/O, and
4
+ subprocess test execution — all routed through EffectRegistry when available.
5
+ """
6
+ import os
7
+ import shutil
8
+
9
+ from clm.cross_cutting.cel_engine import _evaluate_cel, _resolve_arg
10
+
11
+
12
+ def op_write_file(cel: dict, ctx: dict):
13
+ path = _resolve_arg(cel["path"], ctx)
14
+ content = _resolve_arg(cel["content"], ctx)
15
+ effects = ctx.get("effects")
16
+ if effects is not None:
17
+ return effects.execute("write_file", path, content)
18
+ return ctx["write_file"](path, content)
19
+
20
+
21
+ def op_set_env(cel: dict, ctx: dict):
22
+ var = cel["var"]
23
+ value = str(_resolve_arg(cel["value"], ctx))
24
+ effects = ctx.get("effects")
25
+ if effects is not None:
26
+ effects.execute("set_env", var, value)
27
+ return True
28
+ os.environ[var] = value
29
+ return True
30
+
31
+
32
+ def op_unset_env(cel: dict, ctx: dict):
33
+ var = cel["var"]
34
+ effects = ctx.get("effects")
35
+ if effects is not None:
36
+ effects.execute("unset_env", var)
37
+ return True
38
+ os.environ.pop(var, None)
39
+ return True
40
+
41
+
42
+ def op_sleep(cel: dict, ctx: dict):
43
+ import time
44
+ seconds = float(cel["seconds"])
45
+ time.sleep(seconds)
46
+ return True
47
+
48
+
49
+ def op_read_file(cel: dict, ctx: dict):
50
+ path = _resolve_arg(cel["path"], ctx)
51
+ with open(path, encoding="utf-8") as f:
52
+ return f.read()
53
+
54
+
55
+ def op_copy_file(cel: dict, ctx: dict):
56
+ src = _resolve_arg(cel["src"], ctx)
57
+ dest = _resolve_arg(cel["dest"], ctx)
58
+ effects = ctx.get("effects")
59
+ if effects is not None:
60
+ return effects.execute("copy_file", src, dest)
61
+ shutil.copy(src, dest)
62
+ return True
63
+
64
+
65
+ def op_run_test_suite(cel: dict, ctx: dict):
66
+ test_file = _resolve_arg(cel["test_file"], ctx)
67
+ test_path = os.path.join(ctx.get("clm_root", ""), test_file)
68
+ env = {k: str(_resolve_arg(v, ctx)) for k, v in cel.get("env", {}).items()}
69
+ timeout = cel.get("timeout", 300)
70
+ effects = ctx.get("effects")
71
+ if effects is not None:
72
+ return effects.execute("run_test_suite", test_path, ctx.get("clm_root", ""), env, timeout)
73
+ import subprocess
74
+
75
+ from clm.cross_cutting._test_runner import build_test_command
76
+ cmd_env = os.environ.copy()
77
+ cmd_env.update(env)
78
+ cmd = build_test_command(test_path, ctx.get("clm_root", ""))
79
+ proc = subprocess.Popen(cmd, env=cmd_env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
80
+ try:
81
+ proc.communicate(timeout=timeout)
82
+ except subprocess.TimeoutExpired:
83
+ proc.kill()
84
+ proc.communicate()
85
+ return proc.returncode
86
+
87
+
88
+ def op_capture_output(cel: dict, ctx: dict):
89
+ import io
90
+ from contextlib import redirect_stderr, redirect_stdout
91
+ stream = cel.get("stream", "stdout")
92
+ buf = io.StringIO()
93
+ if stream == "stderr":
94
+ with redirect_stderr(buf):
95
+ _evaluate_cel(cel["operation"], ctx)
96
+ else:
97
+ with redirect_stdout(buf):
98
+ _evaluate_cel(cel["operation"], ctx)
99
+ return buf.getvalue()
@@ -0,0 +1,71 @@
1
+ """CEL state and data-structure ops (Sprint 133.2).
2
+
3
+ Extracted from ``cel_ops.py``. Operations for storing/retrieving values,
4
+ accessing dict/list elements, and type inspection.
5
+ """
6
+ import json
7
+
8
+ from clm.cross_cutting.cel_engine import _evaluate_cel, _resolve_arg
9
+
10
+
11
+ def op_value_ref(cel: dict, ctx: dict):
12
+ name = cel["name"]
13
+ return ctx.get(name, name)
14
+
15
+
16
+ def op_store(cel: dict, ctx: dict):
17
+ key = cel["key"]
18
+ value = _resolve_arg(cel["value"], ctx)
19
+ ctx["store"](key, value)
20
+ return value
21
+
22
+
23
+ def op_retrieve(cel: dict, ctx: dict):
24
+ return ctx["retrieve"](cel["key"])
25
+
26
+
27
+ def op_dict_get(cel: dict, ctx: dict):
28
+ value = _resolve_arg(cel["value"], ctx)
29
+ key = _resolve_arg(cel["key"], ctx)
30
+ default = _resolve_arg(cel.get("default"), ctx) if "default" in cel else None
31
+ return value.get(key, default) if isinstance(value, dict) else value[key]
32
+
33
+
34
+ def op_index(cel: dict, ctx: dict):
35
+ value = _resolve_arg(cel["value"], ctx)
36
+ position = int(_resolve_arg(cel["position"], ctx))
37
+ return value[position]
38
+
39
+
40
+ def op_len(cel: dict, ctx: dict):
41
+ value = _resolve_arg(cel["value"], ctx)
42
+ return len(value)
43
+
44
+
45
+ def op_not_null(cel: dict, ctx: dict):
46
+ return _resolve_arg(cel["value"], ctx) is not None
47
+
48
+
49
+ def op_callable_check(cel: dict, ctx: dict):
50
+ return callable(_resolve_arg(cel["value"], ctx))
51
+
52
+
53
+ def op_json_parse(cel: dict, ctx: dict):
54
+ return json.loads(_resolve_arg(cel["value"], ctx))
55
+
56
+
57
+ def op_int_cast(cel: dict, ctx: dict):
58
+ return int(_resolve_arg(cel["value"], ctx))
59
+
60
+
61
+ def op_or_fallback(cel: dict, ctx: dict):
62
+ value = _resolve_arg(cel["value"], ctx)
63
+ fallback = _resolve_arg(cel["fallback"], ctx)
64
+ return value if value else fallback
65
+
66
+
67
+ def op_sequence(cel: dict, ctx: dict):
68
+ result = None
69
+ for step in cel["steps"]:
70
+ result = _evaluate_cel(step, ctx)
71
+ return result if result is not None else True