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,89 @@
|
|
|
1
|
+
"""AsyncPTREngine - Asynchronous interface for the Polynomial Type Runtime (PTR)
|
|
2
|
+
|
|
3
|
+
Provides native async/await methods by delegating to the existing synchronous
|
|
4
|
+
PTREngine using asyncio.to_thread dynamically.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from clm.layer1.engine import ExecutionResult, PTREngine
|
|
13
|
+
from clm.layer1.store import PTRSharedStore
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AsyncPTREngine:
|
|
17
|
+
"""Asynchronous wrapper for PTREngine.
|
|
18
|
+
|
|
19
|
+
Dynamically proxies all synchronous PTREngine methods into asyncio thread pool.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
storage_collection: Any | None = None,
|
|
25
|
+
execution_log_collection: Any | None = None,
|
|
26
|
+
identity_space_collection: Any | None = None,
|
|
27
|
+
**engine_kwargs: Any,
|
|
28
|
+
) -> None:
|
|
29
|
+
self._engine = PTREngine(
|
|
30
|
+
storage_collection=storage_collection,
|
|
31
|
+
execution_log_collection=execution_log_collection,
|
|
32
|
+
identity_space_collection=identity_space_collection,
|
|
33
|
+
**engine_kwargs,
|
|
34
|
+
)
|
|
35
|
+
# Preserve public properties
|
|
36
|
+
self.logger = self._engine.logger
|
|
37
|
+
self.correctness_tracker = self._engine.correctness_tracker
|
|
38
|
+
self.collection = self._engine.collection
|
|
39
|
+
|
|
40
|
+
async def execute_pcard(
|
|
41
|
+
self,
|
|
42
|
+
pcard_hash: str,
|
|
43
|
+
target_hash: str,
|
|
44
|
+
context: dict[str, Any] | PTRSharedStore | None = None,
|
|
45
|
+
specification_embedding: list[float] | None = None,
|
|
46
|
+
) -> ExecutionResult:
|
|
47
|
+
"""Fast-path execute_pcard with optional artifact preloading."""
|
|
48
|
+
# Async prefetch artifacts using CardCollection.aget when available
|
|
49
|
+
store = getattr(self._engine, "collection", None)
|
|
50
|
+
|
|
51
|
+
if isinstance(context, PTRSharedStore):
|
|
52
|
+
pre_context = context
|
|
53
|
+
if store and hasattr(store, "aget"):
|
|
54
|
+
pcard = await store.aget(pcard_hash)
|
|
55
|
+
target = await store.aget(target_hash)
|
|
56
|
+
pre_context.inputs["_preloaded_pcard"] = pcard
|
|
57
|
+
pre_context.inputs["_preloaded_target"] = target
|
|
58
|
+
else:
|
|
59
|
+
pre_context = dict(context or {})
|
|
60
|
+
if store and hasattr(store, "aget"):
|
|
61
|
+
pcard = await store.aget(pcard_hash)
|
|
62
|
+
target = await store.aget(target_hash)
|
|
63
|
+
pre_context["_preloaded_pcard"] = pcard
|
|
64
|
+
pre_context["_preloaded_target"] = target
|
|
65
|
+
|
|
66
|
+
return await asyncio.to_thread(
|
|
67
|
+
self._engine.execute_pcard,
|
|
68
|
+
pcard_hash,
|
|
69
|
+
target_hash,
|
|
70
|
+
pre_context,
|
|
71
|
+
specification_embedding,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def __getattr__(self, name: str) -> Any:
|
|
75
|
+
"""Dynamically propagate all other methods from PTREngine.
|
|
76
|
+
|
|
77
|
+
Automatically wraps callable methods in asyncio.to_thread to make them asynchronous.
|
|
78
|
+
"""
|
|
79
|
+
attr = getattr(self._engine, name)
|
|
80
|
+
|
|
81
|
+
# If it's not a method or it's a private attribute, return as-is
|
|
82
|
+
if not callable(attr) or name.startswith("_"):
|
|
83
|
+
return attr
|
|
84
|
+
|
|
85
|
+
# Create an async wrapper for the synchronous method
|
|
86
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
87
|
+
return await asyncio.to_thread(attr, *args, **kwargs)
|
|
88
|
+
|
|
89
|
+
return async_wrapper
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Evaluation Callback Registry — Dependency inversion for Layer 1.
|
|
2
|
+
|
|
3
|
+
Sprint 143 V-2/V-3 fix: Layer 1 (petri.py, runtime_adapter.py) was importing
|
|
4
|
+
evaluate_cmd from higher layers, creating upward dependencies that violate
|
|
5
|
+
the Type Lattice SSOT principle (type_lattice.md §4.5).
|
|
6
|
+
|
|
7
|
+
This module provides a registry where higher layers can register their evaluate_cmd
|
|
8
|
+
function at startup. Layer 1 calls get_evaluate_callback() to retrieve it,
|
|
9
|
+
avoiding any direct upward import.
|
|
10
|
+
|
|
11
|
+
Usage in higher layers:
|
|
12
|
+
from clm.layer1.eval_callback import register_evaluate_callback
|
|
13
|
+
register_evaluate_callback(evaluate_cmd)
|
|
14
|
+
|
|
15
|
+
Usage in Layer 1:
|
|
16
|
+
from clm.layer1.eval_callback import get_evaluate_callback
|
|
17
|
+
cb = get_evaluate_callback()
|
|
18
|
+
if cb:
|
|
19
|
+
rc = cb(clm_root, clm_file, extra_args)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from typing import Callable, Optional
|
|
23
|
+
|
|
24
|
+
# Type alias: evaluate_cmd(clm_root: str, clm_file: str, extra_args: list[str]) -> int
|
|
25
|
+
EvaluateFn = Callable[[str, str, list[str]], int]
|
|
26
|
+
|
|
27
|
+
_callback: Optional[EvaluateFn] = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def register_evaluate_callback(fn: EvaluateFn) -> None:
|
|
31
|
+
"""Register the evaluate_cmd function from Layer 3."""
|
|
32
|
+
global _callback
|
|
33
|
+
_callback = fn
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_evaluate_callback() -> Optional[EvaluateFn]:
|
|
37
|
+
"""Retrieve the registered evaluate_cmd, or None if not yet registered."""
|
|
38
|
+
return _callback
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def evaluate_or_fail(clm_root: str, clm_file: str, extra_args: list[str]) -> int:
|
|
42
|
+
"""Call the registered evaluate_cmd, or raise if no callback is registered."""
|
|
43
|
+
cb = get_evaluate_callback()
|
|
44
|
+
if cb is None:
|
|
45
|
+
raise RuntimeError(
|
|
46
|
+
"No evaluate callback registered. "
|
|
47
|
+
"Layer 3 must call register_evaluate_callback() at import time."
|
|
48
|
+
)
|
|
49
|
+
return cb(clm_root, clm_file, extra_args)
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"""CLM Gate Composer — Gate PCard composition and nested evaluation.
|
|
2
|
+
|
|
3
|
+
Handles the sequencing and execution of pre/post gate PCards
|
|
4
|
+
in the CLM evaluation lifecycle:
|
|
5
|
+
- compose_conditions_cmd: resolve and run pre/post gate PCards in sequence
|
|
6
|
+
- run_nested_evaluate: execute a gate PCard in an isolated subprocess sandbox
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import threading
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
|
|
15
|
+
from clm.cross_cutting.clm_logger import get_logger
|
|
16
|
+
from clm.layer0.utils import resolve_clm_path
|
|
17
|
+
|
|
18
|
+
from .verifier import get_load_manifest, validate_postcondition, verify_precondition
|
|
19
|
+
|
|
20
|
+
_logger = get_logger("clm.layer1.gate_composer")
|
|
21
|
+
HASH_PREFIX = "sha256:"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def run_nested_evaluate(clm_root: str, gate_file: str, target_clm: str, exit_code: int | None = None) -> int:
|
|
25
|
+
"""
|
|
26
|
+
Runs a nested evaluation of a gate PCard in an isolated sandbox.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
clm_root (str): The root directory of the CLM workspace.
|
|
30
|
+
gate_file (str): The gate PCard to evaluate.
|
|
31
|
+
target_clm (str): The target PCard being gated.
|
|
32
|
+
exit_code (Optional[int]): Optional exit code context for post-gates.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
int: The return code of the nested sandbox.
|
|
36
|
+
"""
|
|
37
|
+
gate_file = resolve_clm_path(clm_root, gate_file)
|
|
38
|
+
target_clm = resolve_clm_path(clm_root, target_clm)
|
|
39
|
+
engine_py = os.path.join(clm_root, "kernel/scripts/clm_runner.py")
|
|
40
|
+
cmd = [sys.executable, engine_py, "evaluate", gate_file, target_clm]
|
|
41
|
+
if exit_code is not None:
|
|
42
|
+
cmd.append(str(exit_code))
|
|
43
|
+
env = os.environ.copy()
|
|
44
|
+
env["CLM_NESTED_EXEC"] = "1"
|
|
45
|
+
env["CLM_NESTED_FILE"] = os.path.abspath(gate_file)
|
|
46
|
+
env["CLM_SKIP_CONDITIONS"] = "1"
|
|
47
|
+
proc = subprocess.Popen(cmd, env=env)
|
|
48
|
+
proc.wait()
|
|
49
|
+
return proc.returncode
|
|
50
|
+
|
|
51
|
+
def compose_conditions_cmd(clm_root: str, clm_file: str, phase: str, exit_code: int = 0) -> int:
|
|
52
|
+
"""
|
|
53
|
+
Composes and runs the sequence of gate PCards for a given execution phase (pre or post).
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
clm_root (str): The root directory of the CLM workspace.
|
|
57
|
+
clm_file (str): The target PCard being executed.
|
|
58
|
+
phase (str): The phase of execution ('pre' or 'post').
|
|
59
|
+
exit_code (int): The exit code from the execution (for 'post' phase).
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
int: The exit code of the gate evaluations. 0 if successful, non-zero otherwise.
|
|
63
|
+
"""
|
|
64
|
+
load_normalized_manifest = get_load_manifest()
|
|
65
|
+
clm_file = resolve_clm_path(clm_root, clm_file)
|
|
66
|
+
custom_pcard = None
|
|
67
|
+
if os.path.exists(clm_file):
|
|
68
|
+
try:
|
|
69
|
+
data = load_normalized_manifest(clm_root, clm_file)
|
|
70
|
+
inner = data.get("clm", data)
|
|
71
|
+
concrete = inner.get("concrete_impl", {})
|
|
72
|
+
if phase == "pre":
|
|
73
|
+
pre = concrete.get("precondition", {})
|
|
74
|
+
if pre:
|
|
75
|
+
custom_pcard = pre.get("vcard_pre")
|
|
76
|
+
elif phase == "post":
|
|
77
|
+
post = concrete.get("postcondition", {})
|
|
78
|
+
if post:
|
|
79
|
+
custom_pcard = post.get("vcard_post")
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
if custom_pcard:
|
|
84
|
+
orig_custom_pcard = custom_pcard
|
|
85
|
+
if custom_pcard.startswith("gate:"):
|
|
86
|
+
gate_name = custom_pcard.split("gate:")[1]
|
|
87
|
+
custom_pcard = f"computable_knowledge/Abstract/layer5_system/bootstrap/governance/gates/{gate_name}.yaml"
|
|
88
|
+
elif custom_pcard.startswith(HASH_PREFIX):
|
|
89
|
+
target = "clm.layer2.storage"
|
|
90
|
+
mod = sys.modules.get(target)
|
|
91
|
+
if mod is None:
|
|
92
|
+
__import__(target)
|
|
93
|
+
mod = sys.modules.get(target)
|
|
94
|
+
if mod is not None:
|
|
95
|
+
db = mod.CLMDB(clm_root)
|
|
96
|
+
repo = mod.CLMRepository(db, clm_root)
|
|
97
|
+
handle = repo.get_handle_by_hash(custom_pcard, "content_memory")
|
|
98
|
+
if handle and handle.startswith("gate:"):
|
|
99
|
+
gate_name = handle.split("gate:")[1]
|
|
100
|
+
custom_pcard = f"computable_knowledge/Abstract/layer5_system/bootstrap/governance/gates/{gate_name}.yaml"
|
|
101
|
+
|
|
102
|
+
resolved_custom_pcard = resolve_clm_path(clm_root, custom_pcard)
|
|
103
|
+
custom_gate = None
|
|
104
|
+
if resolved_custom_pcard and os.path.exists(resolved_custom_pcard):
|
|
105
|
+
try:
|
|
106
|
+
gate_data = load_normalized_manifest(clm_root, resolved_custom_pcard)
|
|
107
|
+
inner_gate = gate_data.get("clm", gate_data)
|
|
108
|
+
custom_gate = inner_gate.get("concrete_impl", {}).get("operation")
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
if custom_gate:
|
|
113
|
+
_logger.info(f"[COMPOSE] Discovered custom {phase} gate: {custom_gate} (from {orig_custom_pcard})")
|
|
114
|
+
|
|
115
|
+
override_gate = os.environ.get("CLM_GATE_OVERRIDE", "0") == "1"
|
|
116
|
+
expr = ""
|
|
117
|
+
if phase == "pre":
|
|
118
|
+
if custom_pcard:
|
|
119
|
+
if override_gate:
|
|
120
|
+
expr = custom_pcard
|
|
121
|
+
elif custom_pcard in ["computable_knowledge/Abstract/layer5_system/bootstrap/evaluator/verify_precondition.yaml", "computable_knowledge/Abstract/layer5_system/bootstrap/governance/gates/payload_nonempty.yaml"]:
|
|
122
|
+
expr = "computable_knowledge/Abstract/layer5_system/bootstrap/evaluator/verify_precondition.yaml"
|
|
123
|
+
else:
|
|
124
|
+
expr = f"computable_knowledge/Abstract/layer5_system/bootstrap/evaluator/verify_precondition.yaml >>> {custom_pcard}"
|
|
125
|
+
else:
|
|
126
|
+
expr = "computable_knowledge/Abstract/layer5_system/bootstrap/evaluator/verify_precondition.yaml"
|
|
127
|
+
else:
|
|
128
|
+
if custom_pcard:
|
|
129
|
+
if override_gate:
|
|
130
|
+
expr = custom_pcard
|
|
131
|
+
elif custom_pcard in ["computable_knowledge/Abstract/layer5_system/bootstrap/evaluator/validate_postcondition.yaml", "computable_knowledge/Abstract/layer5_system/bootstrap/governance/gates/result_sealed.yaml"]:
|
|
132
|
+
expr = "computable_knowledge/Abstract/layer5_system/bootstrap/evaluator/validate_postcondition.yaml"
|
|
133
|
+
else:
|
|
134
|
+
expr = f"computable_knowledge/Abstract/layer5_system/bootstrap/evaluator/validate_postcondition.yaml >>> {custom_pcard}"
|
|
135
|
+
else:
|
|
136
|
+
expr = "computable_knowledge/Abstract/layer5_system/bootstrap/evaluator/validate_postcondition.yaml"
|
|
137
|
+
|
|
138
|
+
seq_gates = [g.strip() for g in expr.split(">>>")]
|
|
139
|
+
for gate in seq_gates:
|
|
140
|
+
if not gate:
|
|
141
|
+
continue
|
|
142
|
+
_logger.info(f"[COMPOSE] Executing Gate: {gate}")
|
|
143
|
+
if gate == "computable_knowledge/Abstract/layer5_system/bootstrap/evaluator/verify_precondition.yaml":
|
|
144
|
+
if not verify_precondition(clm_root, clm_file):
|
|
145
|
+
_logger.info("CRITICAL: Pre-Gate Failed.")
|
|
146
|
+
return 1
|
|
147
|
+
elif gate == "computable_knowledge/Abstract/layer5_system/bootstrap/evaluator/validate_postcondition.yaml":
|
|
148
|
+
if not validate_postcondition(clm_root, clm_file, exit_code):
|
|
149
|
+
_logger.info("CRITICAL: Post-Gate Failed.")
|
|
150
|
+
return 1
|
|
151
|
+
else:
|
|
152
|
+
rc = run_nested_evaluate(clm_root, gate, clm_file, exit_code if phase == "post" else None)
|
|
153
|
+
if rc != 0:
|
|
154
|
+
_logger.info(f"CRITICAL: Gate {gate} Failed with exit code {rc}")
|
|
155
|
+
return rc
|
|
156
|
+
return 0
|
|
157
|
+
|
|
158
|
+
def execute_composed_pcard_py(
|
|
159
|
+
clm_root: str,
|
|
160
|
+
expr: str,
|
|
161
|
+
phase: str,
|
|
162
|
+
exit_code: int = 0,
|
|
163
|
+
payload: str = "",
|
|
164
|
+
lookup: dict[str, Callable] | None = None
|
|
165
|
+
) -> int:
|
|
166
|
+
"""
|
|
167
|
+
Python-native monadic composition executor.
|
|
168
|
+
Supports >>> (sequential), ⊗ (tensor/parallel), and id (passthrough).
|
|
169
|
+
"""
|
|
170
|
+
if lookup is None:
|
|
171
|
+
lookup = {}
|
|
172
|
+
|
|
173
|
+
expr = expr.strip()
|
|
174
|
+
if not expr or expr == "id":
|
|
175
|
+
return 0
|
|
176
|
+
|
|
177
|
+
# 1. Sequential composition (>>>) has lowest precedence (splits first)
|
|
178
|
+
if ">>>" in expr:
|
|
179
|
+
# Split by >>>
|
|
180
|
+
parts = [p.strip() for p in expr.split(">>>")]
|
|
181
|
+
for part in parts:
|
|
182
|
+
if not part:
|
|
183
|
+
continue
|
|
184
|
+
rc = execute_composed_pcard_py(clm_root, part, phase, exit_code, payload, lookup)
|
|
185
|
+
if rc != 0:
|
|
186
|
+
return rc
|
|
187
|
+
return 0
|
|
188
|
+
|
|
189
|
+
# 2. Tensor Product Composition (⊗)
|
|
190
|
+
if "⊗" in expr:
|
|
191
|
+
parts = [p.strip() for p in expr.split("⊗")]
|
|
192
|
+
threads = []
|
|
193
|
+
results = []
|
|
194
|
+
|
|
195
|
+
def worker(part):
|
|
196
|
+
rc = execute_composed_pcard_py(clm_root, part, phase, exit_code, payload, lookup)
|
|
197
|
+
results.append(rc)
|
|
198
|
+
|
|
199
|
+
for part in parts:
|
|
200
|
+
if not part:
|
|
201
|
+
continue
|
|
202
|
+
t = threading.Thread(target=worker, args=(part,))
|
|
203
|
+
threads.append(t)
|
|
204
|
+
t.start()
|
|
205
|
+
|
|
206
|
+
for t in threads:
|
|
207
|
+
t.join()
|
|
208
|
+
|
|
209
|
+
for rc in results:
|
|
210
|
+
if rc != 0:
|
|
211
|
+
return rc
|
|
212
|
+
return 0
|
|
213
|
+
|
|
214
|
+
# 3. Base case: single gate
|
|
215
|
+
gate = expr
|
|
216
|
+
if gate in lookup:
|
|
217
|
+
func = lookup[gate]
|
|
218
|
+
try:
|
|
219
|
+
rc = func(payload) if phase == "pre" else func(exit_code, payload)
|
|
220
|
+
if isinstance(rc, bool):
|
|
221
|
+
return 0 if rc else 1
|
|
222
|
+
if rc is None:
|
|
223
|
+
return 0
|
|
224
|
+
return int(rc)
|
|
225
|
+
except Exception as e:
|
|
226
|
+
_logger.error(f"[COMPOSE] Error running gate function '{gate}': {e}")
|
|
227
|
+
return 1
|
|
228
|
+
|
|
229
|
+
return run_nested_evaluate(clm_root, gate, payload, exit_code if phase == "post" else None)
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""Declarative VCard Hoare Sandwich Engine (Sprint 394 / DEL-394-01).
|
|
2
|
+
|
|
3
|
+
Implements the formal Hoare Triple verification lifecycle:
|
|
4
|
+
{V_pre} Action {V_post}
|
|
5
|
+
Enforces pre-condition invariant gating, operator execution, post-condition lattice
|
|
6
|
+
witness certification, and atomic <1.0ms rollback on violation.
|
|
7
|
+
|
|
8
|
+
Acceptance Criteria:
|
|
9
|
+
- AC-394-01-01: Passing V_pre & V_post completes Z4 lifecycle, outputs certified MCard & VCardReceipt.
|
|
10
|
+
- AC-394-01-02: Failing V_pre aborts with PreconditionBreachError without invoking operator.
|
|
11
|
+
- AC-394-01-03: Failing V_post triggers <1ms atomic rollback, raising PostconditionBreachError.
|
|
12
|
+
|
|
13
|
+
Layer 1 module (no static imports above L1).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import sys
|
|
19
|
+
import time
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from clm.cross_cutting.domain_models import MCard
|
|
25
|
+
from clm.layer0.exceptions import CLMError
|
|
26
|
+
from clm.layer0.model.action import ActionManifest
|
|
27
|
+
from clm.layer0.model.card_triad import compute_canonical_hash
|
|
28
|
+
from clm.layer0.rule_evaluator import SafeRuleEvaluator
|
|
29
|
+
from clm.layer1.action_dispatcher import ActionDispatcher
|
|
30
|
+
from clm.layer1.savepoint_guard import VCardMeetViolation, z4_savepoint_guard
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PreconditionBreachError(CLMError):
|
|
34
|
+
"""Raised when an Action precondition (V_pre) evaluates to false."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class PostconditionBreachError(CLMError):
|
|
38
|
+
"""Raised when an Action postcondition (V_post) evaluates to false, triggering rollback."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class HoareSandwichResult:
|
|
43
|
+
"""Certified result of an Action executed inside the Hoare Sandwich."""
|
|
44
|
+
|
|
45
|
+
status: str
|
|
46
|
+
action_name: str
|
|
47
|
+
outputs: dict[str, MCard] = field(default_factory=dict)
|
|
48
|
+
receipt: MCard | None = None
|
|
49
|
+
precondition_verdicts: list[dict[str, Any]] = field(default_factory=list)
|
|
50
|
+
postcondition_verdicts: list[dict[str, Any]] = field(default_factory=list)
|
|
51
|
+
rollback_occurred: bool = False
|
|
52
|
+
rollback_time_ms: float = 0.0
|
|
53
|
+
metrics: dict[str, Any] = field(default_factory=dict)
|
|
54
|
+
error: str | None = None
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def is_success(self) -> bool:
|
|
58
|
+
return self.status == "success"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class HoareSandwichEngine:
|
|
62
|
+
"""Executes Action manifests within the {V_pre} Action {V_post} Hoare Sandwich."""
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def execute(
|
|
66
|
+
cls,
|
|
67
|
+
manifest: ActionManifest | str | Path | dict[str, Any],
|
|
68
|
+
input_bindings: dict[str, Any] | None = None,
|
|
69
|
+
fs: Any = None,
|
|
70
|
+
) -> HoareSandwichResult:
|
|
71
|
+
"""Execute Action with full pre- and post-condition verification."""
|
|
72
|
+
t_start = time.perf_counter()
|
|
73
|
+
|
|
74
|
+
# 1. Parse manifest
|
|
75
|
+
if isinstance(manifest, (str, Path)):
|
|
76
|
+
action = ActionManifest.from_yaml(manifest)
|
|
77
|
+
elif isinstance(manifest, dict):
|
|
78
|
+
action = ActionManifest.from_dict(manifest)
|
|
79
|
+
elif isinstance(manifest, ActionManifest):
|
|
80
|
+
action = manifest
|
|
81
|
+
else:
|
|
82
|
+
raise TypeError(f"Invalid manifest type: {type(manifest)}")
|
|
83
|
+
|
|
84
|
+
action_hash = compute_canonical_hash(action.to_dict())
|
|
85
|
+
|
|
86
|
+
# 2. Build Precondition Context
|
|
87
|
+
bindings = dict(input_bindings or {})
|
|
88
|
+
if fs is not None:
|
|
89
|
+
for k, v in list(bindings.items()):
|
|
90
|
+
if isinstance(v, str) and (v.startswith("mcard:") or v.startswith("mcard://")):
|
|
91
|
+
clean_uri = v
|
|
92
|
+
if clean_uri.startswith("mcard://"):
|
|
93
|
+
clean_uri = "mcard:" + clean_uri[len("mcard://"):]
|
|
94
|
+
try:
|
|
95
|
+
resolved_card = fs.read_file(clean_uri)
|
|
96
|
+
if resolved_card is not None:
|
|
97
|
+
bindings[k] = resolved_card
|
|
98
|
+
except Exception:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
pre_ctx: dict[str, Any] = {}
|
|
102
|
+
for inp in action.inputs:
|
|
103
|
+
bound_val = bindings.get(inp.name)
|
|
104
|
+
if bound_val is not None:
|
|
105
|
+
card_or_data = bound_val.payload if isinstance(bound_val, MCard) else bound_val
|
|
106
|
+
ctx_info = cls._build_item_context(card_or_data, bound_val)
|
|
107
|
+
pre_ctx[inp.name] = ctx_info
|
|
108
|
+
# Also expose single input as 'input'
|
|
109
|
+
if len(action.inputs) == 1 or inp.name == "dataset":
|
|
110
|
+
pre_ctx["input"] = ctx_info
|
|
111
|
+
|
|
112
|
+
# 3. Phase 1: Evaluate Preconditions (V_pre)
|
|
113
|
+
pre_verdicts: list[dict[str, Any]] = []
|
|
114
|
+
for pre in action.preconditions:
|
|
115
|
+
try:
|
|
116
|
+
passed = SafeRuleEvaluator.evaluate(pre.rule, pre_ctx)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
raise PreconditionBreachError(
|
|
119
|
+
f"Precondition '{pre.invariant}' evaluation error: {e}"
|
|
120
|
+
) from e
|
|
121
|
+
|
|
122
|
+
verdict = {
|
|
123
|
+
"invariant": pre.invariant,
|
|
124
|
+
"rule": pre.rule,
|
|
125
|
+
"passed": passed,
|
|
126
|
+
"description": pre.description,
|
|
127
|
+
}
|
|
128
|
+
pre_verdicts.append(verdict)
|
|
129
|
+
|
|
130
|
+
# AC-394-01-02: Abort immediately prior to operator execution
|
|
131
|
+
if not passed:
|
|
132
|
+
raise PreconditionBreachError(
|
|
133
|
+
f"Precondition '{pre.invariant}' violated: '{pre.rule}' evaluated to false"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# 4. Phase 2 & 3: Savepoint-Guarded Execution and Postcondition Verification
|
|
137
|
+
# If filesystem has a connection, wrap in z4_savepoint_guard
|
|
138
|
+
conn = getattr(fs, "conn", None)
|
|
139
|
+
|
|
140
|
+
if conn is not None:
|
|
141
|
+
# Transactional Savepoint Guard active
|
|
142
|
+
try:
|
|
143
|
+
with z4_savepoint_guard(conn, step_id=f"hoare_{action.clm.name}") as guard:
|
|
144
|
+
exec_result = ActionDispatcher.execute(action, bindings, fs=fs)
|
|
145
|
+
if not exec_result.is_success:
|
|
146
|
+
guard.fail_meet(exec_result.error or "Operator execution failure")
|
|
147
|
+
|
|
148
|
+
# Phase 3: Evaluate Postconditions (V_post)
|
|
149
|
+
post_ctx = cls._build_post_context(pre_ctx, exec_result.outputs)
|
|
150
|
+
post_verdicts = cls._eval_postconditions(action.postconditions, post_ctx)
|
|
151
|
+
|
|
152
|
+
# Advance guard to commit
|
|
153
|
+
guard.advance_phase(3)
|
|
154
|
+
|
|
155
|
+
except VCardMeetViolation as e:
|
|
156
|
+
raise PostconditionBreachError(f"Postcondition breach: {e}") from e
|
|
157
|
+
|
|
158
|
+
else:
|
|
159
|
+
# In-memory execution without direct sqlite connection
|
|
160
|
+
exec_result = ActionDispatcher.execute(action, bindings, fs=fs)
|
|
161
|
+
if not exec_result.is_success:
|
|
162
|
+
raise CLMError(f"Action execution failed: {exec_result.error}")
|
|
163
|
+
|
|
164
|
+
post_ctx = cls._build_post_context(pre_ctx, exec_result.outputs)
|
|
165
|
+
post_verdicts = cls._eval_postconditions(action.postconditions, post_ctx)
|
|
166
|
+
|
|
167
|
+
# 5. Phase 4: Generate VCardReceipt and complete Hoare Sandwich
|
|
168
|
+
receipt = cls._generate_receipt(
|
|
169
|
+
action_name=action.clm.name,
|
|
170
|
+
action_hash=action_hash,
|
|
171
|
+
input_hashes=exec_result.input_hashes,
|
|
172
|
+
output_hashes=exec_result.output_hashes,
|
|
173
|
+
pre_verdicts=pre_verdicts,
|
|
174
|
+
post_verdicts=post_verdicts,
|
|
175
|
+
metrics=exec_result.metrics,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
t_elapsed = (time.perf_counter() - t_start) * 1000.0
|
|
179
|
+
|
|
180
|
+
return HoareSandwichResult(
|
|
181
|
+
status="success",
|
|
182
|
+
action_name=action.clm.name,
|
|
183
|
+
outputs=exec_result.outputs,
|
|
184
|
+
receipt=receipt,
|
|
185
|
+
precondition_verdicts=pre_verdicts,
|
|
186
|
+
postcondition_verdicts=post_verdicts,
|
|
187
|
+
metrics={
|
|
188
|
+
**exec_result.metrics,
|
|
189
|
+
"hoare_total_time_ms": round(t_elapsed, 3),
|
|
190
|
+
},
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
@classmethod
|
|
194
|
+
def _eval_postconditions(
|
|
195
|
+
cls,
|
|
196
|
+
postconditions: list[Any],
|
|
197
|
+
context: dict[str, Any],
|
|
198
|
+
) -> list[dict[str, Any]]:
|
|
199
|
+
"""Evaluate all postconditions, raising PostconditionBreachError on failure."""
|
|
200
|
+
post_verdicts: list[dict[str, Any]] = []
|
|
201
|
+
for post in postconditions:
|
|
202
|
+
try:
|
|
203
|
+
passed = SafeRuleEvaluator.evaluate(post.rule, context)
|
|
204
|
+
except Exception as e:
|
|
205
|
+
raise PostconditionBreachError(
|
|
206
|
+
f"Postcondition '{post.invariant}' evaluation error: {e}"
|
|
207
|
+
) from e
|
|
208
|
+
|
|
209
|
+
verdict = {
|
|
210
|
+
"invariant": post.invariant,
|
|
211
|
+
"rule": post.rule,
|
|
212
|
+
"passed": passed,
|
|
213
|
+
"description": post.description,
|
|
214
|
+
}
|
|
215
|
+
post_verdicts.append(verdict)
|
|
216
|
+
|
|
217
|
+
# AC-394-01-03: Failing postcondition halts execution and triggers rollback
|
|
218
|
+
if not passed:
|
|
219
|
+
raise PostconditionBreachError(
|
|
220
|
+
f"Postcondition '{post.invariant}' violated: '{post.rule}' evaluated to false"
|
|
221
|
+
)
|
|
222
|
+
return post_verdicts
|
|
223
|
+
|
|
224
|
+
@staticmethod
|
|
225
|
+
def _build_item_context(payload: Any, card: Any = None) -> dict[str, Any]:
|
|
226
|
+
"""Extract structured properties for rule evaluation context."""
|
|
227
|
+
ctx: dict[str, Any] = {}
|
|
228
|
+
if isinstance(payload, str):
|
|
229
|
+
ctx["size_bytes"] = len(payload.encode("utf-8"))
|
|
230
|
+
lines = [ln for ln in payload.splitlines() if ln.strip()]
|
|
231
|
+
ctx["line_count"] = len(lines)
|
|
232
|
+
if lines and ("," in lines[0] or "\t" in lines[0]):
|
|
233
|
+
cols = [c.strip() for c in lines[0].split(",")]
|
|
234
|
+
ctx["columns"] = cols
|
|
235
|
+
ctx["column_count"] = len(cols)
|
|
236
|
+
ctx["row_count"] = len(lines) - 1
|
|
237
|
+
elif isinstance(payload, bytes):
|
|
238
|
+
ctx["size_bytes"] = len(payload)
|
|
239
|
+
elif isinstance(payload, dict):
|
|
240
|
+
ctx["keys"] = list(payload.keys())
|
|
241
|
+
ctx["key_count"] = len(payload)
|
|
242
|
+
ctx.update(payload)
|
|
243
|
+
elif isinstance(payload, list):
|
|
244
|
+
ctx["item_count"] = len(payload)
|
|
245
|
+
ctx["row_count"] = len(payload)
|
|
246
|
+
|
|
247
|
+
if card and hasattr(card, "metadata") and isinstance(card.metadata, dict):
|
|
248
|
+
ctx.update(card.metadata)
|
|
249
|
+
|
|
250
|
+
return ctx
|
|
251
|
+
|
|
252
|
+
@classmethod
|
|
253
|
+
def _build_post_context(
|
|
254
|
+
cls,
|
|
255
|
+
pre_ctx: dict[str, Any],
|
|
256
|
+
outputs: dict[str, MCard],
|
|
257
|
+
) -> dict[str, Any]:
|
|
258
|
+
"""Assemble combined context (input + output) for postcondition evaluation."""
|
|
259
|
+
post_ctx = dict(pre_ctx)
|
|
260
|
+
out_dict: dict[str, Any] = {}
|
|
261
|
+
for name, card in outputs.items():
|
|
262
|
+
info = cls._build_item_context(card.payload, card)
|
|
263
|
+
out_dict[name] = info
|
|
264
|
+
|
|
265
|
+
post_ctx["output"] = out_dict
|
|
266
|
+
if len(outputs) == 1:
|
|
267
|
+
# Expose primary output attributes directly on 'output'
|
|
268
|
+
primary_name = next(iter(outputs.keys()))
|
|
269
|
+
post_ctx["output"].update(out_dict[primary_name])
|
|
270
|
+
|
|
271
|
+
return post_ctx
|
|
272
|
+
|
|
273
|
+
@staticmethod
|
|
274
|
+
def _generate_receipt(
|
|
275
|
+
action_name: str,
|
|
276
|
+
action_hash: str,
|
|
277
|
+
input_hashes: dict[str, str],
|
|
278
|
+
output_hashes: dict[str, str],
|
|
279
|
+
pre_verdicts: list[dict[str, Any]],
|
|
280
|
+
post_verdicts: list[dict[str, Any]],
|
|
281
|
+
metrics: dict[str, Any] | None = None,
|
|
282
|
+
) -> MCard:
|
|
283
|
+
"""Deferred dynamic invocation of Layer 2 VCardReceipt generator."""
|
|
284
|
+
target = "clm.layer2.vcard_receipt"
|
|
285
|
+
mod = sys.modules.get(target)
|
|
286
|
+
if mod is None:
|
|
287
|
+
__import__(target)
|
|
288
|
+
mod = sys.modules.get(target)
|
|
289
|
+
fn = mod.generate_vcard_receipt
|
|
290
|
+
return fn(
|
|
291
|
+
action_name=action_name,
|
|
292
|
+
action_hash=action_hash,
|
|
293
|
+
input_hashes=input_hashes,
|
|
294
|
+
output_hashes=output_hashes,
|
|
295
|
+
precondition_verdicts=pre_verdicts,
|
|
296
|
+
postcondition_verdicts=post_verdicts,
|
|
297
|
+
metrics=metrics,
|
|
298
|
+
)
|