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.
- clm/__init__.py +230 -0
- clm/__main__.py +25 -0
- clm/cross_cutting/__init__.py +50 -0
- clm/cross_cutting/_test_runner.py +36 -0
- clm/cross_cutting/async_utils.py +25 -0
- clm/cross_cutting/cel_builder.py +123 -0
- clm/cross_cutting/cel_converter.py +697 -0
- clm/cross_cutting/cel_engine.py +252 -0
- clm/cross_cutting/cel_ops.py +72 -0
- clm/cross_cutting/cel_ops_db.py +157 -0
- clm/cross_cutting/cel_ops_files.py +99 -0
- clm/cross_cutting/cel_ops_state.py +71 -0
- clm/cross_cutting/cel_ops_strings.py +64 -0
- clm/cross_cutting/cel_resolver.py +63 -0
- clm/cross_cutting/cel_slices.py +56 -0
- clm/cross_cutting/cel_store.py +330 -0
- clm/cross_cutting/clm_logger.py +140 -0
- clm/cross_cutting/config/__init__.py +155 -0
- clm/cross_cutting/config/config_constants.py +392 -0
- clm/cross_cutting/config/env_parameters.py +90 -0
- clm/cross_cutting/config/logging.py +378 -0
- clm/cross_cutting/config/settings.py +504 -0
- clm/cross_cutting/context.py +5 -0
- clm/cross_cutting/domain_models.py +55 -0
- clm/cross_cutting/domain_types.py +61 -0
- clm/cross_cutting/effects.py +67 -0
- clm/cross_cutting/errors.py +153 -0
- clm/cross_cutting/governance.py +331 -0
- clm/cross_cutting/guards.py +41 -0
- clm/cross_cutting/mcard.py +321 -0
- clm/cross_cutting/native.py +185 -0
- clm/cross_cutting/observability.py +289 -0
- clm/cross_cutting/pcard.py +434 -0
- clm/cross_cutting/registry.py +71 -0
- clm/cross_cutting/resources.py +84 -0
- clm/cross_cutting/secrets.py +44 -0
- clm/cross_cutting/subprocess_utils.py +118 -0
- clm/cross_cutting/telemetry.py +164 -0
- clm/cross_cutting/testing/__init__.py +8 -0
- clm/cross_cutting/testing/comparator.py +187 -0
- clm/cross_cutting/testing/datasets.py +87 -0
- clm/cross_cutting/timeutil.py +20 -0
- clm/cross_cutting/types/new_type_check.py +0 -0
- clm/cross_cutting/utils/__init__.py +1 -0
- clm/cross_cutting/utils/url_safety.py +50 -0
- clm/cross_cutting/vcard.py +560 -0
- clm/cross_cutting/writer.py +118 -0
- clm/layer0/README.md +50 -0
- clm/layer0/__init__.py +223 -0
- clm/layer0/algebra.py +60 -0
- clm/layer0/algebra_baldwin.py +133 -0
- clm/layer0/algebra_baldwin_generators.py +36 -0
- clm/layer0/algebra_equivalence.py +262 -0
- clm/layer0/algebra_ingest.py +306 -0
- clm/layer0/algebra_manifest_io.py +156 -0
- clm/layer0/assembler.py +481 -0
- clm/layer0/cel/__init__.py +23 -0
- clm/layer0/cel_eval.py +491 -0
- clm/layer0/codec.py +106 -0
- clm/layer0/context.py +122 -0
- clm/layer0/db.py +160 -0
- clm/layer0/exceptions.py +73 -0
- clm/layer0/fibration.py +106 -0
- clm/layer0/gates.py +50 -0
- clm/layer0/hash.py +80 -0
- clm/layer0/loader.py +826 -0
- clm/layer0/math_utils.py +163 -0
- clm/layer0/mcard.py +96 -0
- clm/layer0/model/__init__.py +0 -0
- clm/layer0/model/action.py +223 -0
- clm/layer0/model/card.py +280 -0
- clm/layer0/model/card_triad.py +427 -0
- clm/layer0/model/compatibility.py +60 -0
- clm/layer0/model/dictionary.py +46 -0
- clm/layer0/model/dots.py +331 -0
- clm/layer0/model/event_producer.py +88 -0
- clm/layer0/model/g_time.py +89 -0
- clm/layer0/model/handle.py +163 -0
- clm/layer0/model/hash/__init__.py +6 -0
- clm/layer0/model/hash/algorithms/__init__.py +3 -0
- clm/layer0/model/hash/algorithms/custom_hash.py +7 -0
- clm/layer0/model/hash/algorithms/local_sha256.py +178 -0
- clm/layer0/model/hash/constants.py +53 -0
- clm/layer0/model/hash/enums.py +60 -0
- clm/layer0/model/hash/validator.py +307 -0
- clm/layer0/model/interpreter.py +275 -0
- clm/layer0/model/pagination.py +45 -0
- clm/layer0/model/pcard.py +716 -0
- clm/layer0/model/schema.py +44 -0
- clm/layer0/model/utils/__init__.py +1 -0
- clm/layer0/model/utils/content_analyzer.py +152 -0
- clm/layer0/model/validators/__init__.py +1 -0
- clm/layer0/model/validators/base_validator.py +44 -0
- clm/layer0/model/validators/binary_validator.py +66 -0
- clm/layer0/model/validators/text_validator.py +82 -0
- clm/layer0/model/validators/validation_registry.py +49 -0
- clm/layer0/model/vcard.py +823 -0
- clm/layer0/model/vcard_ext/__init__.py +39 -0
- clm/layer0/model/vcard_ext/core.py +60 -0
- clm/layer0/model/vcard_ext/network.py +23 -0
- clm/layer0/model/vcard_ext/observability.py +63 -0
- clm/layer0/model/vcard_ext/storage.py +60 -0
- clm/layer0/model/vcard_ext/vendors.py +211 -0
- clm/layer0/model/vcard_sandwich.py +119 -0
- clm/layer0/model/vcard_vocabulary.py +463 -0
- clm/layer0/model/workflow.py +274 -0
- clm/layer0/narrative.py +431 -0
- clm/layer0/ontology/collection.py +125 -0
- clm/layer0/parser.py +429 -0
- clm/layer0/relations.py +73 -0
- clm/layer0/rule_evaluator.py +251 -0
- clm/layer0/schema/__init__.py +38 -0
- clm/layer0/schema/ddl.py +97 -0
- clm/layer0/templates/abstract.yaml +99 -0
- clm/layer0/templates/balanced.yaml +253 -0
- clm/layer0/templates/concrete.yaml +172 -0
- clm/layer0/type_registry.py +223 -0
- clm/layer0/types.py +204 -0
- clm/layer0/utils.py +886 -0
- clm/layer0/verifier_core.py +262 -0
- clm/layer1/__init__.py +31 -0
- clm/layer1/action_dispatcher.py +275 -0
- clm/layer1/adapter_registry.py +233 -0
- clm/layer1/algebra_baldwin_generators.py +192 -0
- clm/layer1/arrow_compiler.py +525 -0
- clm/layer1/batch_processor.py +152 -0
- clm/layer1/cel/__init__.py +27 -0
- clm/layer1/cel/builder.py +173 -0
- clm/layer1/engine.py +759 -0
- clm/layer1/engine_async.py +89 -0
- clm/layer1/eval_callback.py +49 -0
- clm/layer1/gate_composer.py +229 -0
- clm/layer1/hoare_sandwich.py +298 -0
- clm/layer1/identity_guard.py +348 -0
- clm/layer1/matrix.py +138 -0
- clm/layer1/neural_runtime.py +221 -0
- clm/layer1/operations/__init__.py +123 -0
- clm/layer1/operations/augment.py +156 -0
- clm/layer1/operations/base.py +109 -0
- clm/layer1/operations/builtins.py +338 -0
- clm/layer1/operations/dispatch.py +144 -0
- clm/layer1/operations/exclude.py +107 -0
- clm/layer1/operations/handle.py +197 -0
- clm/layer1/operations/identity.py +139 -0
- clm/layer1/operations/invert.py +66 -0
- clm/layer1/operations/loader.py +116 -0
- clm/layer1/operations/manipulators/__init__.py +23 -0
- clm/layer1/operations/manipulators/document.py +211 -0
- clm/layer1/operations/manipulators/media.py +134 -0
- clm/layer1/operations/manipulators/structured.py +159 -0
- clm/layer1/operations/manipulators/tabular.py +334 -0
- clm/layer1/operations/port.py +65 -0
- clm/layer1/operations/services.py +529 -0
- clm/layer1/operations/split.py +117 -0
- clm/layer1/operations/substitute.py +75 -0
- clm/layer1/petri.py +1052 -0
- clm/layer1/pocketflow_orchestrator.py +408 -0
- clm/layer1/pocketflow_scheduler.py +519 -0
- clm/layer1/protocols.py +197 -0
- clm/layer1/runtime_adapter.py +718 -0
- clm/layer1/runtimes/__init__.py +71 -0
- clm/layer1/runtimes/_generated_assertions.py +125 -0
- clm/layer1/runtimes/_generated_types.py +42 -0
- clm/layer1/runtimes/base.py +265 -0
- clm/layer1/runtimes/binary.py +101 -0
- clm/layer1/runtimes/factory.py +203 -0
- clm/layer1/runtimes/javascript.py +321 -0
- clm/layer1/runtimes/javascript_runtime.js +33 -0
- clm/layer1/runtimes/js_scripts/isomorphism.mjs +22 -0
- clm/layer1/runtimes/js_scripts/loader.mjs +52 -0
- clm/layer1/runtimes/lambda_calc.py +126 -0
- clm/layer1/runtimes/python.py +288 -0
- clm/layer1/runtimes/python_runtime.py +41 -0
- clm/layer1/runtimes/script.py +114 -0
- clm/layer1/sandbox.py +334 -0
- clm/layer1/savepoint_guard.py +136 -0
- clm/layer1/sparse_net.py +122 -0
- clm/layer1/spec_tester.py +165 -0
- clm/layer1/store.py +295 -0
- clm/layer1/test_runner.py +498 -0
- clm/layer1/type_inspector.py +166 -0
- clm/layer1/verifier.py +772 -0
- clm/layer1/vm.py +91 -0
- clm/layer2/__init__.py +78 -0
- clm/layer2/card_collection.py +678 -0
- clm/layer2/classifier.py +479 -0
- clm/layer2/engine/__init__.py +1 -0
- clm/layer2/engine/abstract_sql_engine.py +320 -0
- clm/layer2/engine/base.py +193 -0
- clm/layer2/engine/duckdb_engine.py +576 -0
- clm/layer2/engine/sqlite_engine.py +518 -0
- clm/layer2/fiber/__init__.py +31 -0
- clm/layer2/fiber/context.py +89 -0
- clm/layer2/fiber/disposable.py +163 -0
- clm/layer2/fiber/fiber.py +125 -0
- clm/layer2/fiber/hoare.py +191 -0
- clm/layer2/file_io.py +376 -0
- clm/layer2/file_registrar.py +77 -0
- clm/layer2/ingest.py +202 -0
- clm/layer2/lineage.py +322 -0
- clm/layer2/mcard_fs.py +262 -0
- clm/layer2/merkle.py +11 -0
- clm/layer2/mime.py +244 -0
- clm/layer2/mime_detector.py +245 -0
- clm/layer2/private_collection.py +118 -0
- clm/layer2/shared_store.py +11 -0
- clm/layer2/storage/__init__.py +190 -0
- clm/layer2/storage/db_bitemporal.py +141 -0
- clm/layer2/storage/db_driver.py +331 -0
- clm/layer2/storage/db_hyperlinks.py +214 -0
- clm/layer2/storage/db_pool.py +139 -0
- clm/layer2/storage/db_repository.py +229 -0
- clm/layer2/storage/savepoint.py +78 -0
- clm/layer2/storage/sqlite_wal.py +74 -0
- clm/layer2/tridb.py +245 -0
- clm/layer2/vcard_receipt.py +71 -0
- clm/layer2/vfs.py +17 -0
- clm/layer3/__init__.py +141 -0
- clm/layer3/agency/__init__.py +19 -0
- clm/layer3/agency/config.py +220 -0
- clm/layer3/agency/providers/__init__.py +15 -0
- clm/layer3/agency/providers/base.py +91 -0
- clm/layer3/agency/providers/mlc_llm.py +169 -0
- clm/layer3/agency/providers/ollama.py +203 -0
- clm/layer3/agency/router.py +71 -0
- clm/layer3/agency/runtime.py +524 -0
- clm/layer3/gateway.py +353 -0
- clm/layer3/open_interpreter_adapter.py +159 -0
- clm/layer3/rag/__init__.py +70 -0
- clm/layer3/rag/cli.py +228 -0
- clm/layer3/rag/config.py +153 -0
- clm/layer3/rag/embeddings/__init__.py +16 -0
- clm/layer3/rag/embeddings/base.py +79 -0
- clm/layer3/rag/embeddings/ollama.py +211 -0
- clm/layer3/rag/embeddings/vision.py +290 -0
- clm/layer3/rag/engine.py +276 -0
- clm/layer3/rag/graph/__init__.py +22 -0
- clm/layer3/rag/graph/community.py +198 -0
- clm/layer3/rag/graph/engine.py +433 -0
- clm/layer3/rag/graph/extractor.py +255 -0
- clm/layer3/rag/graph/schema.py +115 -0
- clm/layer3/rag/graph/store.py +641 -0
- clm/layer3/rag/indexer.py +333 -0
- clm/layer3/rag/llm_providers.json +52 -0
- clm/layer3/rag/semantic_versioning.py +313 -0
- clm/layer3/rag/vector/__init__.py +28 -0
- clm/layer3/rag/vector/handle_vector_store.py +749 -0
- clm/layer3/rag/vector/schema.py +151 -0
- clm/layer3/rag/vector/store.py +632 -0
- clm/layer3/reticulum/__init__.py +55 -0
- clm/layer3/reticulum/bridge.py +74 -0
- clm/layer3/reticulum/daemon.py +88 -0
- clm/layer3/reticulum/discovery.py +164 -0
- clm/layer3/reticulum/identity.py +107 -0
- clm/layer3/reticulum/media.py +148 -0
- clm/layer3/reticulum/transport.py +133 -0
- clm/layer3/satori/__init__.py +209 -0
- clm/layer3/satori/alpha_conversion.py +168 -0
- clm/layer3/satori/beta_reduction.py +359 -0
- clm/layer3/satori/continuation.py +38 -0
- clm/layer3/satori/eta_conversion.py +205 -0
- clm/layer3/satori/free_variables.py +163 -0
- clm/layer3/satori/io_effects.py +370 -0
- clm/layer3/satori/lambda_runtime.py +726 -0
- clm/layer3/satori/lambda_term.py +230 -0
- clm/layer3/satori/sheaf_audit.py +261 -0
- clm/layer3/satori/speech_act.py +279 -0
- clm/layer4/README.md +9 -0
- clm/layer4/__init__.py +67 -0
- clm/layer4/_sovereign_executor.py +580 -0
- clm/layer4/action_commands.py +719 -0
- clm/layer4/api.py +236 -0
- clm/layer4/bootstrap/__init__.py +13 -0
- clm/layer4/bootstrap/bootstrapper.py +118 -0
- clm/layer4/bootstrap/cleanup.py +44 -0
- clm/layer4/bootstrap/clm_hooks.py +49 -0
- clm/layer4/bootstrap/genesis.py +77 -0
- clm/layer4/bootstrap/injection.py +125 -0
- clm/layer4/cli.py +549 -0
- clm/layer4/collection_manager.py +553 -0
- clm/layer4/commands/__init__.py +81 -0
- clm/layer4/commands/action_cmd.py +42 -0
- clm/layer4/commands/check_cmd.py +82 -0
- clm/layer4/commands/envelope.py +73 -0
- clm/layer4/commands/evaluate_cmd.py +224 -0
- clm/layer4/commands/mcard_cmd.py +24 -0
- clm/layer4/commands/petri_cmd.py +92 -0
- clm/layer4/commands/tridb_cmd.py +319 -0
- clm/layer4/commands/version_cmd.py +27 -0
- clm/layer4/diff_commands.py +167 -0
- clm/layer4/eoa_sponsor.py +232 -0
- clm/layer4/events.py +69 -0
- clm/layer4/flux_gateway.py +133 -0
- clm/layer4/gateway.py +91 -0
- clm/layer4/loader.py +307 -0
- clm/layer4/mcard_commands.py +166 -0
- clm/layer4/membrane.py +273 -0
- clm/layer4/navigation.py +50 -0
- clm/layer4/protocol/protocol_entry_python.py +108 -0
- clm/layer4/protocol/python_sync.py +126 -0
- clm/layer4/runner.py +150 -0
- clm/layer4/satori.py +262 -0
- clm/layer4/server/python_sync_client.py +150 -0
- clm/layer4/server/python_sync_server.py +139 -0
- clm/layer4/server/service_api.py +315 -0
- clm/layer4/server/start_servers.py +107 -0
- clm/layer4/server/stop_servers.py +24 -0
- clm/layer4/simulated_optimization_fix.py +0 -0
- clm/layer4/storage.py +463 -0
- clm/layer4/tridb_admin.py +268 -0
- clm/layer4/websocket/test_ws_client.py +104 -0
- clm/layer4/websocket/test_ws_server.py +138 -0
- clm/layer4/websocket/ws_server_local.py +160 -0
- clm/layer5/__init__.py +51 -0
- clm/layer5/async_batch_writer.py +133 -0
- clm/layer5/learning.py +276 -0
- clm/layer5/telemetry_hook.py +107 -0
- clm/layer5/type_lattice.py +238 -0
- clm/py.typed +0 -0
- clm/schemas/__init__.py +652 -0
- clm/schemas/abstract_face_schema.yaml +77 -0
- clm/schemas/cel_schema.yaml +416 -0
- clm/schemas/lifecycle/__init__.py +45 -0
- clm/schemas/lifecycle/models.py +123 -0
- clm/schemas/lifecycle/service.py +364 -0
- clm/schemas/mcard_schema.sql +192 -0
- clm/schemas/mcard_vector_schema.sql +283 -0
- clm/schemas/mlp-context-v1.json +91 -0
- clm_kernel-0.0.0.dist-info/METADATA +121 -0
- clm_kernel-0.0.0.dist-info/RECORD +333 -0
- clm_kernel-0.0.0.dist-info/WHEEL +4 -0
- clm_kernel-0.0.0.dist-info/entry_points.txt +3 -0
- 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
|