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/layer0/cel_eval.py
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
"""CEL Evaluator — Common Expression Language Evaluator for CLM.
|
|
2
|
+
|
|
3
|
+
Layer 0 Canonical Path: clm/kernel/scripts/clm/layer0/cel_eval.py
|
|
4
|
+
Provides side-effect-free deterministic evaluation of VCard pre/post constraints,
|
|
5
|
+
gate predicates, field assertions, and RLCD calibrated meet predicates.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import math
|
|
11
|
+
import re
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class BailVerdict:
|
|
18
|
+
verdict: str # "pass" or "bail"
|
|
19
|
+
reason: str = ""
|
|
20
|
+
invariant_code: str = ""
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def pass_verdict(cls) -> BailVerdict:
|
|
24
|
+
return cls(verdict="pass")
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def bail(cls, reason: str, invariant_code: str = "") -> BailVerdict:
|
|
28
|
+
return cls(verdict="bail", reason=reason, invariant_code=invariant_code)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class CelEvaluationResult:
|
|
33
|
+
matched: bool
|
|
34
|
+
verdict: BailVerdict
|
|
35
|
+
evaluated_value: Any = None
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def passed(self) -> bool:
|
|
39
|
+
return self.verdict.verdict == "pass"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _is_payload_empty(card: Any) -> bool:
|
|
43
|
+
if card is None:
|
|
44
|
+
return True
|
|
45
|
+
if isinstance(card, dict):
|
|
46
|
+
if "payload" in card:
|
|
47
|
+
payload = card["payload"]
|
|
48
|
+
elif "content" in card:
|
|
49
|
+
payload = card["content"]
|
|
50
|
+
else:
|
|
51
|
+
payload = card
|
|
52
|
+
else:
|
|
53
|
+
payload = getattr(card, "payload", getattr(card, "content", None))
|
|
54
|
+
|
|
55
|
+
if payload is None:
|
|
56
|
+
return True
|
|
57
|
+
if isinstance(payload, (bytes, str)):
|
|
58
|
+
return len(payload) == 0
|
|
59
|
+
if isinstance(payload, list):
|
|
60
|
+
return len(payload) == 0
|
|
61
|
+
|
|
62
|
+
# Check payload object / dict
|
|
63
|
+
if isinstance(payload, dict):
|
|
64
|
+
kind = payload.get("kind")
|
|
65
|
+
val = payload.get("value")
|
|
66
|
+
data = payload.get("data")
|
|
67
|
+
if kind == "scalar":
|
|
68
|
+
return val is None or val == ""
|
|
69
|
+
if kind == "text":
|
|
70
|
+
return val is None or str(val).strip() == ""
|
|
71
|
+
if kind == "binary":
|
|
72
|
+
return data is None or len(data) == 0
|
|
73
|
+
if kind == "structured":
|
|
74
|
+
if val is None:
|
|
75
|
+
return True
|
|
76
|
+
if isinstance(val, (list, dict)):
|
|
77
|
+
return len(val) == 0
|
|
78
|
+
if "kind" not in payload:
|
|
79
|
+
return len(payload) == 0
|
|
80
|
+
return val is None or val == ""
|
|
81
|
+
|
|
82
|
+
# Attribute-based payload
|
|
83
|
+
kind = getattr(payload, "kind", None)
|
|
84
|
+
val = getattr(payload, "value", None)
|
|
85
|
+
data = getattr(payload, "data", None)
|
|
86
|
+
if kind == "scalar":
|
|
87
|
+
return val is None or val == ""
|
|
88
|
+
if kind == "text":
|
|
89
|
+
return val is None or str(val).strip() == ""
|
|
90
|
+
if kind == "binary":
|
|
91
|
+
return data is None or len(data) == 0
|
|
92
|
+
if kind == "structured":
|
|
93
|
+
if val is None:
|
|
94
|
+
return True
|
|
95
|
+
if isinstance(val, (list, dict)):
|
|
96
|
+
return len(val) == 0
|
|
97
|
+
return False
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _resolve_context_value(key: str, context: dict) -> Any:
|
|
102
|
+
key = key.strip()
|
|
103
|
+
if not key:
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
# Handle RLCD access e.g., rlcd(decision).confidence or rlcd(decision).is_admissible
|
|
107
|
+
rlcd_match = re.match(r"^rlcd\(([^)]+)\)\.([a-zA-Z0-9_]+)$", key)
|
|
108
|
+
if rlcd_match:
|
|
109
|
+
target_name, prop = rlcd_match.groups()
|
|
110
|
+
target_obj = _resolve_context_value(target_name, context)
|
|
111
|
+
if isinstance(target_obj, dict):
|
|
112
|
+
if prop == "confidence":
|
|
113
|
+
return float(target_obj.get("confidence", target_obj.get("prob", 0.0)))
|
|
114
|
+
if prop == "is_admissible":
|
|
115
|
+
return bool(target_obj.get("is_admissible", target_obj.get("admissible", False)))
|
|
116
|
+
return target_obj.get(prop)
|
|
117
|
+
if hasattr(target_obj, prop):
|
|
118
|
+
return getattr(target_obj, prop)
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
# Strip optional context. prefix if looking inside context root
|
|
122
|
+
if key.startswith("context."):
|
|
123
|
+
clean_key = key[len("context."):]
|
|
124
|
+
resolved = _resolve_context_value(clean_key, context)
|
|
125
|
+
if resolved is not None:
|
|
126
|
+
return resolved
|
|
127
|
+
|
|
128
|
+
# Handle context direct lookups
|
|
129
|
+
parts = key.split(".")
|
|
130
|
+
curr: Any = context
|
|
131
|
+
for p in parts:
|
|
132
|
+
if isinstance(curr, dict):
|
|
133
|
+
if p in curr:
|
|
134
|
+
curr = curr[p]
|
|
135
|
+
elif p.lower() in curr:
|
|
136
|
+
curr = curr[p.lower()]
|
|
137
|
+
else:
|
|
138
|
+
return None
|
|
139
|
+
elif hasattr(curr, p):
|
|
140
|
+
curr = getattr(curr, p)
|
|
141
|
+
elif hasattr(curr, "payload"):
|
|
142
|
+
payload = curr.payload
|
|
143
|
+
if isinstance(payload, dict) and p in payload:
|
|
144
|
+
curr = payload[p]
|
|
145
|
+
elif hasattr(payload, "value") and isinstance(payload.value, dict) and p in payload.value:
|
|
146
|
+
curr = payload.value[p]
|
|
147
|
+
elif hasattr(payload, p):
|
|
148
|
+
curr = getattr(payload, p)
|
|
149
|
+
elif hasattr(curr, "metadata") and isinstance(curr.metadata, dict) and p in curr.metadata:
|
|
150
|
+
curr = curr.metadata[p]
|
|
151
|
+
else:
|
|
152
|
+
return None
|
|
153
|
+
elif hasattr(curr, "metadata") and isinstance(curr.metadata, dict) and p in curr.metadata:
|
|
154
|
+
curr = curr.metadata[p]
|
|
155
|
+
else:
|
|
156
|
+
return None
|
|
157
|
+
return curr
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _compare_values(left: Any, op: str, right: Any) -> bool:
|
|
161
|
+
try:
|
|
162
|
+
# Numeric coerce
|
|
163
|
+
if isinstance(left, (int, float, str)) and isinstance(right, (int, float, str)):
|
|
164
|
+
try:
|
|
165
|
+
lf = float(left)
|
|
166
|
+
rf = float(right)
|
|
167
|
+
if op == "==":
|
|
168
|
+
return math.isclose(lf, rf, rel_tol=1e-6, abs_tol=1e-9)
|
|
169
|
+
if op == "!=":
|
|
170
|
+
return not math.isclose(lf, rf, rel_tol=1e-6, abs_tol=1e-9)
|
|
171
|
+
if op == "<":
|
|
172
|
+
return lf < rf
|
|
173
|
+
if op == "<=":
|
|
174
|
+
return lf <= rf
|
|
175
|
+
if op == ">":
|
|
176
|
+
return lf > rf
|
|
177
|
+
if op == ">=":
|
|
178
|
+
return lf >= rf
|
|
179
|
+
except (ValueError, TypeError):
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
# String coerce for booleans
|
|
183
|
+
if isinstance(right, bool) and isinstance(left, str):
|
|
184
|
+
left = left.lower() in ("true", "1", "yes")
|
|
185
|
+
elif isinstance(left, bool) and isinstance(right, str):
|
|
186
|
+
right = right.lower() in ("true", "1", "yes")
|
|
187
|
+
|
|
188
|
+
if op == "==":
|
|
189
|
+
return left == right
|
|
190
|
+
if op == "!=":
|
|
191
|
+
return left != right
|
|
192
|
+
if op == "<":
|
|
193
|
+
return left < right
|
|
194
|
+
if op == "<=":
|
|
195
|
+
return left <= right
|
|
196
|
+
if op == ">":
|
|
197
|
+
return left > right
|
|
198
|
+
if op == ">=":
|
|
199
|
+
return left >= right
|
|
200
|
+
except Exception:
|
|
201
|
+
return False
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _parse_scalar_literal(val_str: str) -> Any:
|
|
206
|
+
val_str = val_str.strip()
|
|
207
|
+
if val_str.lower() == "true":
|
|
208
|
+
return True
|
|
209
|
+
if val_str.lower() == "false":
|
|
210
|
+
return False
|
|
211
|
+
if val_str.lower() == "null" or val_str.lower() == "none":
|
|
212
|
+
return None
|
|
213
|
+
if (val_str.startswith('"') and val_str.endswith('"')) or (
|
|
214
|
+
val_str.startswith("'") and val_str.endswith("'")
|
|
215
|
+
):
|
|
216
|
+
return val_str[1:-1]
|
|
217
|
+
try:
|
|
218
|
+
if "." in val_str:
|
|
219
|
+
return float(val_str)
|
|
220
|
+
return int(val_str)
|
|
221
|
+
except ValueError:
|
|
222
|
+
return val_str
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class _EvaluatorDescriptor:
|
|
226
|
+
def __get__(self, obj: Any, objtype: type | None = None) -> Any:
|
|
227
|
+
if obj is None:
|
|
228
|
+
assert objtype is not None
|
|
229
|
+
return objtype._evaluate_impl
|
|
230
|
+
def _instance_evaluate(expr_or_context: Any = None, context: dict | None = None) -> CelEvaluationResult:
|
|
231
|
+
if isinstance(expr_or_context, str):
|
|
232
|
+
return objtype._evaluate_impl(expr_or_context, context)
|
|
233
|
+
ctx = expr_or_context if isinstance(expr_or_context, dict) else (context or {})
|
|
234
|
+
return objtype._evaluate_impl(obj.expr, ctx)
|
|
235
|
+
return _instance_evaluate
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class CelEvaluator:
|
|
239
|
+
"""Canonical CEL (Common Expression Language) evaluator for CLM."""
|
|
240
|
+
|
|
241
|
+
def __init__(self, expr: str = "") -> None:
|
|
242
|
+
self.expr = expr
|
|
243
|
+
|
|
244
|
+
def __call__(self, context: dict | None = None) -> CelEvaluationResult:
|
|
245
|
+
return self.evaluate(context=context)
|
|
246
|
+
|
|
247
|
+
evaluate = _EvaluatorDescriptor()
|
|
248
|
+
|
|
249
|
+
@classmethod
|
|
250
|
+
def evaluate_atomic(cls, expr: str, context: dict) -> CelEvaluationResult:
|
|
251
|
+
expr = expr.strip()
|
|
252
|
+
lower = expr.lower()
|
|
253
|
+
|
|
254
|
+
# 0. Literal constants
|
|
255
|
+
if lower in ("true", "pass"):
|
|
256
|
+
return CelEvaluationResult(matched=True, verdict=BailVerdict.pass_verdict(), evaluated_value=True)
|
|
257
|
+
if lower in ("false", "fail"):
|
|
258
|
+
return CelEvaluationResult(
|
|
259
|
+
matched=True,
|
|
260
|
+
verdict=BailVerdict.bail(f'Expression explicitly fails: "{expr}"', expr),
|
|
261
|
+
evaluated_value=False,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# 1. Gate predicates
|
|
265
|
+
if lower == "gate:payload_nonempty":
|
|
266
|
+
inp = context.get("input") or context.get("mcard")
|
|
267
|
+
if inp is None and "payload" in context:
|
|
268
|
+
inp = {"payload": context["payload"]}
|
|
269
|
+
if inp is None:
|
|
270
|
+
return CelEvaluationResult(
|
|
271
|
+
matched=True,
|
|
272
|
+
verdict=BailVerdict.bail("gate:payload_nonempty failed — no input MCard", expr),
|
|
273
|
+
)
|
|
274
|
+
if _is_payload_empty(inp):
|
|
275
|
+
return CelEvaluationResult(
|
|
276
|
+
matched=True,
|
|
277
|
+
verdict=BailVerdict.bail("gate:payload_nonempty failed — input payload is empty", expr),
|
|
278
|
+
)
|
|
279
|
+
return CelEvaluationResult(matched=True, verdict=BailVerdict.pass_verdict())
|
|
280
|
+
|
|
281
|
+
if lower == "gate:result_sealed":
|
|
282
|
+
out = context.get("output") or context.get("input")
|
|
283
|
+
if out is None or not getattr(out, "hash", None):
|
|
284
|
+
return CelEvaluationResult(
|
|
285
|
+
matched=True,
|
|
286
|
+
verdict=BailVerdict.bail("gate:result_sealed failed — output MCard has no sealed hash", expr),
|
|
287
|
+
)
|
|
288
|
+
return CelEvaluationResult(matched=True, verdict=BailVerdict.pass_verdict())
|
|
289
|
+
|
|
290
|
+
if lower == "gate:authorized":
|
|
291
|
+
card = context.get("input")
|
|
292
|
+
author = ""
|
|
293
|
+
if isinstance(card, dict):
|
|
294
|
+
author = str(card.get("author", "") or "")
|
|
295
|
+
elif card is not None:
|
|
296
|
+
author = str(getattr(card, "author", "") or "")
|
|
297
|
+
if not author.startswith("did:"):
|
|
298
|
+
return CelEvaluationResult(
|
|
299
|
+
matched=True,
|
|
300
|
+
verdict=BailVerdict.bail("gate:authorized failed — ungrounded DID", expr),
|
|
301
|
+
)
|
|
302
|
+
return CelEvaluationResult(matched=True, verdict=BailVerdict.pass_verdict())
|
|
303
|
+
|
|
304
|
+
# 2. Metadata field existence: exists:field
|
|
305
|
+
if lower.startswith("exists:"):
|
|
306
|
+
field_name = expr[7:].strip()
|
|
307
|
+
card = context.get("input")
|
|
308
|
+
has_field = False
|
|
309
|
+
if isinstance(card, dict):
|
|
310
|
+
meta = card.get("metadata", {})
|
|
311
|
+
if isinstance(meta, dict) and field_name in meta or field_name in card:
|
|
312
|
+
has_field = True
|
|
313
|
+
elif card is not None:
|
|
314
|
+
meta = getattr(card, "metadata", None)
|
|
315
|
+
if isinstance(meta, dict) and field_name in meta or hasattr(meta, "has") and meta.has(field_name) or hasattr(card, field_name):
|
|
316
|
+
has_field = True
|
|
317
|
+
|
|
318
|
+
if not has_field and "variables" in context and isinstance(context["variables"], dict):
|
|
319
|
+
has_field = field_name in context["variables"]
|
|
320
|
+
if not has_field and field_name in context:
|
|
321
|
+
has_field = True
|
|
322
|
+
|
|
323
|
+
if has_field:
|
|
324
|
+
return CelEvaluationResult(matched=True, verdict=BailVerdict.pass_verdict())
|
|
325
|
+
return CelEvaluationResult(
|
|
326
|
+
matched=True,
|
|
327
|
+
verdict=BailVerdict.bail(f'Field "{field_name}" not found in metadata', expr),
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# 3. Type check: type:kind
|
|
331
|
+
if lower.startswith("type:"):
|
|
332
|
+
expected_kind = expr[5:].strip()
|
|
333
|
+
card = context.get("input")
|
|
334
|
+
actual_kind = None
|
|
335
|
+
if isinstance(card, dict):
|
|
336
|
+
payload = card.get("payload")
|
|
337
|
+
if isinstance(payload, dict):
|
|
338
|
+
actual_kind = payload.get("kind")
|
|
339
|
+
else:
|
|
340
|
+
actual_kind = card.get("kind")
|
|
341
|
+
elif card is not None:
|
|
342
|
+
payload = getattr(card, "payload", None)
|
|
343
|
+
actual_kind = getattr(payload, "kind", None) if payload else None
|
|
344
|
+
if actual_kind is None and isinstance(payload, dict):
|
|
345
|
+
actual_kind = payload.get("kind")
|
|
346
|
+
|
|
347
|
+
if actual_kind == expected_kind:
|
|
348
|
+
return CelEvaluationResult(matched=True, verdict=BailVerdict.pass_verdict())
|
|
349
|
+
return CelEvaluationResult(
|
|
350
|
+
matched=True,
|
|
351
|
+
verdict=BailVerdict.bail(
|
|
352
|
+
f'Expected payload kind "{expected_kind}", got "{actual_kind}"', expr
|
|
353
|
+
),
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
# 4. Built-in size checks e.g. size(str) > 0
|
|
357
|
+
size_match = re.match(r"^size\(([^)]+)\)\s*(==|!=|<=|>=|<|>)\s*(.*)$", expr)
|
|
358
|
+
if size_match:
|
|
359
|
+
var_name, op, right_str = size_match.groups()
|
|
360
|
+
val = _resolve_context_value(var_name, context)
|
|
361
|
+
length = len(val) if val is not None and hasattr(val, "__len__") else 0
|
|
362
|
+
right_val = _parse_scalar_literal(right_str)
|
|
363
|
+
passed = _compare_values(length, op, right_val)
|
|
364
|
+
if passed:
|
|
365
|
+
return CelEvaluationResult(matched=True, verdict=BailVerdict.pass_verdict(), evaluated_value=passed)
|
|
366
|
+
return CelEvaluationResult(
|
|
367
|
+
matched=True,
|
|
368
|
+
verdict=BailVerdict.bail(f"Size constraint failed: size({var_name})={length} {op} {right_val}", expr),
|
|
369
|
+
evaluated_value=passed,
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# 5. Comparison: leftKey op rightVal
|
|
373
|
+
comp_match = re.match(r"^([a-zA-Z0-9_.()]+)\s*(==|!=|<=|>=|<|>)\s*(.*)$", expr)
|
|
374
|
+
if comp_match:
|
|
375
|
+
left_key, op, right_str = comp_match.groups()
|
|
376
|
+
left_val = _parse_scalar_literal(left_key)
|
|
377
|
+
if isinstance(left_val, str) and not (left_key.startswith('"') or left_key.startswith("'")):
|
|
378
|
+
resolved_left = _resolve_context_value(left_key, context)
|
|
379
|
+
if resolved_left is not None:
|
|
380
|
+
left_val = resolved_left
|
|
381
|
+
elif left_key not in ("true", "false", "null"):
|
|
382
|
+
left_val = None
|
|
383
|
+
|
|
384
|
+
right_val = _parse_scalar_literal(right_str)
|
|
385
|
+
if isinstance(right_val, str) and not (right_str.startswith('"') or right_str.startswith("'")):
|
|
386
|
+
resolved_right = _resolve_context_value(right_val, context)
|
|
387
|
+
if resolved_right is not None:
|
|
388
|
+
right_val = resolved_right
|
|
389
|
+
|
|
390
|
+
result = _compare_values(left_val, op, right_val)
|
|
391
|
+
if result:
|
|
392
|
+
return CelEvaluationResult(matched=True, verdict=BailVerdict.pass_verdict(), evaluated_value=result)
|
|
393
|
+
return CelEvaluationResult(
|
|
394
|
+
matched=True,
|
|
395
|
+
verdict=BailVerdict.bail(f"Comparison failed: {left_key} ({left_val}) {op} {right_str}", expr),
|
|
396
|
+
evaluated_value=result,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
# 6. Boolean identifier / property lookup e.g. "is_valid", "rlcd(decision).is_admissible"
|
|
400
|
+
val = _resolve_context_value(expr, context)
|
|
401
|
+
if val is not None:
|
|
402
|
+
if bool(val):
|
|
403
|
+
return CelEvaluationResult(matched=True, verdict=BailVerdict.pass_verdict(), evaluated_value=True)
|
|
404
|
+
return CelEvaluationResult(
|
|
405
|
+
matched=True,
|
|
406
|
+
verdict=BailVerdict.bail(f"Boolean expression '{expr}' evaluated to {val}", expr),
|
|
407
|
+
evaluated_value=False,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
# Default fallback
|
|
411
|
+
return CelEvaluationResult(matched=False, verdict=BailVerdict.pass_verdict())
|
|
412
|
+
|
|
413
|
+
@classmethod
|
|
414
|
+
def _evaluate_impl(cls, expr: str, context: dict | None = None) -> CelEvaluationResult:
|
|
415
|
+
if context is None:
|
|
416
|
+
context = {}
|
|
417
|
+
expr = expr.strip()
|
|
418
|
+
if not expr:
|
|
419
|
+
return CelEvaluationResult(matched=True, verdict=BailVerdict.pass_verdict())
|
|
420
|
+
|
|
421
|
+
# Handle logical AND (&&)
|
|
422
|
+
if "&&" in expr:
|
|
423
|
+
parts = [p.strip() for p in expr.split("&&")]
|
|
424
|
+
for part in parts:
|
|
425
|
+
res = cls._evaluate_impl(part, context)
|
|
426
|
+
if not res.passed:
|
|
427
|
+
return res
|
|
428
|
+
return CelEvaluationResult(matched=True, verdict=BailVerdict.pass_verdict())
|
|
429
|
+
|
|
430
|
+
# Handle logical OR (||)
|
|
431
|
+
if "||" in expr:
|
|
432
|
+
parts = [p.strip() for p in expr.split("||")]
|
|
433
|
+
last_res = None
|
|
434
|
+
for part in parts:
|
|
435
|
+
res = cls._evaluate_impl(part, context)
|
|
436
|
+
if res.passed:
|
|
437
|
+
return res
|
|
438
|
+
last_res = res
|
|
439
|
+
return last_res or CelEvaluationResult(
|
|
440
|
+
matched=True,
|
|
441
|
+
verdict=BailVerdict.bail(f"All branches of OR failed: {expr}", expr),
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
return cls.evaluate_atomic(expr, context)
|
|
445
|
+
|
|
446
|
+
evaluate = _EvaluatorDescriptor()
|
|
447
|
+
|
|
448
|
+
@classmethod
|
|
449
|
+
def evaluate_vcard_meet(
|
|
450
|
+
cls,
|
|
451
|
+
input_card: Any,
|
|
452
|
+
output_card: Any,
|
|
453
|
+
v_pre: str | list[str] | None = None,
|
|
454
|
+
v_post: str | list[str] | None = None,
|
|
455
|
+
context: dict | None = None,
|
|
456
|
+
) -> bool:
|
|
457
|
+
"""Evaluates formal lattice meet: T_valid = T_cand ∧ V_pre ∧ V_post.
|
|
458
|
+
|
|
459
|
+
Returns True if all preconditions and postconditions hold, else False (collapses to ⊥).
|
|
460
|
+
"""
|
|
461
|
+
ctx = dict(context or {})
|
|
462
|
+
ctx["input"] = input_card
|
|
463
|
+
ctx["output"] = output_card
|
|
464
|
+
|
|
465
|
+
pre_list = []
|
|
466
|
+
if isinstance(v_pre, str):
|
|
467
|
+
pre_list = [v_pre]
|
|
468
|
+
elif isinstance(v_pre, list):
|
|
469
|
+
pre_list = v_pre
|
|
470
|
+
|
|
471
|
+
for cond in pre_list:
|
|
472
|
+
res = cls.evaluate(cond, ctx)
|
|
473
|
+
if not res.passed:
|
|
474
|
+
return False
|
|
475
|
+
|
|
476
|
+
post_list = []
|
|
477
|
+
if isinstance(v_post, str):
|
|
478
|
+
post_list = [v_post]
|
|
479
|
+
elif isinstance(v_post, list):
|
|
480
|
+
post_list = v_post
|
|
481
|
+
|
|
482
|
+
for cond in post_list:
|
|
483
|
+
res = cls.evaluate(cond, ctx)
|
|
484
|
+
if not res.passed:
|
|
485
|
+
return False
|
|
486
|
+
|
|
487
|
+
return True
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
evaluate_vcard_meet = CelEvaluator.evaluate_vcard_meet
|
|
491
|
+
|
clm/layer0/codec.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Layer 0: Stateless Kenotic Codecs & Canonical Serialization.
|
|
2
|
+
|
|
3
|
+
Canonical Path: kernel/scripts/clm/layer0/codec.py
|
|
4
|
+
Parity: Rust `src/layer0/codec.rs`, TypeScript `src/layer0/codec.ts`
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import yaml
|
|
13
|
+
|
|
14
|
+
_B58_ALPHABET = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
|
15
|
+
_B58_MAP = [-1] * 128
|
|
16
|
+
for i, c in enumerate(_B58_ALPHABET):
|
|
17
|
+
_B58_MAP[c] = i
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def to_hex(data: bytes) -> str:
|
|
21
|
+
"""Encode bytes to lowercase hexadecimal string."""
|
|
22
|
+
return data.hex().lower()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def from_hex(hex_str: str) -> bytes:
|
|
26
|
+
"""Decode hexadecimal string into bytes."""
|
|
27
|
+
return bytes.fromhex(hex_str.strip())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def to_base58(data: bytes) -> str:
|
|
31
|
+
"""Encode bytes to Base58 string (Bitcoin alphabet)."""
|
|
32
|
+
zeroes = 0
|
|
33
|
+
while zeroes < len(data) and data[zeroes] == 0:
|
|
34
|
+
zeroes += 1
|
|
35
|
+
|
|
36
|
+
size = (len(data) - zeroes) * 138 // 100 + 1
|
|
37
|
+
b58 = bytearray(size)
|
|
38
|
+
|
|
39
|
+
for b in data[zeroes:]:
|
|
40
|
+
carry = b
|
|
41
|
+
j = 0
|
|
42
|
+
for k in range(size - 1, -1, -1):
|
|
43
|
+
if carry == 0 and j >= size:
|
|
44
|
+
break
|
|
45
|
+
carry += 256 * b58[k]
|
|
46
|
+
b58[k] = carry % 58
|
|
47
|
+
carry //= 58
|
|
48
|
+
j += 1
|
|
49
|
+
|
|
50
|
+
it = 0
|
|
51
|
+
while it < size and b58[it] == 0:
|
|
52
|
+
it += 1
|
|
53
|
+
|
|
54
|
+
res = "1" * zeroes + "".join(chr(_B58_ALPHABET[d]) for d in b58[it:])
|
|
55
|
+
return res
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def from_base58(s: str) -> bytes:
|
|
59
|
+
"""Decode Base58 string into bytes."""
|
|
60
|
+
clean = s.strip()
|
|
61
|
+
zeroes = 0
|
|
62
|
+
while zeroes < len(clean) and clean[zeroes] == "1":
|
|
63
|
+
zeroes += 1
|
|
64
|
+
|
|
65
|
+
size = len(clean) * 733 // 1000 + 1
|
|
66
|
+
b256 = bytearray(size)
|
|
67
|
+
|
|
68
|
+
for ch in clean[zeroes:]:
|
|
69
|
+
code = ord(ch)
|
|
70
|
+
if code >= 128 or _B58_MAP[code] == -1:
|
|
71
|
+
raise ValueError(f"Invalid Base58 character: {ch}")
|
|
72
|
+
carry = _B58_MAP[code]
|
|
73
|
+
j = 0
|
|
74
|
+
for k in range(size - 1, -1, -1):
|
|
75
|
+
if carry == 0 and j >= size:
|
|
76
|
+
break
|
|
77
|
+
carry += 58 * b256[k]
|
|
78
|
+
b256[k] = carry % 256
|
|
79
|
+
carry //= 256
|
|
80
|
+
j += 1
|
|
81
|
+
|
|
82
|
+
it = 0
|
|
83
|
+
while it < size and b256[it] == 0:
|
|
84
|
+
it += 1
|
|
85
|
+
|
|
86
|
+
return bytes(zeroes) + bytes(b256[it:])
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _sort_json_value(val: Any) -> Any:
|
|
90
|
+
if isinstance(val, dict):
|
|
91
|
+
return {k: _sort_json_value(v) for k, v in sorted(val.items())}
|
|
92
|
+
if isinstance(val, list):
|
|
93
|
+
return [_sort_json_value(x) for x in val]
|
|
94
|
+
return val
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def canonical_json(val: Any) -> str:
|
|
98
|
+
"""Canonical JSON serialization with recursively sorted keys and no whitespace."""
|
|
99
|
+
sorted_obj = _sort_json_value(val)
|
|
100
|
+
return json.dumps(sorted_obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def canonical_yaml(val: Any) -> str:
|
|
104
|
+
"""Canonical YAML serialization."""
|
|
105
|
+
sorted_obj = _sort_json_value(val)
|
|
106
|
+
return yaml.safe_dump(sorted_obj, sort_keys=True)
|