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,434 @@
|
|
|
1
|
+
"""Cross-Cutting — PCard (Π-type / Control Plane / Lens)
|
|
2
|
+
|
|
3
|
+
PCard is the computation type / Dependent Product (Π-type) of the MVP Cards triad.
|
|
4
|
+
It encodes transformations as polynomial functors over MCard references.
|
|
5
|
+
PCard functions as the Monadic Executor: handling dynamic control flow and
|
|
6
|
+
dependent sequencing via Reader, State, and Writer monadic patterns.
|
|
7
|
+
|
|
8
|
+
Design references:
|
|
9
|
+
- MVP Cards Design Rationale (§PCard — Control Plane)
|
|
10
|
+
- Function-Number Duality
|
|
11
|
+
- The Currying Adjunction
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import hashlib
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
import time
|
|
20
|
+
from collections.abc import Callable
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from clm.cross_cutting.cel_engine import ( # noqa: F401
|
|
24
|
+
_DEP_LOGGER,
|
|
25
|
+
_DEPRECATED_FUNCS,
|
|
26
|
+
_evaluate_cel,
|
|
27
|
+
_evaluate_expr,
|
|
28
|
+
_resolve_arg,
|
|
29
|
+
_smart_evaluate,
|
|
30
|
+
)
|
|
31
|
+
from clm.cross_cutting.clm_logger import get_logger
|
|
32
|
+
from clm.cross_cutting.telemetry import jsonl_emit_typed
|
|
33
|
+
from clm.cross_cutting.timeutil import default_agent_did
|
|
34
|
+
from clm.layer0.context import CLMContext
|
|
35
|
+
from clm.layer0.utils import clm_hash_string as hash_string
|
|
36
|
+
|
|
37
|
+
_logger = get_logger("clm.pcard")
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# Sprint 384 (DEL-384-02): Re-export canonical PCard from Layer 0 card_triad
|
|
41
|
+
from clm.layer0.model.card_triad import PCard # noqa: F401
|
|
42
|
+
|
|
43
|
+
# Dependency Inversion hooks for Layer 2 classifier integration
|
|
44
|
+
_classifier_hook: tuple[Callable, Callable] | None = None
|
|
45
|
+
_classifier_hook_resolver: Callable[[], tuple[Callable, Callable] | None] | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def register_classifier_hook(classify_fn: Callable, dialect_fn: Callable) -> None:
|
|
49
|
+
"""Register classifier and dialect detection functions (Dependency Inversion)."""
|
|
50
|
+
global _classifier_hook
|
|
51
|
+
_classifier_hook = (classify_fn, dialect_fn)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def register_classifier_hook_resolver(resolver: Callable[[], tuple[Callable, Callable] | None]) -> None:
|
|
55
|
+
"""Register lazy resolver for classifier hook."""
|
|
56
|
+
global _classifier_hook_resolver
|
|
57
|
+
_classifier_hook_resolver = resolver
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# PCard Serialization
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
def serialize_pcard(
|
|
65
|
+
ctx: CLMContext,
|
|
66
|
+
operation: str,
|
|
67
|
+
runtime: str,
|
|
68
|
+
script_hash: str,
|
|
69
|
+
input_place_hashes: list[str],
|
|
70
|
+
timestamp: str,
|
|
71
|
+
) -> str:
|
|
72
|
+
"""Serialize a PCard token and persist it to the execution log.
|
|
73
|
+
|
|
74
|
+
A PCard (Π-type / Dependent Product) encodes a transformation as a
|
|
75
|
+
polynomial functor over MCard references. This function freezes the
|
|
76
|
+
live PCard into an MCard (Function-Number Duality: a verb stored as
|
|
77
|
+
a noun / gerund) and commits it to the execution_log pillar.
|
|
78
|
+
|
|
79
|
+
Sprint 131.5: YAML emission now delegates to ``Card(token_type="pcard", ...).to_yaml()``
|
|
80
|
+
so the unified model is the single source of truth; the ``save_card_and_handle``
|
|
81
|
+
and ``jsonl_emit`` side effects are preserved.
|
|
82
|
+
"""
|
|
83
|
+
from clm.cross_cutting.mcard import Card
|
|
84
|
+
|
|
85
|
+
pcard_yaml = Card(
|
|
86
|
+
token_type="pcard",
|
|
87
|
+
operation=operation,
|
|
88
|
+
runtime=runtime,
|
|
89
|
+
script_hash=script_hash,
|
|
90
|
+
timestamp=timestamp,
|
|
91
|
+
input_place_hashes=input_place_hashes,
|
|
92
|
+
).to_yaml()
|
|
93
|
+
|
|
94
|
+
pcard_hash = hash_string(pcard_yaml)
|
|
95
|
+
|
|
96
|
+
handle_name = f"pcard:{operation}:{pcard_hash}"
|
|
97
|
+
ctx.repo.save_card_and_handle(
|
|
98
|
+
handle_name, pcard_hash, pcard_yaml, "execution_log",
|
|
99
|
+
timestamp=timestamp, replace=False,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
jsonl_emit_typed("pcard_serialized", {
|
|
103
|
+
"pcard_hash": pcard_hash,
|
|
104
|
+
"operation": operation,
|
|
105
|
+
"runtime": runtime,
|
|
106
|
+
"script_hash": script_hash,
|
|
107
|
+
"input_place_hashes": input_place_hashes,
|
|
108
|
+
}, ctx.root)
|
|
109
|
+
return pcard_hash
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
# Operation Registry
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
_TRIDB_OP_NAMES = [
|
|
117
|
+
"verify_tables", "evaluator_functions", "mcard_kernel", "memorize_mcard",
|
|
118
|
+
"polyglot_ingest", "modular_adapters", "polyglot_execution", "polyglot_parity",
|
|
119
|
+
"polyglot_generalization", "normalize_syntax", "validate_type_lattice",
|
|
120
|
+
"type_system", "exclude", "assert_algebraic_operators", "check_precondition",
|
|
121
|
+
"execute_composed_pcard", "verify_authorization", "validate_session_lifecycle",
|
|
122
|
+
"verify_zero_fs_isolation", "dispatch_over_loopback",
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class OperationRegistry:
|
|
127
|
+
"""Maps operation names to callable implementations.
|
|
128
|
+
|
|
129
|
+
The default registry bootstraps the Tri-Database for all Stage 1
|
|
130
|
+
operations. Additional operations can be registered at runtime.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
def __init__(self) -> None:
|
|
134
|
+
import sys
|
|
135
|
+
target = "clm.layer2.storage"
|
|
136
|
+
mod = sys.modules.get(target)
|
|
137
|
+
if mod is None:
|
|
138
|
+
__import__(target)
|
|
139
|
+
mod = sys.modules.get(target)
|
|
140
|
+
init_tridb = mod.init_tridb
|
|
141
|
+
# Sprint 132.3: replaced 20 identical lambdas with functools.partial
|
|
142
|
+
# Sprint 132.5: lazy-init TriDB once per tmp_dir (was 20x init_tridb)
|
|
143
|
+
self._init_tridb = init_tridb
|
|
144
|
+
self._tridb_initialised: set[str] = set()
|
|
145
|
+
self._ops: dict[str, Callable] = dict.fromkeys(_TRIDB_OP_NAMES, self._lazy_tridb)
|
|
146
|
+
|
|
147
|
+
def _lazy_tridb(self, tmp_dir: str, **kw: Any) -> int:
|
|
148
|
+
"""Lazy-init TriDB once per tmp_dir, then no-op (Sprint 132.5)."""
|
|
149
|
+
if tmp_dir not in self._tridb_initialised:
|
|
150
|
+
self._init_tridb(tmp_dir)
|
|
151
|
+
self._tridb_initialised.add(tmp_dir)
|
|
152
|
+
return 0
|
|
153
|
+
|
|
154
|
+
def register(self, name: str, fn: Callable) -> None:
|
|
155
|
+
"""Register an operation handler: ``fn(tmp_dir, **kw) -> int``."""
|
|
156
|
+
self._ops[name] = fn
|
|
157
|
+
|
|
158
|
+
def dispatch(self, operation: str, tmp_dir: str, **kwargs: Any) -> int:
|
|
159
|
+
"""Dispatch *operation* and return its exit code."""
|
|
160
|
+
fn = self._ops.get(operation)
|
|
161
|
+
if fn is None:
|
|
162
|
+
raise ValueError(f"Unknown operation: {operation}")
|
|
163
|
+
return fn(tmp_dir, **kwargs)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
# Post-context builder — DELETED in Sprint 128.5
|
|
168
|
+
# Replaced by clm.cross_cutting.cel_store.build_store() which returns a
|
|
169
|
+
# CelStore (typed, Flux-pattern) whose .to_dict() produces the same context
|
|
170
|
+
# dict shape that op handlers expect.
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------------------------------
|
|
175
|
+
# Sprint 130.2 — Runtime-agnostic verify_test_file expansion
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
_TEST_RUNTIMES = {
|
|
179
|
+
".py": ("python", lambda clm_root: {
|
|
180
|
+
"CLM_ROOT": {"ref": "env.clm_root"},
|
|
181
|
+
"PYTHONPATH": {"op": "path_join", "parts": [{"ref": "env.clm_root"}, "kernel/scripts"]},
|
|
182
|
+
}),
|
|
183
|
+
".sh": ("shell", lambda clm_root: {
|
|
184
|
+
"CLM_ROOT": {"ref": "env.clm_root"},
|
|
185
|
+
}),
|
|
186
|
+
".rs": ("rust", lambda clm_root: {
|
|
187
|
+
"CLM_ROOT": {"ref": "env.clm_root"},
|
|
188
|
+
"CARGO_TARGET_DIR": {"op": "path_join", "parts": [{"ref": "env.clm_root"}, "target"]},
|
|
189
|
+
}),
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _expand_verify_test_file(verify_file: str, operation: str) -> dict:
|
|
194
|
+
"""Expand a ``verify_test_file`` shorthand into vcard_pre + vcard_post blocks.
|
|
195
|
+
|
|
196
|
+
Auto-detects the test file extension (.py, .sh, .rs) and builds
|
|
197
|
+
runtime-appropriate env vars. Returns a dict with ``vcard_pre`` and
|
|
198
|
+
``vcard_post`` keys.
|
|
199
|
+
"""
|
|
200
|
+
import os
|
|
201
|
+
|
|
202
|
+
ext = os.path.splitext(verify_file)[1]
|
|
203
|
+
runtime_info = _TEST_RUNTIMES.get(ext, _TEST_RUNTIMES[".py"])
|
|
204
|
+
label, env_builder = runtime_info
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
"vcard_pre": [{
|
|
208
|
+
"test_case": f"{operation} {label} suite exists",
|
|
209
|
+
"gate": "gate:file_exists",
|
|
210
|
+
"value": {
|
|
211
|
+
"op": "path_join",
|
|
212
|
+
"parts": [{"ref": "env.clm_root"}, verify_file],
|
|
213
|
+
},
|
|
214
|
+
}],
|
|
215
|
+
"vcard_post": [{
|
|
216
|
+
"test_case": f"{operation} {label} suite passes",
|
|
217
|
+
"gate": "gate:hash_matches",
|
|
218
|
+
"expected": 0,
|
|
219
|
+
"actual": {
|
|
220
|
+
"op": "run_test_suite",
|
|
221
|
+
"test_file": verify_file,
|
|
222
|
+
"env": env_builder({"ref": "env.clm_root"}),
|
|
223
|
+
},
|
|
224
|
+
}],
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
# Balanced Runner (Orchestrator)
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
class BalancedRunner:
|
|
233
|
+
"""Orchestrates Face B (Balanced Expectations) verification.
|
|
234
|
+
|
|
235
|
+
For each ``pending_case`` in the manifest:
|
|
236
|
+
|
|
237
|
+
1. Evaluates ``vcard_pre`` gates (precondition checks).
|
|
238
|
+
2. Creates a sandboxed environment (``CLMTestSandbox``).
|
|
239
|
+
3. Wraps execution in a ``VCardSandwich`` (Gatekeeper Pattern).
|
|
240
|
+
4. Dispatches the operation via ``OperationRegistry``.
|
|
241
|
+
5. Evaluates ``vcard_post`` gates (postcondition assertions).
|
|
242
|
+
6. Seals the sandwich with pass/fail.
|
|
243
|
+
7. Reports aggregate results.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
def __init__(
|
|
247
|
+
self,
|
|
248
|
+
clm_root: str,
|
|
249
|
+
gate_evaluator: Any | None = None,
|
|
250
|
+
op_registry: OperationRegistry | None = None,
|
|
251
|
+
) -> None:
|
|
252
|
+
self.clm_root = clm_root
|
|
253
|
+
# Sprint 132.1: GateEvaluator import moved to cross_cutting/__init__.py;
|
|
254
|
+
# import here to avoid circular dependency at module load
|
|
255
|
+
from clm.cross_cutting.vcard import GateEvaluator
|
|
256
|
+
self.gates = gate_evaluator or GateEvaluator()
|
|
257
|
+
self.ops = op_registry or OperationRegistry()
|
|
258
|
+
|
|
259
|
+
def run(self, clm_file: str) -> int:
|
|
260
|
+
"""Execute all pending cases from a Face B manifest.
|
|
261
|
+
|
|
262
|
+
Returns 0 if every case passes, 1 otherwise.
|
|
263
|
+
"""
|
|
264
|
+
import yaml
|
|
265
|
+
|
|
266
|
+
test_harness_path = os.path.join(self.clm_root, "tests", "00_microkernel")
|
|
267
|
+
if test_harness_path not in sys.path:
|
|
268
|
+
sys.path.insert(0, test_harness_path)
|
|
269
|
+
|
|
270
|
+
import test_utils
|
|
271
|
+
|
|
272
|
+
from clm.cross_cutting.vcard import VCardSandwich
|
|
273
|
+
from clm.layer0.context import CLMContext
|
|
274
|
+
|
|
275
|
+
if _classifier_hook is not None:
|
|
276
|
+
classify_clm, detect_dialect = _classifier_hook
|
|
277
|
+
elif _classifier_hook_resolver is not None:
|
|
278
|
+
hook = _classifier_hook_resolver()
|
|
279
|
+
classify_clm, detect_dialect = hook if hook else (lambda *a, **k: None, lambda *a, **k: None)
|
|
280
|
+
else:
|
|
281
|
+
classify_clm, detect_dialect = (lambda *a, **k: None, lambda *a, **k: None)
|
|
282
|
+
|
|
283
|
+
if not os.path.exists(clm_file):
|
|
284
|
+
_logger.error(f"FATAL: CLM manifest file not found at {clm_file}")
|
|
285
|
+
return 1
|
|
286
|
+
|
|
287
|
+
with open(clm_file, encoding="utf-8") as f:
|
|
288
|
+
manifest = yaml.safe_load(f)
|
|
289
|
+
|
|
290
|
+
clm_block = manifest.get("clm", manifest)
|
|
291
|
+
pending_cases = clm_block.get("pending_cases", [])
|
|
292
|
+
|
|
293
|
+
overall_failed = 0
|
|
294
|
+
|
|
295
|
+
for case in pending_cases:
|
|
296
|
+
rc = self._run_case(
|
|
297
|
+
case, test_utils, CLMDB, CLMRepository, CLMContext,
|
|
298
|
+
VCardSandwich, classify_clm, detect_dialect,
|
|
299
|
+
)
|
|
300
|
+
if rc != 0:
|
|
301
|
+
overall_failed += 1
|
|
302
|
+
|
|
303
|
+
if overall_failed == 0:
|
|
304
|
+
_logger.info("All dynamic Face B assertions verified successfully!")
|
|
305
|
+
return 0
|
|
306
|
+
else:
|
|
307
|
+
_logger.error(f"Face B verification failed: {overall_failed} test suite(s) had errors.")
|
|
308
|
+
return 1
|
|
309
|
+
|
|
310
|
+
def _run_case(
|
|
311
|
+
self, case: dict, test_utils: Any,
|
|
312
|
+
CLMDB: type, CLMRepository: type, CLMContext: type,
|
|
313
|
+
VCardSandwich: type, classify_clm: Callable, detect_dialect: Callable,
|
|
314
|
+
) -> int:
|
|
315
|
+
"""Execute a single pending case. Returns 0 on success, 1 on failure.
|
|
316
|
+
|
|
317
|
+
Sprint 128.5: Uses a unified ``CelStore`` for both pre and post gates
|
|
318
|
+
(progressive enrichment).
|
|
319
|
+
Sprint 130.2: Adds Phase 0 (verify_test_file expansion) and passes
|
|
320
|
+
``store=`` to ``seal()`` so ``flush_action_log()`` persists the
|
|
321
|
+
action_log to ``execution_log.db``.
|
|
322
|
+
"""
|
|
323
|
+
from clm.cross_cutting.cel_store import CelStore, EnvSlice, StateSlice
|
|
324
|
+
|
|
325
|
+
given = case.get("given", {})
|
|
326
|
+
step_seq = given.get("step_sequence")
|
|
327
|
+
inputs = given.get("inputs", {})
|
|
328
|
+
|
|
329
|
+
when = case.get("when", {})
|
|
330
|
+
operation = when.get("operation")
|
|
331
|
+
|
|
332
|
+
then = case.get("then", {})
|
|
333
|
+
assertions = then.get("assertions", {})
|
|
334
|
+
expected_outputs = then.get("outputs", {})
|
|
335
|
+
|
|
336
|
+
suite = test_utils.CLMTestSuite(f"Test Suite: {operation} (Step {step_seq})")
|
|
337
|
+
|
|
338
|
+
# ---- 0. Dynamic Gate Expansion (Sprint 130.2) ---------------------
|
|
339
|
+
# If verify_test_file is declared, expand it into vcard_pre +
|
|
340
|
+
# vcard_post gate blocks before evaluation. User-defined gates
|
|
341
|
+
# take precedence (prepended, not overwritten).
|
|
342
|
+
verify_file = then.get("verify_test_file")
|
|
343
|
+
if verify_file:
|
|
344
|
+
expanded = _expand_verify_test_file(verify_file, operation)
|
|
345
|
+
for gate_key in ("vcard_pre", "vcard_post"):
|
|
346
|
+
if gate_key not in assertions:
|
|
347
|
+
assertions[gate_key] = []
|
|
348
|
+
assertions[gate_key] = expanded[gate_key] + assertions[gate_key]
|
|
349
|
+
|
|
350
|
+
# ---- A. VCard Pre-Gate (minimal store) -----------------------------
|
|
351
|
+
store = CelStore(
|
|
352
|
+
state=StateSlice(inputs=inputs),
|
|
353
|
+
env=EnvSlice(
|
|
354
|
+
clm_root=self.clm_root,
|
|
355
|
+
os=os, sys=sys, time=time, hashlib=hashlib, str=str,
|
|
356
|
+
),
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
for pre_item in assertions.get("vcard_pre", []):
|
|
360
|
+
if not self.gates.evaluate_pre(pre_item, store.to_dict(), suite):
|
|
361
|
+
suite.report_results()
|
|
362
|
+
return 1
|
|
363
|
+
|
|
364
|
+
# ---- B. Sandbox + VCardSandwich (Sprint 133.4: via thunk()) ------
|
|
365
|
+
with test_utils.CLMTestSandbox(self.clm_root, copy_schema=True) as tmp_dir:
|
|
366
|
+
db = CLMDB(tmp_dir)
|
|
367
|
+
ctx = CLMContext(root=tmp_dir, db=db, zero_fs=False)
|
|
368
|
+
repo = CLMRepository(db, tmp_dir)
|
|
369
|
+
|
|
370
|
+
agent_did = default_agent_did()
|
|
371
|
+
|
|
372
|
+
# Sprint 133.4: dispatch_fn replaces manual sandwich.__enter__/seal.
|
|
373
|
+
def _dispatch_fn(sandwich, store_arg):
|
|
374
|
+
step_passed = True
|
|
375
|
+
error_msg = ""
|
|
376
|
+
rc = 0
|
|
377
|
+
try:
|
|
378
|
+
rc = self.ops.dispatch(operation, tmp_dir)
|
|
379
|
+
except Exception as e:
|
|
380
|
+
step_passed = False
|
|
381
|
+
error_msg = str(e)
|
|
382
|
+
suite.tu_fail(f"Exception during transition execution: {e}")
|
|
383
|
+
|
|
384
|
+
# ---- D. VCard Post-Gate (enriched store) -----------------
|
|
385
|
+
from clm.cross_cutting.cel_store import build_store
|
|
386
|
+
post_store = build_store(
|
|
387
|
+
inputs=inputs, outputs=expected_outputs, rc=rc,
|
|
388
|
+
tmp_dir=tmp_dir, db=db, ctx=ctx, repo=repo,
|
|
389
|
+
classify_clm=classify_clm, detect_dialect=detect_dialect,
|
|
390
|
+
clm_root=self.clm_root,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
for post_item in assertions.get("vcard_post", []):
|
|
394
|
+
if not self.gates.evaluate_post(post_item, post_store.to_dict(), suite):
|
|
395
|
+
step_passed = False
|
|
396
|
+
|
|
397
|
+
if suite.failed > 0 or not step_passed:
|
|
398
|
+
return 1, f"Verification failed: {error_msg or 'Suite assertions failed.'}"
|
|
399
|
+
return 0, "success"
|
|
400
|
+
|
|
401
|
+
from clm.cross_cutting.vcard import thunk
|
|
402
|
+
exit_code, _ = thunk(
|
|
403
|
+
store=None, action={}, ctx=ctx, operation=operation,
|
|
404
|
+
agent_did=agent_did,
|
|
405
|
+
dispatch_fn=_dispatch_fn,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
if exit_code != 0:
|
|
409
|
+
suite.report_results()
|
|
410
|
+
return 1
|
|
411
|
+
|
|
412
|
+
suite.report_results()
|
|
413
|
+
return 0
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
# ---------------------------------------------------------------------------
|
|
417
|
+
# Public API
|
|
418
|
+
# ---------------------------------------------------------------------------
|
|
419
|
+
|
|
420
|
+
def run_balanced_expectations(clm_root: str, clm_file: str) -> int:
|
|
421
|
+
"""Run all pending_cases from a Face B (Balanced Expectations) manifest.
|
|
422
|
+
|
|
423
|
+
This is the public entry point. Internally it creates a
|
|
424
|
+
:class:`BalancedRunner` with default gates and operations, then
|
|
425
|
+
delegates to :meth:`BalancedRunner.run`.
|
|
426
|
+
"""
|
|
427
|
+
runner = BalancedRunner(clm_root)
|
|
428
|
+
return runner.run(clm_file)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
# ---------------------------------------------------------------------------
|
|
432
|
+
# Absorbed re-exports — MOVED to cross_cutting/__init__.py in Sprint 132.1
|
|
433
|
+
# (facade inversion: pcard/ no longer imports upward into layer0/1/2)
|
|
434
|
+
# ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Declarative CLM Operation Registry (Sprint 179).
|
|
2
|
+
|
|
3
|
+
Replaces string-patched exec() with a strongly-typed, decorator-based dispatch mechanism.
|
|
4
|
+
Supports command aliases, metadata inspection, and isolated context injection.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from clm.cross_cutting.errors import OperationNotFoundError
|
|
13
|
+
from clm.layer0.context import CLMContext
|
|
14
|
+
|
|
15
|
+
_OPERATION_REGISTRY: dict[str, Callable[[list[str], CLMContext], int]] = {}
|
|
16
|
+
_OPERATION_METADATA: dict[str, dict[str, Any]] = {}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def clm_operation(
|
|
20
|
+
name: str,
|
|
21
|
+
*,
|
|
22
|
+
aliases: list[str] | None = None,
|
|
23
|
+
description: str = "",
|
|
24
|
+
) -> Callable:
|
|
25
|
+
"""Decorator to register a function as a CLM CLI / evaluator operation."""
|
|
26
|
+
|
|
27
|
+
def decorator(
|
|
28
|
+
fn: Callable[[list[str], CLMContext], int]
|
|
29
|
+
) -> Callable[[list[str], CLMContext], int]:
|
|
30
|
+
_OPERATION_REGISTRY[name] = fn
|
|
31
|
+
meta = {
|
|
32
|
+
"name": name,
|
|
33
|
+
"description": description,
|
|
34
|
+
"aliases": aliases or [],
|
|
35
|
+
"fn_name": fn.__name__,
|
|
36
|
+
"module": fn.__module__,
|
|
37
|
+
}
|
|
38
|
+
_OPERATION_METADATA[name] = meta
|
|
39
|
+
if aliases:
|
|
40
|
+
for alias in aliases:
|
|
41
|
+
_OPERATION_REGISTRY[alias] = fn
|
|
42
|
+
_OPERATION_METADATA[alias] = meta
|
|
43
|
+
return fn
|
|
44
|
+
|
|
45
|
+
return decorator
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def dispatch_operation(
|
|
49
|
+
name: str,
|
|
50
|
+
args: list[str] | None = None,
|
|
51
|
+
ctx: CLMContext | None = None,
|
|
52
|
+
) -> int:
|
|
53
|
+
"""Dispatch a registered operation by name with typed arguments and isolated context."""
|
|
54
|
+
fn = _OPERATION_REGISTRY.get(name)
|
|
55
|
+
if not fn:
|
|
56
|
+
available = ", ".join(sorted(set(_OPERATION_REGISTRY.keys())))
|
|
57
|
+
raise OperationNotFoundError(
|
|
58
|
+
f"Unknown CLM operation: '{name}'. Available operations: [{available}]"
|
|
59
|
+
)
|
|
60
|
+
context = ctx or CLMContext.current()
|
|
61
|
+
return fn(args or [], context)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_registered_operations() -> list[str]:
|
|
65
|
+
"""Return all unique registered operation names."""
|
|
66
|
+
return sorted(set(_OPERATION_REGISTRY.keys()))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_operation_metadata(name: str) -> dict[str, Any] | None:
|
|
70
|
+
"""Return metadata dict for an operation name or alias."""
|
|
71
|
+
return _OPERATION_METADATA.get(name)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Resource resolution module for CLM packages and developmental workspaces.
|
|
2
|
+
|
|
3
|
+
Provides robust resolution of bundled schema definitions, SQL scripts, and
|
|
4
|
+
configuration templates via PEP 302/366/3002 importlib.resources standards with
|
|
5
|
+
seamless fallback to local source tree layouts during development.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import importlib.resources
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_schema_path(filename: str = "mcard_schema.sql") -> Path:
|
|
16
|
+
"""Resolve the absolute filesystem path for a schema file.
|
|
17
|
+
|
|
18
|
+
Search priority:
|
|
19
|
+
1. Bundled package resources in `clm.schemas` (wheel/installed mode)
|
|
20
|
+
2. Package-relative path `clm/schemas/`
|
|
21
|
+
3. Monorepo workspace `CLM_ROOT/types/schemas/` or `types/schemas/`
|
|
22
|
+
4. Parent directory `../types/schemas/`
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
filename: Name of the schema file (e.g. 'mcard_schema.sql', 'cel_schema.yaml').
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Path object pointing to the existing schema file.
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
FileNotFoundError: If the schema file cannot be located across all candidates.
|
|
32
|
+
"""
|
|
33
|
+
# 1. Try importlib.resources (installed wheel package mode)
|
|
34
|
+
try:
|
|
35
|
+
resource_ref = importlib.resources.files("clm.schemas").joinpath(filename)
|
|
36
|
+
# In Python 3.9+, Traversable might be a concrete path or in a zip
|
|
37
|
+
candidate = Path(str(resource_ref))
|
|
38
|
+
if candidate.is_file():
|
|
39
|
+
return candidate
|
|
40
|
+
except Exception:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
# 2. Try relative to this file's package tree: kernel/scripts/clm/schemas/
|
|
44
|
+
pkg_schema = Path(__file__).resolve().parent.parent / "schemas" / filename
|
|
45
|
+
if pkg_schema.is_file():
|
|
46
|
+
return pkg_schema
|
|
47
|
+
|
|
48
|
+
# 3. Try CLM_ROOT environment variable or current directory
|
|
49
|
+
clm_root_env = os.environ.get("CLM_ROOT")
|
|
50
|
+
if clm_root_env:
|
|
51
|
+
root_path = Path(clm_root_env).resolve()
|
|
52
|
+
candidate = root_path / "types" / "schemas" / filename
|
|
53
|
+
if candidate.is_file():
|
|
54
|
+
return candidate
|
|
55
|
+
candidate = root_path / "clm" / "types" / "schemas" / filename
|
|
56
|
+
if candidate.is_file():
|
|
57
|
+
return candidate
|
|
58
|
+
|
|
59
|
+
# 4. Try current working directory and parent paths
|
|
60
|
+
cwd = Path.cwd().resolve()
|
|
61
|
+
for base in [cwd, cwd.parent, cwd.parent.parent]:
|
|
62
|
+
candidate = base / "types" / "schemas" / filename
|
|
63
|
+
if candidate.is_file():
|
|
64
|
+
return candidate
|
|
65
|
+
candidate = base / "clm" / "types" / "schemas" / filename
|
|
66
|
+
if candidate.is_file():
|
|
67
|
+
return candidate
|
|
68
|
+
|
|
69
|
+
raise FileNotFoundError(
|
|
70
|
+
f"CLM schema '{filename}' could not be resolved via importlib.resources or workspace search."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_schema_content(filename: str = "mcard_schema.sql") -> str:
|
|
75
|
+
"""Read and return the text contents of a CLM schema file.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
filename: Name of the schema file.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
String content of the schema file decoded as UTF-8.
|
|
82
|
+
"""
|
|
83
|
+
path = get_schema_path(filename)
|
|
84
|
+
return path.read_text(encoding="utf-8")
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Cryptographic secret container with explicit zeroization and RAII protection (Sprint 176).
|
|
2
|
+
|
|
3
|
+
Prevents accidental key leaks into log files or tracebacks via custom __repr__ and __str__,
|
|
4
|
+
and scrubs memory buffer with null bytes on drop or context exit.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import ctypes
|
|
10
|
+
from typing import Self
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SecretBytes:
|
|
14
|
+
"""Container for sensitive cryptographic material with explicit zeroization."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, data: bytes) -> None:
|
|
17
|
+
self._buf = bytearray(data)
|
|
18
|
+
|
|
19
|
+
def reveal(self) -> bytes:
|
|
20
|
+
"""Access raw bytes. Caller should avoid assigning to long-lived variables."""
|
|
21
|
+
if not self._buf:
|
|
22
|
+
raise ValueError("SecretBytes buffer has been zeroized")
|
|
23
|
+
return bytes(self._buf)
|
|
24
|
+
|
|
25
|
+
def zeroize(self) -> None:
|
|
26
|
+
"""Explicitly overwrite buffer memory with null bytes."""
|
|
27
|
+
if self._buf:
|
|
28
|
+
ctypes.memset((ctypes.c_char * len(self._buf)).from_buffer(self._buf), 0, len(self._buf))
|
|
29
|
+
self._buf.clear()
|
|
30
|
+
|
|
31
|
+
def __enter__(self) -> Self:
|
|
32
|
+
return self
|
|
33
|
+
|
|
34
|
+
def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None:
|
|
35
|
+
self.zeroize()
|
|
36
|
+
|
|
37
|
+
def __del__(self) -> None:
|
|
38
|
+
self.zeroize()
|
|
39
|
+
|
|
40
|
+
def __repr__(self) -> str:
|
|
41
|
+
return "<SecretBytes [REDACTED]>"
|
|
42
|
+
|
|
43
|
+
def __str__(self) -> str:
|
|
44
|
+
return "<SecretBytes [REDACTED]>"
|