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,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
mcard/ontology/collection.py
|
|
3
|
+
MCardCollection Resolver Prototype
|
|
4
|
+
Implements unified management and resolution across all three MCard morphologies:
|
|
5
|
+
- DirectoryMCard
|
|
6
|
+
- FileMCard
|
|
7
|
+
- UriMCard
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
import yaml
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MorphologyType(str, Enum):
|
|
20
|
+
DIRECTORY = "directory"
|
|
21
|
+
FILE = "file"
|
|
22
|
+
URI = "uri"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class DirectoryMCard:
|
|
27
|
+
path: Path
|
|
28
|
+
role: str
|
|
29
|
+
manifest: dict[str, Any]
|
|
30
|
+
mcard_morphology: str = "directory"
|
|
31
|
+
tree_hash: str = ""
|
|
32
|
+
|
|
33
|
+
def __post_init__(self):
|
|
34
|
+
if not self.tree_hash:
|
|
35
|
+
self.tree_hash = self._compute_tree_hash()
|
|
36
|
+
|
|
37
|
+
def _compute_tree_hash(self) -> str:
|
|
38
|
+
# Canonical hash of manifest content
|
|
39
|
+
manifest_str = yaml.dump(self.manifest, sort_keys=True)
|
|
40
|
+
return hashlib.sha256(manifest_str.encode("utf-8")).hexdigest()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class FileMCard:
|
|
45
|
+
hash: str
|
|
46
|
+
content: str | dict[str, Any]
|
|
47
|
+
mime_type: str
|
|
48
|
+
g_time: int
|
|
49
|
+
mcard_morphology: str = "file"
|
|
50
|
+
tags: list[str] = field(default_factory=list)
|
|
51
|
+
handles: list[str] = field(default_factory=list)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class UriMCard:
|
|
56
|
+
uri: str
|
|
57
|
+
protocol: str
|
|
58
|
+
mcard_morphology: str = "uri"
|
|
59
|
+
digest: str = ""
|
|
60
|
+
entrypoint: str = ""
|
|
61
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class MCardCollection:
|
|
65
|
+
"""Unified collection and resolver for all three MCard morphologies."""
|
|
66
|
+
|
|
67
|
+
def __init__(self):
|
|
68
|
+
self._entries: dict[str, DirectoryMCard | FileMCard | UriMCard] = {}
|
|
69
|
+
|
|
70
|
+
def register_directory(self, path: str | Path) -> DirectoryMCard:
|
|
71
|
+
dir_path = Path(path).expanduser().resolve()
|
|
72
|
+
manifest_file = dir_path / "mcard.yaml"
|
|
73
|
+
if not manifest_file.exists():
|
|
74
|
+
raise FileNotFoundError(f"Missing mcard.yaml in directory: {dir_path}")
|
|
75
|
+
|
|
76
|
+
with open(manifest_file, encoding="utf-8") as f:
|
|
77
|
+
manifest = yaml.safe_load(f)
|
|
78
|
+
|
|
79
|
+
role = manifest.get("role", "developmental-subsystem")
|
|
80
|
+
dm = DirectoryMCard(path=dir_path, role=role, manifest=manifest)
|
|
81
|
+
self._entries[f"dir:{dir_path.name}"] = dm
|
|
82
|
+
return dm
|
|
83
|
+
|
|
84
|
+
def register_file(
|
|
85
|
+
self,
|
|
86
|
+
content: str | dict[str, Any],
|
|
87
|
+
mime_type: str = "text/plain",
|
|
88
|
+
g_time: int = 1,
|
|
89
|
+
) -> FileMCard:
|
|
90
|
+
if isinstance(content, dict):
|
|
91
|
+
raw_bytes = yaml.dump(content, sort_keys=True).encode("utf-8")
|
|
92
|
+
else:
|
|
93
|
+
raw_bytes = content.encode("utf-8")
|
|
94
|
+
|
|
95
|
+
file_hash = hashlib.sha256(raw_bytes).hexdigest()
|
|
96
|
+
fm = FileMCard(
|
|
97
|
+
hash=file_hash, content=content, mime_type=mime_type, g_time=g_time
|
|
98
|
+
)
|
|
99
|
+
self._entries[f"file:{file_hash}"] = fm
|
|
100
|
+
return fm
|
|
101
|
+
|
|
102
|
+
def register_uri(
|
|
103
|
+
self, uri: str, protocol: str = "", entrypoint: str = ""
|
|
104
|
+
) -> UriMCard:
|
|
105
|
+
if not protocol:
|
|
106
|
+
protocol = uri.split("://")[0] if "://" in uri else "uri"
|
|
107
|
+
|
|
108
|
+
um = UriMCard(uri=uri, protocol=protocol, entrypoint=entrypoint)
|
|
109
|
+
self._entries[f"uri:{uri}"] = um
|
|
110
|
+
return um
|
|
111
|
+
|
|
112
|
+
def get(self, key: str) -> DirectoryMCard | FileMCard | UriMCard | None:
|
|
113
|
+
return self._entries.get(key)
|
|
114
|
+
|
|
115
|
+
def list_by_morphology(
|
|
116
|
+
self, morphology: str
|
|
117
|
+
) -> list[DirectoryMCard | FileMCard | UriMCard]:
|
|
118
|
+
return [
|
|
119
|
+
item
|
|
120
|
+
for item in self._entries.values()
|
|
121
|
+
if item.mcard_morphology == morphology
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
def count(self) -> int:
|
|
125
|
+
return len(self._entries)
|
clm/layer0/parser.py
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
"""Unified Two-Pass CLM Parser (DV-374-01).
|
|
2
|
+
|
|
3
|
+
Implements the unified CLM interpretation pipeline:
|
|
4
|
+
- Pass 1 (Structural - ⊤): Path reference interception, CAS file wrapping into MCards,
|
|
5
|
+
environment variable substitution, and dimension extraction. Fully subsumes legacy ClmRoot.
|
|
6
|
+
- Pass 2 (Semantic - Lattice Descent): Triadic normalization ({abstract, concrete, balanced, meta}),
|
|
7
|
+
compilation of CEL pre/postcondition invariants into VCard meet gates, and compilation of
|
|
8
|
+
Baldwin modularity operators into Coloured Petri Net (CPN) subgraphs.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Callable
|
|
18
|
+
|
|
19
|
+
import yaml
|
|
20
|
+
|
|
21
|
+
from clm.layer0.cel_eval import CelEvaluator
|
|
22
|
+
from clm.layer0.model.card import MCard
|
|
23
|
+
from clm.layer0.model.dictionary import ClmReservedWords, DataSemanticType
|
|
24
|
+
from clm.layer0.model.pcard import PCard
|
|
25
|
+
from clm.layer0.model.vcard import VCard
|
|
26
|
+
|
|
27
|
+
_ENV_VAR_PATTERN = re.compile(r"\$\{([A-Za-z0-9_]+)(?::-([^}]*))?\}|\$([A-Za-z0-9_]+)")
|
|
28
|
+
|
|
29
|
+
# Compiler hook for Baldwin Modularity Operators (Dependency Inversion: Layer 1 registers compiler)
|
|
30
|
+
BaldwinCompilerFn = Callable[[list[dict[str, Any]]], dict[str, Any]]
|
|
31
|
+
_baldwin_compiler: BaldwinCompilerFn | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def register_baldwin_compiler(compiler: BaldwinCompilerFn) -> None:
|
|
35
|
+
"""Register a Baldwin CPN compiler from Layer 1."""
|
|
36
|
+
global _baldwin_compiler
|
|
37
|
+
_baldwin_compiler = compiler
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def substitute_env_vars(text: str, env: dict[str, str] | None = None) -> str:
|
|
41
|
+
"""Substitutes ${VAR:-default} and $VAR occurrences from env or os.environ."""
|
|
42
|
+
source_env = os.environ if env is None else env
|
|
43
|
+
|
|
44
|
+
def _replace(match: re.Match) -> str:
|
|
45
|
+
var_name = match.group(1) or match.group(3)
|
|
46
|
+
default_val = match.group(2) if match.group(2) is not None else ""
|
|
47
|
+
return source_env.get(var_name, default_val)
|
|
48
|
+
|
|
49
|
+
return _ENV_VAR_PATTERN.sub(_replace, text)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class ParsedClmAst:
|
|
54
|
+
"""Canonical representation of a parsed CLM specification."""
|
|
55
|
+
|
|
56
|
+
raw: dict[str, Any]
|
|
57
|
+
abstract: dict[str, Any]
|
|
58
|
+
concrete: dict[str, Any]
|
|
59
|
+
balanced: dict[str, Any]
|
|
60
|
+
meta: dict[str, Any]
|
|
61
|
+
preconditions: list[str] = field(default_factory=list)
|
|
62
|
+
postconditions: list[str] = field(default_factory=list)
|
|
63
|
+
cel_evaluators: dict[str, CelEvaluator] = field(default_factory=dict)
|
|
64
|
+
baldwin_nets: dict[str, Any] = field(default_factory=dict)
|
|
65
|
+
cards: tuple[MCard, MCard, PCard, VCard, list[MCard]] | None = None
|
|
66
|
+
chapter: dict[str, Any] | None = None
|
|
67
|
+
chapter_name: str | None = None
|
|
68
|
+
chapter_number: int | None = None
|
|
69
|
+
narrative: str | None = None
|
|
70
|
+
|
|
71
|
+
def to_dict(self) -> dict[str, Any]:
|
|
72
|
+
"""Serializes the AST into a structural dictionary for AST parity validation."""
|
|
73
|
+
data = {
|
|
74
|
+
"abstract": self.abstract,
|
|
75
|
+
"concrete": self.concrete,
|
|
76
|
+
"balanced": self.balanced,
|
|
77
|
+
"meta": self.meta,
|
|
78
|
+
"preconditions": self.preconditions,
|
|
79
|
+
"postconditions": self.postconditions,
|
|
80
|
+
"baldwin_operator_count": len(self.baldwin_nets),
|
|
81
|
+
}
|
|
82
|
+
if self.chapter_name is not None:
|
|
83
|
+
data["chapter_name"] = self.chapter_name
|
|
84
|
+
if self.chapter_number is not None:
|
|
85
|
+
data["chapter_number"] = self.chapter_number
|
|
86
|
+
if self.narrative is not None:
|
|
87
|
+
data["narrative"] = self.narrative
|
|
88
|
+
return data
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class UnifiedClmParser:
|
|
92
|
+
"""Unified Two-Pass CLM Parser subsuming legacy ClmRoot and implementing multi-pass semantic descent."""
|
|
93
|
+
|
|
94
|
+
# ── Pass 1: Structural Resolution & Path Interception ───────────────────
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def extract_dimension(cls, val: Any, keys: list[str]) -> Any:
|
|
98
|
+
"""Recursively searches through a YAML dict tree to find dimension keys."""
|
|
99
|
+
if isinstance(val, dict):
|
|
100
|
+
for key_str in keys:
|
|
101
|
+
if key_str in val:
|
|
102
|
+
return val[key_str]
|
|
103
|
+
for child_val in val.values():
|
|
104
|
+
found = cls.extract_dimension(child_val, keys)
|
|
105
|
+
if found is not None:
|
|
106
|
+
return found
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def traverse_and_intercept(
|
|
111
|
+
cls,
|
|
112
|
+
value: Any,
|
|
113
|
+
dictionary: ClmReservedWords | None,
|
|
114
|
+
g_time: str,
|
|
115
|
+
extra_cards: list[MCard],
|
|
116
|
+
clm_dir: Path | None = None,
|
|
117
|
+
) -> None:
|
|
118
|
+
"""Mutably traverses the YAML tree, wrapping physical files into CAS MCards and replacing paths with content hashes."""
|
|
119
|
+
if isinstance(value, dict):
|
|
120
|
+
changes = {}
|
|
121
|
+
for k, v in value.items():
|
|
122
|
+
is_path_ref = False
|
|
123
|
+
if dictionary and isinstance(k, str):
|
|
124
|
+
semantic_type = dictionary.lookup(k)
|
|
125
|
+
if semantic_type == DataSemanticType.PathReference:
|
|
126
|
+
is_path_ref = True
|
|
127
|
+
elif isinstance(k, str) and k in {
|
|
128
|
+
"file",
|
|
129
|
+
"file_path",
|
|
130
|
+
"path",
|
|
131
|
+
"source_path",
|
|
132
|
+
"script_path",
|
|
133
|
+
"content_path",
|
|
134
|
+
}:
|
|
135
|
+
is_path_ref = True
|
|
136
|
+
|
|
137
|
+
if is_path_ref and isinstance(v, str):
|
|
138
|
+
target_path = (clm_dir / v) if clm_dir else Path(v)
|
|
139
|
+
if target_path.exists() and target_path.is_file():
|
|
140
|
+
try:
|
|
141
|
+
bytes_content = target_path.read_bytes()
|
|
142
|
+
new_card = MCard(content=bytes_content)
|
|
143
|
+
changes[k] = str(new_card.hash)
|
|
144
|
+
extra_cards.append(new_card)
|
|
145
|
+
except Exception:
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
cls.traverse_and_intercept(v, dictionary, g_time, extra_cards, clm_dir)
|
|
149
|
+
|
|
150
|
+
for k_str, new_val in changes.items():
|
|
151
|
+
value[k_str] = new_val
|
|
152
|
+
|
|
153
|
+
elif isinstance(value, list):
|
|
154
|
+
for element in value:
|
|
155
|
+
cls.traverse_and_intercept(
|
|
156
|
+
element, dictionary, g_time, extra_cards, clm_dir
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
@classmethod
|
|
160
|
+
def map_to_cards(
|
|
161
|
+
cls,
|
|
162
|
+
yaml_bytes: bytes,
|
|
163
|
+
g_time: str,
|
|
164
|
+
dictionary: ClmReservedWords | None = None,
|
|
165
|
+
clm_dir: Path | None = None,
|
|
166
|
+
) -> tuple[MCard, MCard, PCard, VCard, list[MCard]]:
|
|
167
|
+
"""Maps raw YAML bytes into Card topology (root, abstract, concrete, balanced, extra)."""
|
|
168
|
+
yaml_str = yaml_bytes.decode("utf-8")
|
|
169
|
+
extra_cards: list[MCard] = []
|
|
170
|
+
|
|
171
|
+
root_mcard = MCard(content=yaml_bytes)
|
|
172
|
+
parsed = yaml.safe_load(yaml_str) or {}
|
|
173
|
+
|
|
174
|
+
if dictionary or clm_dir:
|
|
175
|
+
cls.traverse_and_intercept(parsed, dictionary, g_time, extra_cards, clm_dir)
|
|
176
|
+
|
|
177
|
+
# 2. Abstract Section -> MCard
|
|
178
|
+
abstract_dim = cls.extract_dimension(parsed, ["abstract", "abstract_spec", "A"])
|
|
179
|
+
abstract_bytes = (
|
|
180
|
+
yaml.safe_dump(abstract_dim).encode("utf-8")
|
|
181
|
+
if abstract_dim is not None
|
|
182
|
+
else b"null\n"
|
|
183
|
+
)
|
|
184
|
+
abstract_mcard = MCard(content=abstract_bytes)
|
|
185
|
+
|
|
186
|
+
# 3. Concrete Section -> PCard
|
|
187
|
+
concrete_dim = cls.extract_dimension(parsed, ["concrete", "concrete_impl", "C"])
|
|
188
|
+
concrete_bytes = (
|
|
189
|
+
yaml.safe_dump(concrete_dim).encode("utf-8")
|
|
190
|
+
if concrete_dim is not None
|
|
191
|
+
else b"{}\n"
|
|
192
|
+
)
|
|
193
|
+
pcard = PCard(concrete_bytes)
|
|
194
|
+
|
|
195
|
+
# 4. Balanced Section -> VCard
|
|
196
|
+
balanced_dim = cls.extract_dimension(parsed, ["balanced", "balanced_exp", "B"])
|
|
197
|
+
balanced_bytes = (
|
|
198
|
+
yaml.safe_dump(balanced_dim).encode("utf-8")
|
|
199
|
+
if balanced_dim is not None
|
|
200
|
+
else b"{}\n"
|
|
201
|
+
)
|
|
202
|
+
vcard = VCard(balanced_bytes)
|
|
203
|
+
|
|
204
|
+
return (root_mcard, abstract_mcard, pcard, vcard, extra_cards)
|
|
205
|
+
|
|
206
|
+
# ── Pass 2: Semantic Analysis & Lattice Descent ─────────────────────────
|
|
207
|
+
|
|
208
|
+
@classmethod
|
|
209
|
+
def parse(
|
|
210
|
+
cls,
|
|
211
|
+
input_data: str | bytes | dict[str, Any],
|
|
212
|
+
clm_dir: Path | None = None,
|
|
213
|
+
env: dict[str, str] | None = None,
|
|
214
|
+
g_time: str = "1970-01-01T00:00:00Z",
|
|
215
|
+
dictionary: ClmReservedWords | None = None,
|
|
216
|
+
) -> ParsedClmAst:
|
|
217
|
+
"""Full Two-Pass CLM Parse: Structural Ingestion + Semantic Descent."""
|
|
218
|
+
# Normalize input to string and raw dict
|
|
219
|
+
if isinstance(input_data, bytes):
|
|
220
|
+
raw_text = input_data.decode("utf-8")
|
|
221
|
+
raw_text = substitute_env_vars(raw_text, env)
|
|
222
|
+
raw_dict = yaml.safe_load(raw_text) or {}
|
|
223
|
+
source_bytes = input_data
|
|
224
|
+
elif isinstance(input_data, str):
|
|
225
|
+
raw_text = substitute_env_vars(input_data, env)
|
|
226
|
+
raw_dict = yaml.safe_load(raw_text) or {}
|
|
227
|
+
source_bytes = raw_text.encode("utf-8")
|
|
228
|
+
elif isinstance(input_data, dict):
|
|
229
|
+
raw_dict = input_data
|
|
230
|
+
source_bytes = yaml.safe_dump(raw_dict).encode("utf-8")
|
|
231
|
+
else:
|
|
232
|
+
raise TypeError(f"Unsupported input type for CLM parser: {type(input_data)}")
|
|
233
|
+
|
|
234
|
+
# Pass 1: Card mapping & path interception
|
|
235
|
+
cards = cls.map_to_cards(
|
|
236
|
+
yaml_bytes=source_bytes,
|
|
237
|
+
g_time=g_time,
|
|
238
|
+
dictionary=dictionary,
|
|
239
|
+
clm_dir=clm_dir,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# Pass 2: Triadic Normalization & Dialect Detection
|
|
243
|
+
clm_root = raw_dict.get("clm", raw_dict) if isinstance(raw_dict, dict) else {}
|
|
244
|
+
if not isinstance(clm_root, dict):
|
|
245
|
+
clm_root = {}
|
|
246
|
+
|
|
247
|
+
abstract = (
|
|
248
|
+
clm_root.get("abstract")
|
|
249
|
+
or clm_root.get("abstract_spec")
|
|
250
|
+
or clm_root.get("A")
|
|
251
|
+
or {}
|
|
252
|
+
)
|
|
253
|
+
concrete = (
|
|
254
|
+
clm_root.get("concrete")
|
|
255
|
+
or clm_root.get("concrete_impl")
|
|
256
|
+
or clm_root.get("C")
|
|
257
|
+
or {}
|
|
258
|
+
)
|
|
259
|
+
balanced = (
|
|
260
|
+
clm_root.get("balanced")
|
|
261
|
+
or clm_root.get("balanced_exp")
|
|
262
|
+
or clm_root.get("B")
|
|
263
|
+
or {}
|
|
264
|
+
)
|
|
265
|
+
meta = (
|
|
266
|
+
clm_root.get("_meta")
|
|
267
|
+
or clm_root.get("meta")
|
|
268
|
+
or raw_dict.get("_meta")
|
|
269
|
+
or raw_dict.get("meta")
|
|
270
|
+
or {}
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Dialect B Synthesis: Formal CPN Net Specification
|
|
274
|
+
places = raw_dict.get("places") or clm_root.get("places")
|
|
275
|
+
transitions = raw_dict.get("transitions") or clm_root.get("transitions")
|
|
276
|
+
if places or transitions:
|
|
277
|
+
if not abstract:
|
|
278
|
+
abstract = {"places": places or [], "transitions": transitions or []}
|
|
279
|
+
if not concrete:
|
|
280
|
+
concrete = {"transitions": transitions or [], "arrows": raw_dict.get("arrows", [])}
|
|
281
|
+
if not balanced:
|
|
282
|
+
balanced = {"guards": raw_dict.get("guards", []), "invariants": raw_dict.get("invariants", [])}
|
|
283
|
+
|
|
284
|
+
# Chapter & Narrative Metadata Extraction
|
|
285
|
+
chapter_data = raw_dict.get("chapter") or clm_root.get("chapter")
|
|
286
|
+
chapter_dict: dict[str, Any] | None = chapter_data if isinstance(chapter_data, dict) else None
|
|
287
|
+
chapter_name: str | None = None
|
|
288
|
+
chapter_number: int | None = None
|
|
289
|
+
narrative: str | None = None
|
|
290
|
+
|
|
291
|
+
if chapter_dict:
|
|
292
|
+
chapter_name = chapter_dict.get("title") or chapter_dict.get("name")
|
|
293
|
+
num_val = chapter_dict.get("id") or chapter_dict.get("number")
|
|
294
|
+
if num_val is not None:
|
|
295
|
+
try:
|
|
296
|
+
chapter_number = int(num_val)
|
|
297
|
+
except (ValueError, TypeError):
|
|
298
|
+
chapter_number = None
|
|
299
|
+
narrative = chapter_dict.get("narrative") or chapter_dict.get("description")
|
|
300
|
+
else:
|
|
301
|
+
narrative = raw_dict.get("narrative") or clm_root.get("narrative")
|
|
302
|
+
|
|
303
|
+
# Precondition / Postcondition Extraction
|
|
304
|
+
preconditions: list[str] = []
|
|
305
|
+
postconditions: list[str] = []
|
|
306
|
+
|
|
307
|
+
if isinstance(concrete, dict):
|
|
308
|
+
pre = concrete.get("precondition") or concrete.get("vcard_pre")
|
|
309
|
+
if isinstance(pre, str):
|
|
310
|
+
preconditions.append(pre)
|
|
311
|
+
elif isinstance(pre, dict):
|
|
312
|
+
vpre = pre.get("vcard_pre") or pre.get("expr")
|
|
313
|
+
if isinstance(vpre, str):
|
|
314
|
+
preconditions.append(vpre)
|
|
315
|
+
|
|
316
|
+
post = concrete.get("postcondition") or concrete.get("vcard_post")
|
|
317
|
+
if isinstance(post, str):
|
|
318
|
+
postconditions.append(post)
|
|
319
|
+
elif isinstance(post, dict):
|
|
320
|
+
vpost = post.get("vcard_post") or post.get("expr")
|
|
321
|
+
if isinstance(vpost, str):
|
|
322
|
+
postconditions.append(vpost)
|
|
323
|
+
|
|
324
|
+
# Compile CEL Invariant Gates
|
|
325
|
+
cel_evaluators: dict[str, CelEvaluator] = {}
|
|
326
|
+
for idx, cond in enumerate(preconditions):
|
|
327
|
+
cel_evaluators[f"pre_{idx}"] = CelEvaluator(cond)
|
|
328
|
+
for idx, cond in enumerate(postconditions):
|
|
329
|
+
cel_evaluators[f"post_{idx}"] = CelEvaluator(cond)
|
|
330
|
+
|
|
331
|
+
# Compile Baldwin Modularity Operators (Dependency Inversion for INV-383-02)
|
|
332
|
+
baldwin_nets: dict[str, Any] = {}
|
|
333
|
+
operators = (
|
|
334
|
+
raw_dict.get("operators")
|
|
335
|
+
or clm_root.get("operators")
|
|
336
|
+
or (concrete.get("operators") if isinstance(concrete, dict) else None)
|
|
337
|
+
)
|
|
338
|
+
if isinstance(operators, list):
|
|
339
|
+
if _baldwin_compiler is not None:
|
|
340
|
+
try:
|
|
341
|
+
baldwin_nets = _baldwin_compiler(operators)
|
|
342
|
+
except Exception:
|
|
343
|
+
pass
|
|
344
|
+
else:
|
|
345
|
+
import sys
|
|
346
|
+
|
|
347
|
+
gen_mod = sys.modules.get("clm.layer1.algebra_baldwin_generators")
|
|
348
|
+
arrow_mod = sys.modules.get("clm.layer1.arrow_compiler")
|
|
349
|
+
if gen_mod and arrow_mod:
|
|
350
|
+
try:
|
|
351
|
+
Arrow = getattr(arrow_mod, "Arrow", None)
|
|
352
|
+
BaldwinCpnGenerators = getattr(gen_mod, "BaldwinCpnGenerators", None)
|
|
353
|
+
if Arrow and BaldwinCpnGenerators:
|
|
354
|
+
for op_def in operators:
|
|
355
|
+
if not isinstance(op_def, dict):
|
|
356
|
+
continue
|
|
357
|
+
op_name = op_def.get("name")
|
|
358
|
+
if not op_name:
|
|
359
|
+
continue
|
|
360
|
+
if op_name == "split":
|
|
361
|
+
base_arr = Arrow.arr(lambda x: x, name="base")
|
|
362
|
+
net = BaldwinCpnGenerators.split(
|
|
363
|
+
base_arr,
|
|
364
|
+
branch_a_fn=lambda x: x,
|
|
365
|
+
branch_b_fn=lambda x: x,
|
|
366
|
+
name_prefix=f"ast_{op_name}",
|
|
367
|
+
)
|
|
368
|
+
baldwin_nets[op_name] = net
|
|
369
|
+
elif op_name == "invert":
|
|
370
|
+
pair_arr = Arrow.arr(lambda x: (x, x), name="pair")
|
|
371
|
+
net = BaldwinCpnGenerators.invert(pair_arr)
|
|
372
|
+
baldwin_nets[op_name] = net
|
|
373
|
+
except Exception:
|
|
374
|
+
pass
|
|
375
|
+
|
|
376
|
+
return ParsedClmAst(
|
|
377
|
+
raw=raw_dict,
|
|
378
|
+
abstract=abstract if isinstance(abstract, dict) else {"spec": abstract},
|
|
379
|
+
concrete=concrete if isinstance(concrete, dict) else {"impl": concrete},
|
|
380
|
+
balanced=balanced if isinstance(balanced, dict) else {"exp": balanced},
|
|
381
|
+
meta=meta if isinstance(meta, dict) else {"meta": meta},
|
|
382
|
+
preconditions=preconditions,
|
|
383
|
+
postconditions=postconditions,
|
|
384
|
+
cel_evaluators=cel_evaluators,
|
|
385
|
+
baldwin_nets=baldwin_nets,
|
|
386
|
+
cards=cards,
|
|
387
|
+
chapter=chapter_dict,
|
|
388
|
+
chapter_name=chapter_name,
|
|
389
|
+
chapter_number=chapter_number,
|
|
390
|
+
narrative=narrative,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
@classmethod
|
|
394
|
+
def parse_file(
|
|
395
|
+
cls,
|
|
396
|
+
file_path: str | Path,
|
|
397
|
+
env: dict[str, str] | None = None,
|
|
398
|
+
g_time: str = "1970-01-01T00:00:00Z",
|
|
399
|
+
dictionary: ClmReservedWords | None = None,
|
|
400
|
+
) -> ParsedClmAst:
|
|
401
|
+
"""Parses a CLM manifest or chapter specification file from disk."""
|
|
402
|
+
path = Path(file_path).resolve()
|
|
403
|
+
if not path.exists():
|
|
404
|
+
raise FileNotFoundError(f"CLM specification file not found: {path}")
|
|
405
|
+
|
|
406
|
+
if path.is_dir():
|
|
407
|
+
candidates = ["manifest.yaml", "manifest.yml", "clm.yaml", "clm.yml", "index.yaml"]
|
|
408
|
+
for cand in candidates:
|
|
409
|
+
cand_path = path / cand
|
|
410
|
+
if cand_path.exists():
|
|
411
|
+
return cls.parse_file(cand_path, env=env, g_time=g_time, dictionary=dictionary)
|
|
412
|
+
yaml_files = sorted(list(path.glob("*.yaml")) + list(path.glob("*.yml")))
|
|
413
|
+
if yaml_files:
|
|
414
|
+
return cls.parse_file(yaml_files[0], env=env, g_time=g_time, dictionary=dictionary)
|
|
415
|
+
raise ValueError(f"No CLM YAML manifests found in directory: {path}")
|
|
416
|
+
|
|
417
|
+
source_bytes = path.read_bytes()
|
|
418
|
+
return cls.parse(
|
|
419
|
+
input_data=source_bytes,
|
|
420
|
+
clm_dir=path.parent,
|
|
421
|
+
env=env,
|
|
422
|
+
g_time=g_time,
|
|
423
|
+
dictionary=dictionary,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
# Canonical alias for backward compatibility across the repository
|
|
428
|
+
ClmRoot = UnifiedClmParser
|
|
429
|
+
|
clm/layer0/relations.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Relational card queries (Sprint 133.1 + 133.6).
|
|
2
|
+
|
|
3
|
+
Extracted from ``CLMRepository`` in ``db.py``. These methods import ``Card``
|
|
4
|
+
from ``cross_cutting.mcard`` lazily (inside function bodies) to avoid a
|
|
5
|
+
``layer0 -> cross_cutting`` import cycle. Isolating them here makes the
|
|
6
|
+
cycle explicit and documented rather than hidden inside a 1,100-line
|
|
7
|
+
god-module.
|
|
8
|
+
|
|
9
|
+
Long-term fix (Sprint 134): move ``Card`` to ``layer0/card.py`` so
|
|
10
|
+
``layer0`` has no upward dependency on ``cross_cutting`` at all.
|
|
11
|
+
"""
|
|
12
|
+
import contextlib
|
|
13
|
+
import json as _json
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def query_card_relations(repo: Any, card_hash: str, pillar: str = "execution_log") -> dict[str, list]:
|
|
18
|
+
"""Resolve related cards via SQLite relational calculus (Sprint 131.2).
|
|
19
|
+
|
|
20
|
+
Returns ``{"inputs": [...], "outputs": [...], "processes": [...]}``
|
|
21
|
+
where each entry is a parsed ``Card``.
|
|
22
|
+
"""
|
|
23
|
+
from clm.cross_cutting.mcard import Card
|
|
24
|
+
|
|
25
|
+
relations: dict[str, list] = {"inputs": [], "outputs": [], "processes": []}
|
|
26
|
+
|
|
27
|
+
content = repo.get_card_content(card_hash, pillar)
|
|
28
|
+
if not content:
|
|
29
|
+
return relations
|
|
30
|
+
try:
|
|
31
|
+
card = Card.from_yaml(content)
|
|
32
|
+
except Exception:
|
|
33
|
+
return relations
|
|
34
|
+
|
|
35
|
+
if card.token_type == "pcard":
|
|
36
|
+
for h in card.input_place_hashes:
|
|
37
|
+
c = repo.get_card_content(h, pillar)
|
|
38
|
+
if c:
|
|
39
|
+
with contextlib.suppress(Exception):
|
|
40
|
+
relations["inputs"].append(Card.from_yaml(c))
|
|
41
|
+
for cand in _scan_cards_containing(repo, pillar, card_hash):
|
|
42
|
+
if getattr(cand, "producer_pcard_hash", None) == card_hash:
|
|
43
|
+
relations["outputs"].append(cand)
|
|
44
|
+
|
|
45
|
+
elif card.token_type == "vcard":
|
|
46
|
+
if card.pcard_hash:
|
|
47
|
+
c = repo.get_card_content(card.pcard_hash, pillar)
|
|
48
|
+
if c:
|
|
49
|
+
with contextlib.suppress(Exception):
|
|
50
|
+
relations["processes"].append(Card.from_yaml(c))
|
|
51
|
+
|
|
52
|
+
else: # mcard or state
|
|
53
|
+
for cand in _scan_cards_containing(repo, pillar, card_hash):
|
|
54
|
+
if (cand.token_type == "pcard" and card_hash in (cand.input_place_hashes or [])) \
|
|
55
|
+
or getattr(cand, "producer_pcard_hash", None) == card_hash:
|
|
56
|
+
relations["processes"].append(cand)
|
|
57
|
+
|
|
58
|
+
return relations
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _scan_cards_containing(repo: Any, pillar: str, needle: str) -> list:
|
|
62
|
+
"""Scan all cards in a pillar and return parsed Cards whose content
|
|
63
|
+
mentions ``needle``. Used as the FTS5 fallback per §6.2 risk row."""
|
|
64
|
+
from clm.cross_cutting.mcard import Card
|
|
65
|
+
|
|
66
|
+
rows_json = repo.db.execute(pillar, "SELECT content FROM card;", as_json=True)
|
|
67
|
+
out = []
|
|
68
|
+
for row in _json.loads(rows_json or "[]"):
|
|
69
|
+
content = row.get("content", "")
|
|
70
|
+
if needle in content:
|
|
71
|
+
with contextlib.suppress(Exception):
|
|
72
|
+
out.append(Card.from_yaml(content))
|
|
73
|
+
return out
|