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
clm/layer1/verifier.py
ADDED
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
"""CLM Verifier — Pre/post condition verification, identity bootstrapping, and type validation.
|
|
2
|
+
|
|
3
|
+
Handles the execution lifecycle gates:
|
|
4
|
+
- verify_precondition: integrity check + agent bootstrap + type validation
|
|
5
|
+
- validate_postcondition: seal result as VCard execution MCard
|
|
6
|
+
- emit_exec_mcard_py: emit execution MCard to Tri-Database
|
|
7
|
+
- resolve_active_hashes_py: resolve A-hash / C-hash pair from manifest
|
|
8
|
+
- check_precondition_py: runtime requirement guard (e.g. interpreter:python>=3.9)
|
|
9
|
+
- normalize_output_py: output normalization helper
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import json
|
|
14
|
+
import logging
|
|
15
|
+
import os
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
import base58
|
|
23
|
+
import yaml
|
|
24
|
+
from cryptography.hazmat.primitives import serialization
|
|
25
|
+
from cryptography.hazmat.primitives.asymmetric import ed25519
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
from clm.layer0.model.card import MCard
|
|
29
|
+
except ImportError:
|
|
30
|
+
from clm.layer0.mcard import MCard
|
|
31
|
+
|
|
32
|
+
from clm.cross_cutting.clm_logger import get_logger
|
|
33
|
+
from clm.layer0.narrative import Writer
|
|
34
|
+
from clm.layer0.utils import (
|
|
35
|
+
clm_hash_file,
|
|
36
|
+
clm_hash_string,
|
|
37
|
+
get_bash_command,
|
|
38
|
+
prefixed_hash_raw,
|
|
39
|
+
resolve_clm_path,
|
|
40
|
+
)
|
|
41
|
+
from clm.layer0.verifier_core import ( # re-export for backward compat
|
|
42
|
+
emit_exec_mcard_py as _emit_exec_mcard_py_core,
|
|
43
|
+
)
|
|
44
|
+
from clm.layer0.verifier_core import (
|
|
45
|
+
verify_precondition as _verify_precondition_core,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
_logger = get_logger("clm.layer1.verifier")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Lazy load algebra helpers to avoid circular dependencies
|
|
52
|
+
def get_load_manifest_str() -> Callable:
|
|
53
|
+
"""
|
|
54
|
+
Lazy loads the manifest string loader from algebra.py to avoid circular dependencies.
|
|
55
|
+
"""
|
|
56
|
+
from clm.layer0.algebra import load_normalized_manifest_str
|
|
57
|
+
return load_normalized_manifest_str
|
|
58
|
+
|
|
59
|
+
def get_load_manifest() -> Callable:
|
|
60
|
+
"""
|
|
61
|
+
Lazy loads the manifest parser from algebra.py.
|
|
62
|
+
"""
|
|
63
|
+
from clm.layer0.algebra import load_normalized_manifest
|
|
64
|
+
return load_normalized_manifest
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def verify_precondition(clm_root: str, clm_file: str) -> bool:
|
|
68
|
+
"""
|
|
69
|
+
Validates the precondition bounds and zero-trust identity before executing a PCard.
|
|
70
|
+
|
|
71
|
+
Delegates to clm.layer0.verifier_core.verify_precondition (Sprint 143 V-1 fix).
|
|
72
|
+
"""
|
|
73
|
+
return _verify_precondition_core(clm_root, clm_file)
|
|
74
|
+
|
|
75
|
+
def emit_exec_mcard_py(clm_root: str, operation: str, content_hash: str, extra_yaml: str = "", a_hash: str = "", c_hash: str = "") -> str:
|
|
76
|
+
"""
|
|
77
|
+
Emits an Execution MCard to the Tri-Database to track state changes.
|
|
78
|
+
|
|
79
|
+
Delegates to clm.layer0.verifier_core.emit_exec_mcard_py (Sprint 143 V-1 fix).
|
|
80
|
+
"""
|
|
81
|
+
return _emit_exec_mcard_py_core(clm_root, operation, content_hash, extra_yaml, a_hash, c_hash)
|
|
82
|
+
|
|
83
|
+
def resolve_active_hashes_py(clm_file: str) -> tuple[str | None, str | None]:
|
|
84
|
+
"""
|
|
85
|
+
Resolves the abstract and concrete hashes from a CLM manifest.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
clm_file (str): The CLM manifest file.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Tuple[Optional[str], Optional[str]]: A tuple containing the a_hash and c_hash, or (None, None) if parsing fails.
|
|
92
|
+
"""
|
|
93
|
+
load_normalized_manifest_fn = get_load_manifest()
|
|
94
|
+
try:
|
|
95
|
+
clm_root = os.environ.get("CLM_ROOT") or os.getcwd()
|
|
96
|
+
yaml_data = load_normalized_manifest_fn(clm_root, clm_file)
|
|
97
|
+
inner = yaml_data.get("clm", yaml_data)
|
|
98
|
+
abstract_spec = inner.get("abstract_spec")
|
|
99
|
+
concrete_impl = inner.get("concrete_impl")
|
|
100
|
+
if not abstract_spec or not concrete_impl:
|
|
101
|
+
return None, None
|
|
102
|
+
|
|
103
|
+
import copy
|
|
104
|
+
c_clean = copy.deepcopy(concrete_impl)
|
|
105
|
+
if "precondition" in c_clean and isinstance(c_clean["precondition"], dict):
|
|
106
|
+
c_clean["precondition"].pop("vcard_pre", None)
|
|
107
|
+
if "postcondition" in c_clean and isinstance(c_clean["postcondition"], dict):
|
|
108
|
+
c_clean["postcondition"].pop("vcard_post", None)
|
|
109
|
+
|
|
110
|
+
a_json = json.dumps(abstract_spec, separators=(',', ':'))
|
|
111
|
+
a_hash = clm_hash_string(a_json)
|
|
112
|
+
|
|
113
|
+
c_clean["a_hash"] = prefixed_hash_raw(a_hash)
|
|
114
|
+
c_json = json.dumps(c_clean, separators=(',', ':'))
|
|
115
|
+
c_hash = clm_hash_string(c_json)
|
|
116
|
+
return prefixed_hash_raw(a_hash), prefixed_hash_raw(c_hash)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
_logger.info(f"[RESOLVE] Failed to resolve active hashes: {e}")
|
|
119
|
+
return None, None
|
|
120
|
+
|
|
121
|
+
def validate_postcondition(clm_root: str, clm_file: str, eval_rc: int) -> bool:
|
|
122
|
+
"""
|
|
123
|
+
Validates the postcondition invariants and emits a signed VCard to seal the result.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
clm_root (str): The root directory of the CLM workspace.
|
|
127
|
+
clm_file (str): The PCard file.
|
|
128
|
+
eval_rc (int): The exit code from the execution layer.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
bool: True if the postcondition was validated, False otherwise.
|
|
132
|
+
"""
|
|
133
|
+
load_normalized_manifest_fn = get_load_manifest()
|
|
134
|
+
clm_file = resolve_clm_path(clm_root, clm_file)
|
|
135
|
+
content = ""
|
|
136
|
+
exec_out = os.environ.get("_EXEC_OUT")
|
|
137
|
+
if exec_out and os.path.exists(exec_out) and os.path.getsize(exec_out) > 0:
|
|
138
|
+
with open(exec_out, encoding="utf-8") as f:
|
|
139
|
+
lines = f.readlines()
|
|
140
|
+
if len(lines) < 5:
|
|
141
|
+
content = " ".join([ln.strip() for ln in lines]).replace('"', '').replace('\\', '')
|
|
142
|
+
else:
|
|
143
|
+
content = prefixed_hash_raw(clm_hash_file(exec_out))
|
|
144
|
+
else:
|
|
145
|
+
content = f"exit code: {eval_rc}"
|
|
146
|
+
|
|
147
|
+
content_hash = clm_hash_string(content)
|
|
148
|
+
extra_yaml = f"content: \"{content}\""
|
|
149
|
+
operation = "validate_postcondition"
|
|
150
|
+
|
|
151
|
+
if clm_file and os.path.exists(clm_file):
|
|
152
|
+
try:
|
|
153
|
+
yaml_data = load_normalized_manifest_fn(clm_root, clm_file)
|
|
154
|
+
inner = yaml_data.get("clm", yaml_data)
|
|
155
|
+
operation = inner.get("concrete_impl", {}).get("operation", "unknown")
|
|
156
|
+
except Exception:
|
|
157
|
+
operation = "unknown"
|
|
158
|
+
impl_hash = clm_hash_file(clm_file)
|
|
159
|
+
extra_yaml += f"\noperation: \"{operation}\"\nimpl_hash: \"{impl_hash}\""
|
|
160
|
+
|
|
161
|
+
a_hash, c_hash = None, None
|
|
162
|
+
if clm_file and os.path.exists(clm_file):
|
|
163
|
+
a_hash, c_hash = resolve_active_hashes_py(clm_file)
|
|
164
|
+
|
|
165
|
+
mcard_file = emit_exec_mcard_py(clm_root, operation, content_hash, extra_yaml, a_hash or "", c_hash or "")
|
|
166
|
+
os.environ["CLM_LAST_MCARD_FILE"] = mcard_file
|
|
167
|
+
os.environ["CLM_LAST_MCARD_HASH"] = content_hash
|
|
168
|
+
_logger.info(f"[VCARD POST] ✅ Execution MCard generated: {mcard_file}")
|
|
169
|
+
_logger.info(f"[VCARD POST] content_hash: {content_hash}")
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
def check_precondition_py(req_string: str) -> bool:
|
|
173
|
+
"""
|
|
174
|
+
Evaluates a runtime requirement string (e.g. interpreter:python>=3.9) against the current host environment.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
req_string (str): The requirement string.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
bool: True if the requirement is satisfied, False otherwise.
|
|
181
|
+
"""
|
|
182
|
+
import re
|
|
183
|
+
m = re.match(r"^interpreter:([a-zA-Z0-9_-]+)(>=|<=|>|<|==|=)([0-9.]+)$", req_string)
|
|
184
|
+
if not m:
|
|
185
|
+
return True
|
|
186
|
+
interpreter, op, req_ver = m.groups()
|
|
187
|
+
|
|
188
|
+
current_ver = ""
|
|
189
|
+
if interpreter == "python":
|
|
190
|
+
current_ver = ".".join(map(str, sys.version_info[:3]))
|
|
191
|
+
elif interpreter in ("shell", "bash"):
|
|
192
|
+
try:
|
|
193
|
+
proc = subprocess.Popen([get_bash_command(), "-c", "echo $BASH_VERSION"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8")
|
|
194
|
+
stdout_data, _ = proc.communicate()
|
|
195
|
+
current_ver = stdout_data.strip().split("(")[0]
|
|
196
|
+
except Exception:
|
|
197
|
+
return False
|
|
198
|
+
elif interpreter == "node":
|
|
199
|
+
try:
|
|
200
|
+
proc = subprocess.Popen(["node", "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8")
|
|
201
|
+
stdout_data, _ = proc.communicate()
|
|
202
|
+
current_ver = stdout_data.strip().lstrip("v")
|
|
203
|
+
except Exception:
|
|
204
|
+
return False
|
|
205
|
+
elif interpreter == "rustc":
|
|
206
|
+
try:
|
|
207
|
+
proc = subprocess.Popen(["rustc", "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8")
|
|
208
|
+
stdout_data, _ = proc.communicate()
|
|
209
|
+
parts = stdout_data.strip().split()
|
|
210
|
+
if len(parts) >= 2:
|
|
211
|
+
current_ver = parts[1]
|
|
212
|
+
import tempfile
|
|
213
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
214
|
+
test_src = os.path.join(tmpdir, "chk.rs")
|
|
215
|
+
test_bin = os.path.join(tmpdir, "chk.exe" if sys.platform == "win32" else "chk")
|
|
216
|
+
with open(test_src, "w") as f:
|
|
217
|
+
f.write('fn main(){println!("1");}')
|
|
218
|
+
c_proc = subprocess.Popen(["rustc", "-O", "-o", test_bin, test_src], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
219
|
+
c_proc.communicate()
|
|
220
|
+
if c_proc.returncode != 0:
|
|
221
|
+
return False
|
|
222
|
+
except Exception:
|
|
223
|
+
return False
|
|
224
|
+
else:
|
|
225
|
+
import shutil
|
|
226
|
+
return shutil.which(interpreter) is not None
|
|
227
|
+
|
|
228
|
+
def parse_version(v_str):
|
|
229
|
+
m_v = re.search(r"(\d+)\.(\d+)(?:\.(\d+))?", v_str)
|
|
230
|
+
if not m_v:
|
|
231
|
+
return (0, 0, 0)
|
|
232
|
+
return (int(m_v.group(1)), int(m_v.group(2)), int(m_v.group(3)) if m_v.group(3) else 0)
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
v1 = parse_version(current_ver)
|
|
236
|
+
v2 = parse_version(req_ver)
|
|
237
|
+
ops = {">=": v1 >= v2, "<=": v1 <= v2, ">": v1 > v2, "<": v1 < v2}
|
|
238
|
+
return ops.get(op, v1 == v2)
|
|
239
|
+
except Exception:
|
|
240
|
+
return False
|
|
241
|
+
|
|
242
|
+
def normalize_output_py(raw_out: str) -> str:
|
|
243
|
+
"""
|
|
244
|
+
Normalizes command output by stripping returns and sorting JSON keys.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
raw_out (str): The raw standard output string.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
str: The normalized string.
|
|
251
|
+
"""
|
|
252
|
+
stripped = raw_out.replace("\r", "").rstrip()
|
|
253
|
+
try:
|
|
254
|
+
parsed = json.loads(stripped)
|
|
255
|
+
return json.dumps(parsed, sort_keys=True, separators=(",", ":"))
|
|
256
|
+
except Exception:
|
|
257
|
+
return stripped
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def pem_to_did(pem_bytes: bytes) -> str:
|
|
261
|
+
pubkey = serialization.load_pem_public_key(pem_bytes)
|
|
262
|
+
raw_bytes = pubkey.public_bytes(
|
|
263
|
+
encoding=serialization.Encoding.Raw,
|
|
264
|
+
format=serialization.PublicFormat.Raw
|
|
265
|
+
)
|
|
266
|
+
multicodec = b'\xed\x01' + raw_bytes
|
|
267
|
+
return 'did:key:z' + base58.b58encode(multicodec).decode('utf-8')
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def did_to_pubkey(did: str) -> ed25519.Ed25519PublicKey:
|
|
271
|
+
if not did.startswith('did:key:z'):
|
|
272
|
+
raise ValueError('Unsupported DID method: ' + did)
|
|
273
|
+
b58_part = did[9:]
|
|
274
|
+
decoded = base58.b58decode(b58_part)
|
|
275
|
+
if decoded[0] != 0xed or decoded[1] != 0x01:
|
|
276
|
+
raise ValueError('Unsupported multicodec identifier in did:key')
|
|
277
|
+
raw_key = decoded[2:]
|
|
278
|
+
return ed25519.Ed25519PublicKey.from_public_bytes(raw_key)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def clm_derive_session_key() -> dict:
|
|
282
|
+
private_key = ed25519.Ed25519PrivateKey.generate()
|
|
283
|
+
public_key = private_key.public_key()
|
|
284
|
+
|
|
285
|
+
private_pem = private_key.private_bytes(
|
|
286
|
+
encoding=serialization.Encoding.PEM,
|
|
287
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
288
|
+
encryption_algorithm=serialization.NoEncryption()
|
|
289
|
+
).decode('utf-8')
|
|
290
|
+
|
|
291
|
+
public_pem = public_key.public_bytes(
|
|
292
|
+
encoding=serialization.Encoding.PEM,
|
|
293
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
|
294
|
+
).decode('utf-8')
|
|
295
|
+
|
|
296
|
+
did = pem_to_did(public_pem.encode('utf-8'))
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
"private_key": private_pem,
|
|
300
|
+
"public_key": public_pem,
|
|
301
|
+
"did": did
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def clm_issue_delegation(parent_private_key_path: str, session_did: str, ttl: int = 3600, capabilities: str = "exec") -> str:
|
|
306
|
+
with open(parent_private_key_path, "rb") as f:
|
|
307
|
+
parent_priv = serialization.load_pem_private_key(f.read(), password=None)
|
|
308
|
+
|
|
309
|
+
parent_pub = parent_priv.public_key()
|
|
310
|
+
parent_pub_pem = parent_pub.public_bytes(
|
|
311
|
+
encoding=serialization.Encoding.PEM,
|
|
312
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo
|
|
313
|
+
)
|
|
314
|
+
parent_did = pem_to_did(parent_pub_pem)
|
|
315
|
+
|
|
316
|
+
import datetime
|
|
317
|
+
expires_at = (datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=ttl)).isoformat()
|
|
318
|
+
if expires_at.endswith('+00:00'):
|
|
319
|
+
expires_at = expires_at[:-6] + 'Z'
|
|
320
|
+
elif '+' not in expires_at and '-' not in expires_at[-6:] and not expires_at.endswith('Z'):
|
|
321
|
+
expires_at = expires_at + 'Z'
|
|
322
|
+
|
|
323
|
+
payload = f"{parent_did}|{session_did}|{expires_at}|{capabilities}"
|
|
324
|
+
signature = parent_priv.sign(payload.encode('utf-8'))
|
|
325
|
+
signature_hex = signature.hex()
|
|
326
|
+
|
|
327
|
+
caps_list = [c.strip() for c in capabilities.split(",") if c.strip()]
|
|
328
|
+
|
|
329
|
+
delegation_data = {
|
|
330
|
+
"session_delegation": {
|
|
331
|
+
"type": "meta/session_delegation",
|
|
332
|
+
"parent_did": parent_did,
|
|
333
|
+
"session_did": session_did,
|
|
334
|
+
"expires_at": expires_at,
|
|
335
|
+
"capabilities": caps_list,
|
|
336
|
+
"signature": f"ed25519:{signature_hex}"
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return yaml.dump(delegation_data, default_flow_style=False)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def clm_verify_delegation(cert_yaml: str, expected_session_did: str = None, expected_cap: str = None) -> bool:
|
|
343
|
+
try:
|
|
344
|
+
data = yaml.safe_load(cert_yaml)
|
|
345
|
+
sd = data.get("session_delegation", {})
|
|
346
|
+
parent_did = sd.get("parent_did")
|
|
347
|
+
session_did = sd.get("session_did")
|
|
348
|
+
expires_at = sd.get("expires_at")
|
|
349
|
+
signature = sd.get("signature")
|
|
350
|
+
capabilities = sd.get("capabilities", [])
|
|
351
|
+
except Exception:
|
|
352
|
+
return False
|
|
353
|
+
|
|
354
|
+
if not all([parent_did, session_did, expires_at, signature]):
|
|
355
|
+
return False
|
|
356
|
+
|
|
357
|
+
if expected_session_did and session_did != expected_session_did:
|
|
358
|
+
return False
|
|
359
|
+
|
|
360
|
+
if expected_cap and expected_cap not in capabilities:
|
|
361
|
+
return False
|
|
362
|
+
|
|
363
|
+
import datetime
|
|
364
|
+
try:
|
|
365
|
+
exp_str = expires_at
|
|
366
|
+
if exp_str.endswith('Z'):
|
|
367
|
+
exp_str = exp_str[:-1] + '+00:00'
|
|
368
|
+
exp_dt = datetime.datetime.fromisoformat(exp_str)
|
|
369
|
+
now_dt = datetime.datetime.now(datetime.timezone.utc)
|
|
370
|
+
if now_dt > exp_dt:
|
|
371
|
+
return False
|
|
372
|
+
except Exception:
|
|
373
|
+
return False
|
|
374
|
+
|
|
375
|
+
try:
|
|
376
|
+
caps_comma = ",".join(capabilities)
|
|
377
|
+
payload = f"{parent_did}|{session_did}|{expires_at}|{caps_comma}"
|
|
378
|
+
|
|
379
|
+
signature_hex = signature.split("ed25519:")[1]
|
|
380
|
+
signature_bytes = bytes.fromhex(signature_hex)
|
|
381
|
+
|
|
382
|
+
parent_pub = did_to_pubkey(parent_did)
|
|
383
|
+
parent_pub.verify(signature_bytes, payload.encode('utf-8'))
|
|
384
|
+
|
|
385
|
+
os.environ["CLM_DELEGATED_PARENT_DID"] = parent_did
|
|
386
|
+
return True
|
|
387
|
+
except Exception:
|
|
388
|
+
return False
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@dataclass
|
|
392
|
+
class VerificationResult:
|
|
393
|
+
"""Result of CLM verification"""
|
|
394
|
+
|
|
395
|
+
is_valid: bool
|
|
396
|
+
abstract_valid: bool
|
|
397
|
+
concrete_valid: bool
|
|
398
|
+
balanced_valid: bool
|
|
399
|
+
errors: list[str]
|
|
400
|
+
warnings: list[str]
|
|
401
|
+
verification_details: dict[str, Any]
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
class CLMVerifier:
|
|
405
|
+
"""
|
|
406
|
+
Verifier for the Cubical Logic Model.
|
|
407
|
+
|
|
408
|
+
Ensures that every PCard maintains consistency between:
|
|
409
|
+
1. Abstract Specification (WHAT - the specification)
|
|
410
|
+
2. Concrete Implementation (HOW - the implementation)
|
|
411
|
+
3. Balanced Expectations (WHY - the verification proofs)
|
|
412
|
+
"""
|
|
413
|
+
|
|
414
|
+
def __init__(self, engine):
|
|
415
|
+
self.engine = engine
|
|
416
|
+
self.logger = logging.getLogger(__name__)
|
|
417
|
+
self.verification_cache = {}
|
|
418
|
+
|
|
419
|
+
def verify_clm_consistency(
|
|
420
|
+
self, pcard: MCard, target: MCard, context: dict[str, Any] | None = None
|
|
421
|
+
) -> VerificationResult:
|
|
422
|
+
"""
|
|
423
|
+
Verify CLM consistency for a PCard-target pair.
|
|
424
|
+
|
|
425
|
+
This implements the core verification workflow:
|
|
426
|
+
1. Load PCard and extract CLM dimensions
|
|
427
|
+
2. Verify Abstract Specification
|
|
428
|
+
3. Verify Concrete Implementation
|
|
429
|
+
4. Verify Balanced Expectations
|
|
430
|
+
5. Check cross-dimensional consistency
|
|
431
|
+
"""
|
|
432
|
+
context = context or {}
|
|
433
|
+
errors = []
|
|
434
|
+
warnings = []
|
|
435
|
+
details = {}
|
|
436
|
+
|
|
437
|
+
try:
|
|
438
|
+
# Step 1: Parse PCard content
|
|
439
|
+
pcard_content = pcard.get_content().decode("utf-8")
|
|
440
|
+
pcard_data = yaml.safe_load(pcard_content)
|
|
441
|
+
|
|
442
|
+
if not isinstance(pcard_data, dict):
|
|
443
|
+
raise ValueError("PCard content must be a YAML dictionary")
|
|
444
|
+
|
|
445
|
+
# Extract CLM base (it might be nested under a 'clm' key)
|
|
446
|
+
clm_base = pcard_data.get("clm", pcard_data)
|
|
447
|
+
|
|
448
|
+
# Extract CLM dimensions (with aliases)
|
|
449
|
+
abstract = clm_base.get("abstract_spec", clm_base.get("abstract", {}))
|
|
450
|
+
concrete = clm_base.get("concrete_impl", clm_base.get("concrete", {}))
|
|
451
|
+
balanced = clm_base.get("balanced_exp", clm_base.get("balanced", {}))
|
|
452
|
+
|
|
453
|
+
self.logger.info(f"Verifying CLM dimensions for PCard {pcard.hash}")
|
|
454
|
+
self.logger.info(f"PCard keys: {list(pcard_data.keys())}")
|
|
455
|
+
self.logger.info(f"Abstract keys: {list(abstract.keys())}")
|
|
456
|
+
|
|
457
|
+
# Step 2: Verify Abstract Specification (Writer Monad)
|
|
458
|
+
abstract_writer = self._verify_abstract(abstract, target, context)
|
|
459
|
+
abstract_details, abstract_errors = abstract_writer.run()
|
|
460
|
+
|
|
461
|
+
errors.extend(abstract_errors)
|
|
462
|
+
details["abstract"] = abstract_details
|
|
463
|
+
abstract_valid = len(abstract_errors) == 0
|
|
464
|
+
|
|
465
|
+
# Step 3: Verify Concrete Implementation (Writer Monad)
|
|
466
|
+
concrete_writer = self._verify_concrete(concrete, abstract, target, context)
|
|
467
|
+
concrete_details, concrete_errors = concrete_writer.run()
|
|
468
|
+
|
|
469
|
+
errors.extend(concrete_errors)
|
|
470
|
+
details["concrete"] = concrete_details
|
|
471
|
+
concrete_valid = len(concrete_errors) == 0
|
|
472
|
+
|
|
473
|
+
# Step 4: Verify Balanced Expectations (Writer Monad)
|
|
474
|
+
balanced_writer = self._verify_balanced(
|
|
475
|
+
balanced, abstract, concrete, target, context
|
|
476
|
+
)
|
|
477
|
+
balanced_details, balanced_errors = balanced_writer.run()
|
|
478
|
+
|
|
479
|
+
errors.extend(balanced_errors)
|
|
480
|
+
details["balanced"] = balanced_details
|
|
481
|
+
balanced_valid = len(balanced_errors) == 0
|
|
482
|
+
|
|
483
|
+
# Step 5: Check cross-dimensional consistency (Writer Monad)
|
|
484
|
+
consistency_writer = self._verify_cross_consistency(
|
|
485
|
+
abstract, concrete, balanced
|
|
486
|
+
)
|
|
487
|
+
consistency_valid_flag, consistency_errors = consistency_writer.run()
|
|
488
|
+
|
|
489
|
+
errors.extend(consistency_errors)
|
|
490
|
+
consistency_valid = consistency_valid_flag and len(consistency_errors) == 0
|
|
491
|
+
|
|
492
|
+
details["cross_consistency"] = {
|
|
493
|
+
"valid": consistency_valid,
|
|
494
|
+
"errors": consistency_errors,
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
# Overall validity
|
|
498
|
+
is_valid = (
|
|
499
|
+
abstract_valid
|
|
500
|
+
and concrete_valid
|
|
501
|
+
and balanced_valid
|
|
502
|
+
and consistency_valid
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
if is_valid:
|
|
506
|
+
self.logger.info(f"CLM verification PASSED for PCard {pcard.hash}")
|
|
507
|
+
else:
|
|
508
|
+
self.logger.warning(
|
|
509
|
+
f"CLM verification FAILED for PCard {pcard.hash}: {errors}"
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
return VerificationResult(
|
|
513
|
+
is_valid=is_valid,
|
|
514
|
+
abstract_valid=abstract_valid,
|
|
515
|
+
concrete_valid=concrete_valid,
|
|
516
|
+
balanced_valid=balanced_valid,
|
|
517
|
+
errors=errors,
|
|
518
|
+
warnings=warnings,
|
|
519
|
+
verification_details=details,
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
except (
|
|
523
|
+
yaml.YAMLError,
|
|
524
|
+
UnicodeDecodeError,
|
|
525
|
+
ValueError,
|
|
526
|
+
TypeError,
|
|
527
|
+
KeyError,
|
|
528
|
+
AttributeError,
|
|
529
|
+
) as e:
|
|
530
|
+
self.logger.error(f"CLM verification error: {str(e)}", exc_info=True)
|
|
531
|
+
return VerificationResult(
|
|
532
|
+
is_valid=False,
|
|
533
|
+
abstract_valid=False,
|
|
534
|
+
concrete_valid=False,
|
|
535
|
+
balanced_valid=False,
|
|
536
|
+
errors=[f"Verification error: {str(e)}"],
|
|
537
|
+
warnings=[],
|
|
538
|
+
verification_details={},
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
async def averify_clm_consistency(
|
|
542
|
+
self, pcard: MCard, target: MCard, context: dict[str, Any] | None = None
|
|
543
|
+
) -> VerificationResult:
|
|
544
|
+
"""Async wrapper for verify_clm_consistency using a thread pool."""
|
|
545
|
+
return await asyncio.to_thread(
|
|
546
|
+
self.verify_clm_consistency, pcard, target, context
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
def _verify_abstract(
|
|
550
|
+
self, abstract: dict[str, Any], target: MCard, context: dict[str, Any]
|
|
551
|
+
) -> Writer[str, dict[str, Any]]:
|
|
552
|
+
"""
|
|
553
|
+
Verify the Abstract Specification dimension using Writer Monad.
|
|
554
|
+
Returns Writer(details, errors).
|
|
555
|
+
"""
|
|
556
|
+
errors = []
|
|
557
|
+
details = {}
|
|
558
|
+
|
|
559
|
+
# Check required abstract fields
|
|
560
|
+
required_fields = [
|
|
561
|
+
"purpose",
|
|
562
|
+
"inputs",
|
|
563
|
+
"outputs",
|
|
564
|
+
"preconditions",
|
|
565
|
+
"postconditions",
|
|
566
|
+
]
|
|
567
|
+
for field in required_fields:
|
|
568
|
+
if field not in abstract:
|
|
569
|
+
errors.append(f"Missing required abstract field: {field}")
|
|
570
|
+
|
|
571
|
+
# Validate input/output specifications
|
|
572
|
+
inputs = abstract.get("inputs", {})
|
|
573
|
+
outputs = abstract.get("outputs", {})
|
|
574
|
+
|
|
575
|
+
if not isinstance(inputs, dict):
|
|
576
|
+
errors.append("Abstract inputs must be a dictionary")
|
|
577
|
+
else:
|
|
578
|
+
details["input_count"] = len(inputs)
|
|
579
|
+
|
|
580
|
+
if not isinstance(outputs, dict):
|
|
581
|
+
errors.append("Abstract outputs must be a dictionary")
|
|
582
|
+
else:
|
|
583
|
+
details["output_count"] = len(outputs)
|
|
584
|
+
|
|
585
|
+
# Validate preconditions and postconditions
|
|
586
|
+
preconditions = abstract.get("preconditions", [])
|
|
587
|
+
postconditions = abstract.get("postconditions", [])
|
|
588
|
+
|
|
589
|
+
if not isinstance(preconditions, list):
|
|
590
|
+
errors.append("Abstract preconditions must be a list")
|
|
591
|
+
else:
|
|
592
|
+
details["precondition_count"] = len(preconditions)
|
|
593
|
+
|
|
594
|
+
if not isinstance(postconditions, list):
|
|
595
|
+
errors.append("Abstract postconditions must be a list")
|
|
596
|
+
else:
|
|
597
|
+
details["postcondition_count"] = len(postconditions)
|
|
598
|
+
|
|
599
|
+
# Check semantic consistency
|
|
600
|
+
purpose = abstract.get("purpose", "")
|
|
601
|
+
if len(purpose) < 10:
|
|
602
|
+
errors.append("Abstract purpose should be descriptive (min 10 characters)")
|
|
603
|
+
|
|
604
|
+
details["purpose_length"] = len(purpose)
|
|
605
|
+
|
|
606
|
+
return Writer(lambda: (details, errors))
|
|
607
|
+
|
|
608
|
+
def _verify_concrete(
|
|
609
|
+
self,
|
|
610
|
+
concrete: dict[str, Any],
|
|
611
|
+
abstract: dict[str, Any],
|
|
612
|
+
target: MCard,
|
|
613
|
+
context: dict[str, Any],
|
|
614
|
+
) -> Writer[str, dict[str, Any]]:
|
|
615
|
+
"""
|
|
616
|
+
Verify the Concrete Implementation dimension using Writer Monad.
|
|
617
|
+
Returns Writer(details, errors).
|
|
618
|
+
"""
|
|
619
|
+
errors = []
|
|
620
|
+
details = {}
|
|
621
|
+
|
|
622
|
+
# Check required concrete fields
|
|
623
|
+
if "operation" not in concrete:
|
|
624
|
+
errors.append("Missing required concrete field: operation")
|
|
625
|
+
|
|
626
|
+
# Validate operation matches abstract purpose
|
|
627
|
+
operation = concrete.get("operation", "")
|
|
628
|
+
abstract_purpose = abstract.get("purpose", "").lower()
|
|
629
|
+
|
|
630
|
+
if operation and abstract_purpose:
|
|
631
|
+
# Simple semantic check - in production would use NLP
|
|
632
|
+
if "transform" in abstract_purpose and "transform" not in operation:
|
|
633
|
+
errors.append("Concrete operation doesn't match abstract purpose")
|
|
634
|
+
|
|
635
|
+
details["operation"] = operation
|
|
636
|
+
|
|
637
|
+
# Check implementation details
|
|
638
|
+
implementation = concrete.get("implementation", {})
|
|
639
|
+
if not isinstance(implementation, dict):
|
|
640
|
+
errors.append("Concrete implementation must be a dictionary")
|
|
641
|
+
else:
|
|
642
|
+
details["implementation_keys"] = list(implementation.keys())
|
|
643
|
+
|
|
644
|
+
# Validate resource requirements
|
|
645
|
+
resources = concrete.get("resources", {})
|
|
646
|
+
if resources:
|
|
647
|
+
if not isinstance(resources, dict):
|
|
648
|
+
errors.append("Concrete resources must be a dictionary")
|
|
649
|
+
else:
|
|
650
|
+
details["resource_requirements"] = resources
|
|
651
|
+
|
|
652
|
+
# Check error handling
|
|
653
|
+
error_handling = concrete.get("error_handling", {})
|
|
654
|
+
if error_handling and not isinstance(error_handling, dict):
|
|
655
|
+
errors.append("Concrete error_handling must be a dictionary")
|
|
656
|
+
|
|
657
|
+
return Writer(lambda: (details, errors))
|
|
658
|
+
|
|
659
|
+
def _verify_balanced(
|
|
660
|
+
self,
|
|
661
|
+
balanced: dict[str, Any],
|
|
662
|
+
abstract: dict[str, Any],
|
|
663
|
+
concrete: dict[str, Any],
|
|
664
|
+
target: MCard,
|
|
665
|
+
context: dict[str, Any],
|
|
666
|
+
) -> Writer[str, dict[str, Any]]:
|
|
667
|
+
"""
|
|
668
|
+
Verify the Balanced Expectations dimension using Writer Monad.
|
|
669
|
+
Returns Writer(details, errors).
|
|
670
|
+
"""
|
|
671
|
+
errors = []
|
|
672
|
+
details = {}
|
|
673
|
+
|
|
674
|
+
# Check required balanced fields
|
|
675
|
+
required_fields = ["test_cases", "expectations"]
|
|
676
|
+
for field in required_fields:
|
|
677
|
+
if field not in balanced:
|
|
678
|
+
errors.append(f"Missing required balanced field: {field}")
|
|
679
|
+
|
|
680
|
+
# Validate test cases
|
|
681
|
+
test_cases = balanced.get("test_cases", [])
|
|
682
|
+
if not isinstance(test_cases, list):
|
|
683
|
+
errors.append("Balanced test_cases must be a list")
|
|
684
|
+
else:
|
|
685
|
+
details["test_case_count"] = len(test_cases)
|
|
686
|
+
|
|
687
|
+
# Validate each test case structure
|
|
688
|
+
for i, test_case in enumerate(test_cases):
|
|
689
|
+
if not isinstance(test_case, dict):
|
|
690
|
+
errors.append(f"Test case {i} must be a dictionary")
|
|
691
|
+
continue
|
|
692
|
+
|
|
693
|
+
# Check for either BDD ('given', 'when', 'then') or ABC ('assumption', 'behavior', 'confirmation')
|
|
694
|
+
has_bdd = all(field in test_case for field in ["given", "when", "then"])
|
|
695
|
+
has_abc = all(
|
|
696
|
+
field in test_case
|
|
697
|
+
for field in ["assumption", "behavior", "confirmation"]
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
if not (has_bdd or has_abc):
|
|
701
|
+
errors.append(
|
|
702
|
+
f"Test case {i} missing required routing fields (BDD given/when/then or ABC assumption/behavior/confirmation)"
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
# Validate expectations
|
|
706
|
+
expectations = balanced.get("expectations", {})
|
|
707
|
+
if not isinstance(expectations, dict):
|
|
708
|
+
errors.append("Balanced expectations must be a dictionary")
|
|
709
|
+
else:
|
|
710
|
+
details["expectation_keys"] = list(expectations.keys())
|
|
711
|
+
|
|
712
|
+
# Check performance expectations
|
|
713
|
+
performance = expectations.get("performance", {})
|
|
714
|
+
if performance:
|
|
715
|
+
if not isinstance(performance, dict):
|
|
716
|
+
errors.append("Performance expectations must be a dictionary")
|
|
717
|
+
else:
|
|
718
|
+
details["performance_expectations"] = performance
|
|
719
|
+
|
|
720
|
+
# Check quality metrics
|
|
721
|
+
quality = expectations.get("quality", {})
|
|
722
|
+
if quality:
|
|
723
|
+
if not isinstance(quality, dict):
|
|
724
|
+
errors.append("Quality expectations must be a dictionary")
|
|
725
|
+
else:
|
|
726
|
+
details["quality_expectations"] = quality
|
|
727
|
+
|
|
728
|
+
return Writer(lambda: (details, errors))
|
|
729
|
+
|
|
730
|
+
def _verify_cross_consistency(
|
|
731
|
+
self,
|
|
732
|
+
abstract: dict[str, Any],
|
|
733
|
+
concrete: dict[str, Any],
|
|
734
|
+
balanced: dict[str, Any],
|
|
735
|
+
) -> Writer[str, bool]:
|
|
736
|
+
"""
|
|
737
|
+
Verify cross-dimensional consistency between CLM dimensions using Writer Monad.
|
|
738
|
+
Returns Writer(valid_flag, errors).
|
|
739
|
+
|
|
740
|
+
Note: This is intentionally lenient to match JavaScript CLMRunner behavior,
|
|
741
|
+
which doesn't perform strict abstract/concrete input matching.
|
|
742
|
+
"""
|
|
743
|
+
errors = []
|
|
744
|
+
valid = len(errors) == 0
|
|
745
|
+
return Writer(lambda: (valid, errors))
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
__all__ = [
|
|
749
|
+
"verify_precondition",
|
|
750
|
+
"emit_exec_mcard_py",
|
|
751
|
+
"resolve_active_hashes_py",
|
|
752
|
+
"validate_postcondition",
|
|
753
|
+
"check_precondition_py",
|
|
754
|
+
"normalize_output_py",
|
|
755
|
+
"pem_to_did",
|
|
756
|
+
"did_to_pubkey",
|
|
757
|
+
"clm_derive_session_key",
|
|
758
|
+
"clm_issue_delegation",
|
|
759
|
+
"clm_verify_delegation",
|
|
760
|
+
"VerificationResult",
|
|
761
|
+
"CLMVerifier",
|
|
762
|
+
]
|
|
763
|
+
|
|
764
|
+
# Register manifest evaluator with native layer (Dependency Inversion for layer stratification)
|
|
765
|
+
try:
|
|
766
|
+
from clm.cross_cutting.native import register_manifest_evaluator
|
|
767
|
+
from clm.layer1.gate_composer import run_nested_evaluate
|
|
768
|
+
|
|
769
|
+
register_manifest_evaluator(run_nested_evaluate)
|
|
770
|
+
except ImportError:
|
|
771
|
+
pass
|
|
772
|
+
|