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/layer2/classifier.py
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
"""CLM Document Classifier & Structural Hierarchy Analysis.
|
|
2
|
+
|
|
3
|
+
Canonical Path: kernel/scripts/clm/layer2/classifier.py
|
|
4
|
+
Parity: Rust `src/layer2/storage.rs`, TypeScript `src/layer2/triDb.ts`
|
|
5
|
+
|
|
6
|
+
Classifies CLM documents by structure (Function vs Number), detects
|
|
7
|
+
their dialect (M, A, B, or C type), and provides full recursive hierarchy
|
|
8
|
+
introspection across nested CLM composition trees.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from enum import Enum
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import yaml
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def classify_clm(clm_data: Any) -> str:
|
|
22
|
+
"""Classifies a CLM document structure to determine its high-level type.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
clm_data (Any): Parsed YAML or JSON dict representing the CLM.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str: "Function" if it has abstract_spec and concrete_impl, otherwise "Number".
|
|
29
|
+
"""
|
|
30
|
+
if not isinstance(clm_data, dict):
|
|
31
|
+
return "Number"
|
|
32
|
+
inner = clm_data.get("clm", clm_data)
|
|
33
|
+
if not isinstance(inner, dict):
|
|
34
|
+
return "Number"
|
|
35
|
+
if "abstract_spec" in inner and "concrete_impl" in inner:
|
|
36
|
+
return "Function"
|
|
37
|
+
return "Number"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def detect_dialect(clm_data: Any, classification: str) -> str:
|
|
41
|
+
"""Detects the dialect (M, A, B, or C type) of a CLM Function.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
clm_data (Any): Parsed YAML/JSON.
|
|
45
|
+
classification (str): The result of classify_clm.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
str: The dialect identifier string or 'UNKNOWN'.
|
|
49
|
+
"""
|
|
50
|
+
if classification != "Function":
|
|
51
|
+
return "UNKNOWN"
|
|
52
|
+
inner = clm_data.get("clm", clm_data)
|
|
53
|
+
concrete = inner.get("concrete_impl", {})
|
|
54
|
+
if not isinstance(concrete, dict):
|
|
55
|
+
concrete = inner.get("concrete", {})
|
|
56
|
+
if not isinstance(concrete, dict):
|
|
57
|
+
return "UNKNOWN"
|
|
58
|
+
|
|
59
|
+
process = concrete.get("process", {})
|
|
60
|
+
process_type = None
|
|
61
|
+
if isinstance(process, dict):
|
|
62
|
+
process_type = process.get("type")
|
|
63
|
+
elif isinstance(process, list) and len(process) > 0 and isinstance(process[0], dict):
|
|
64
|
+
process_type = process[0].get("type")
|
|
65
|
+
|
|
66
|
+
disp = None
|
|
67
|
+
exec_block = concrete.get("process_behavior_exec", {})
|
|
68
|
+
if isinstance(exec_block, dict):
|
|
69
|
+
disp = exec_block.get("dispatcher")
|
|
70
|
+
|
|
71
|
+
rt = concrete.get("runtime")
|
|
72
|
+
op = concrete.get("operation", "invoke")
|
|
73
|
+
|
|
74
|
+
if process_type:
|
|
75
|
+
return f"M:{process_type}"
|
|
76
|
+
elif disp:
|
|
77
|
+
return f"A:{disp}"
|
|
78
|
+
elif rt:
|
|
79
|
+
return f"B:{rt}:{op}"
|
|
80
|
+
else:
|
|
81
|
+
c_rt = concrete.get("runtime")
|
|
82
|
+
c_op = concrete.get("operation", "invoke")
|
|
83
|
+
if c_rt:
|
|
84
|
+
return f"C:{c_rt}:{c_op}"
|
|
85
|
+
return "UNKNOWN"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
89
|
+
# Recursive CLM Hierarchy Introspection
|
|
90
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class CLMType(Enum):
|
|
94
|
+
"""Type classification for CLM nodes."""
|
|
95
|
+
|
|
96
|
+
CHAPTER = "chapter" # Has chapter metadata
|
|
97
|
+
PCARD = "pcard" # Pure PCard (no chapter wrapper)
|
|
98
|
+
COMPOSED = "composed" # Sequential composition (then)
|
|
99
|
+
PARALLEL = "parallel" # Tensor product (and_also)
|
|
100
|
+
REFERENCE = "reference" # Reference to another CLM by hash
|
|
101
|
+
UNKNOWN = "unknown"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class CompositionType(Enum):
|
|
105
|
+
"""Type of composition between CLMs."""
|
|
106
|
+
|
|
107
|
+
SEQUENTIAL = "sequential" # A ; B (Coend composition)
|
|
108
|
+
PARALLEL = "parallel" # A ⊗ B (Tensor product)
|
|
109
|
+
NONE = "none" # Leaf node
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class CLMNode:
|
|
114
|
+
"""Represents a node in the CLM hierarchy tree.
|
|
115
|
+
|
|
116
|
+
Each node corresponds to a CLM (PCard) in the composition graph.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
# Content hash (MCard identity)
|
|
120
|
+
hash: str
|
|
121
|
+
|
|
122
|
+
# Node classification
|
|
123
|
+
clm_type: CLMType
|
|
124
|
+
|
|
125
|
+
# CLM dimensions
|
|
126
|
+
abstract: dict[str, Any] | None = None
|
|
127
|
+
concrete: dict[str, Any] | None = None
|
|
128
|
+
balanced: dict[str, Any] | None = None
|
|
129
|
+
|
|
130
|
+
# Chapter metadata (if present)
|
|
131
|
+
chapter: dict[str, Any] | None = None
|
|
132
|
+
|
|
133
|
+
# Composition metadata
|
|
134
|
+
composition_type: CompositionType = CompositionType.NONE
|
|
135
|
+
|
|
136
|
+
# Children nodes (for composed CLMs)
|
|
137
|
+
children: list[CLMNode] = field(default_factory=list)
|
|
138
|
+
|
|
139
|
+
# Parent hash (for traversal)
|
|
140
|
+
parent_hash: str | None = None
|
|
141
|
+
|
|
142
|
+
# Depth in the hierarchy (root = 0)
|
|
143
|
+
depth: int = 0
|
|
144
|
+
|
|
145
|
+
# Declared runtime
|
|
146
|
+
runtime: str = "unknown"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dataclass
|
|
150
|
+
class CLMIntrospectionResult:
|
|
151
|
+
"""Result of CLM hierarchy introspection.
|
|
152
|
+
|
|
153
|
+
Contains the full tree structure and analysis metadata.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
# Root node of the hierarchy
|
|
157
|
+
root: CLMNode
|
|
158
|
+
|
|
159
|
+
# All nodes indexed by hash
|
|
160
|
+
nodes: dict[str, CLMNode]
|
|
161
|
+
|
|
162
|
+
# Maximum depth of the hierarchy
|
|
163
|
+
max_depth: int
|
|
164
|
+
|
|
165
|
+
# Total node count
|
|
166
|
+
node_count: int
|
|
167
|
+
|
|
168
|
+
# Detected circular dependencies
|
|
169
|
+
cycles: list[list[str]]
|
|
170
|
+
|
|
171
|
+
# Runtime distribution
|
|
172
|
+
runtimes: dict[str, int]
|
|
173
|
+
|
|
174
|
+
# Composition statistics
|
|
175
|
+
sequential_count: int
|
|
176
|
+
parallel_count: int
|
|
177
|
+
leaf_count: int
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class CLMIntrospector:
|
|
181
|
+
"""Recursive CLM Hierarchy Introspector.
|
|
182
|
+
|
|
183
|
+
Analyzes CLM composition graphs, extracting structure and metadata
|
|
184
|
+
from arbitrarily nested CLM hierarchies.
|
|
185
|
+
|
|
186
|
+
Usage:
|
|
187
|
+
introspector = CLMIntrospector(collection)
|
|
188
|
+
result = await introspector.introspect("root_pcard_hash")
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
def __init__(self, collection: Any = None, max_depth: int = 100):
|
|
192
|
+
"""Initialize the introspector.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
collection: MCard collection for content retrieval.
|
|
196
|
+
max_depth: Maximum recursion depth to prevent infinite loops.
|
|
197
|
+
"""
|
|
198
|
+
self.logger = logging.getLogger(__name__)
|
|
199
|
+
self._collection = collection
|
|
200
|
+
self._max_depth = max_depth
|
|
201
|
+
self._visited: set[str] = set()
|
|
202
|
+
self._nodes: dict[str, CLMNode] = {}
|
|
203
|
+
self._cycles: list[list[str]] = []
|
|
204
|
+
|
|
205
|
+
async def introspect(self, pcard_hash: str) -> CLMIntrospectionResult:
|
|
206
|
+
"""Introspect a CLM hierarchy starting from the given root.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
pcard_hash: Hash of the root PCard.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
CLMIntrospectionResult containing the full hierarchy analysis.
|
|
213
|
+
"""
|
|
214
|
+
self._visited = set()
|
|
215
|
+
self._nodes = {}
|
|
216
|
+
self._cycles = []
|
|
217
|
+
|
|
218
|
+
root = await self._build_tree(pcard_hash, depth=0, path=[])
|
|
219
|
+
|
|
220
|
+
if root is None:
|
|
221
|
+
root = CLMNode(hash=pcard_hash, clm_type=CLMType.UNKNOWN)
|
|
222
|
+
|
|
223
|
+
# Calculate statistics
|
|
224
|
+
max_depth = max((n.depth for n in self._nodes.values()), default=0)
|
|
225
|
+
runtimes = self._count_runtimes()
|
|
226
|
+
seq_count, par_count, leaf_count = self._count_compositions()
|
|
227
|
+
|
|
228
|
+
return CLMIntrospectionResult(
|
|
229
|
+
root=root,
|
|
230
|
+
nodes=self._nodes,
|
|
231
|
+
max_depth=max_depth,
|
|
232
|
+
node_count=len(self._nodes),
|
|
233
|
+
cycles=self._cycles,
|
|
234
|
+
runtimes=runtimes,
|
|
235
|
+
sequential_count=seq_count,
|
|
236
|
+
parallel_count=par_count,
|
|
237
|
+
leaf_count=leaf_count,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
async def _build_tree(
|
|
241
|
+
self,
|
|
242
|
+
pcard_hash: str,
|
|
243
|
+
depth: int,
|
|
244
|
+
path: list[str],
|
|
245
|
+
parent_hash: str | None = None,
|
|
246
|
+
) -> CLMNode | None:
|
|
247
|
+
"""Recursively build the CLM tree from a PCard hash."""
|
|
248
|
+
# Check for cycles
|
|
249
|
+
if pcard_hash in path:
|
|
250
|
+
cycle = path[path.index(pcard_hash) :] + [pcard_hash]
|
|
251
|
+
self._cycles.append(cycle)
|
|
252
|
+
self.logger.warning(f"Circular dependency detected: {' -> '.join(cycle)}")
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
# Check max depth
|
|
256
|
+
if depth > self._max_depth:
|
|
257
|
+
self.logger.warning(f"Max depth {self._max_depth} exceeded at {pcard_hash}")
|
|
258
|
+
return None
|
|
259
|
+
|
|
260
|
+
# Check if already processed
|
|
261
|
+
if pcard_hash in self._nodes:
|
|
262
|
+
return self._nodes[pcard_hash]
|
|
263
|
+
|
|
264
|
+
# Get PCard content
|
|
265
|
+
clm_data = await self._get_clm_data(pcard_hash)
|
|
266
|
+
if clm_data is None:
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
# Create node
|
|
270
|
+
node = self._create_node(pcard_hash, clm_data, depth, parent_hash)
|
|
271
|
+
self._nodes[pcard_hash] = node
|
|
272
|
+
|
|
273
|
+
# Process children based on composition type
|
|
274
|
+
new_path = path + [pcard_hash]
|
|
275
|
+
|
|
276
|
+
if node.composition_type == CompositionType.SEQUENTIAL:
|
|
277
|
+
# Sequential composition: process steps
|
|
278
|
+
steps = clm_data.get("clm", {}).get("abstract", {}).get("steps", [])
|
|
279
|
+
for step_hash in steps:
|
|
280
|
+
child = await self._build_tree(
|
|
281
|
+
step_hash, depth + 1, new_path, pcard_hash
|
|
282
|
+
)
|
|
283
|
+
if child:
|
|
284
|
+
node.children.append(child)
|
|
285
|
+
|
|
286
|
+
elif node.composition_type == CompositionType.PARALLEL:
|
|
287
|
+
# Parallel composition: process components
|
|
288
|
+
left = clm_data.get("clm", {}).get("abstract", {}).get("left")
|
|
289
|
+
right = clm_data.get("clm", {}).get("abstract", {}).get("right")
|
|
290
|
+
|
|
291
|
+
if left:
|
|
292
|
+
child = await self._build_tree(left, depth + 1, new_path, pcard_hash)
|
|
293
|
+
if child:
|
|
294
|
+
node.children.append(child)
|
|
295
|
+
if right:
|
|
296
|
+
child = await self._build_tree(right, depth + 1, new_path, pcard_hash)
|
|
297
|
+
if child:
|
|
298
|
+
node.children.append(child)
|
|
299
|
+
|
|
300
|
+
# Check for recursive runtime references
|
|
301
|
+
runtime = node.runtime
|
|
302
|
+
if runtime.endswith(".yaml") or runtime.endswith(".clm"):
|
|
303
|
+
pass
|
|
304
|
+
|
|
305
|
+
return node
|
|
306
|
+
|
|
307
|
+
async def _get_clm_data(self, pcard_hash: str) -> dict[str, Any] | None:
|
|
308
|
+
"""Get CLM data from a PCard hash."""
|
|
309
|
+
if self._collection is None:
|
|
310
|
+
return None
|
|
311
|
+
|
|
312
|
+
try:
|
|
313
|
+
pcard = self._collection.get(pcard_hash)
|
|
314
|
+
if pcard is None:
|
|
315
|
+
return None
|
|
316
|
+
|
|
317
|
+
content = (
|
|
318
|
+
pcard.content
|
|
319
|
+
if isinstance(pcard.content, str)
|
|
320
|
+
else pcard.content.decode("utf-8")
|
|
321
|
+
)
|
|
322
|
+
return yaml.safe_load(content)
|
|
323
|
+
|
|
324
|
+
except (
|
|
325
|
+
yaml.YAMLError,
|
|
326
|
+
ValueError,
|
|
327
|
+
TypeError,
|
|
328
|
+
UnicodeDecodeError,
|
|
329
|
+
AttributeError,
|
|
330
|
+
) as e:
|
|
331
|
+
self.logger.error(
|
|
332
|
+
f"Failed to get CLM data for {pcard_hash}: {e}", exc_info=True
|
|
333
|
+
)
|
|
334
|
+
return None
|
|
335
|
+
|
|
336
|
+
def _create_node(
|
|
337
|
+
self,
|
|
338
|
+
pcard_hash: str,
|
|
339
|
+
clm_data: dict[str, Any],
|
|
340
|
+
depth: int,
|
|
341
|
+
parent_hash: str | None,
|
|
342
|
+
) -> CLMNode:
|
|
343
|
+
"""Create a CLMNode from parsed CLM data."""
|
|
344
|
+
# Determine CLM type
|
|
345
|
+
clm_type = self._classify_clm(clm_data)
|
|
346
|
+
|
|
347
|
+
# Extract CLM dimensions
|
|
348
|
+
clm_section = clm_data.get("clm", {})
|
|
349
|
+
abstract = clm_section.get("abstract") or clm_data.get("abstract")
|
|
350
|
+
concrete = clm_section.get("concrete") or clm_data.get("concrete")
|
|
351
|
+
balanced = clm_section.get("balanced") or clm_data.get("balanced")
|
|
352
|
+
|
|
353
|
+
# Extract chapter
|
|
354
|
+
chapter = clm_data.get("chapter")
|
|
355
|
+
|
|
356
|
+
# Determine composition type
|
|
357
|
+
composition_type = CompositionType.NONE
|
|
358
|
+
abstract_type = (abstract or {}).get("type", "")
|
|
359
|
+
|
|
360
|
+
if abstract_type == "sequential_composition":
|
|
361
|
+
composition_type = CompositionType.SEQUENTIAL
|
|
362
|
+
elif abstract_type == "tensor_product":
|
|
363
|
+
composition_type = CompositionType.PARALLEL
|
|
364
|
+
|
|
365
|
+
# Extract runtime
|
|
366
|
+
runtime = (
|
|
367
|
+
(concrete or {}).get("runtime")
|
|
368
|
+
or clm_section.get("concrete", {}).get("runtime")
|
|
369
|
+
or "unknown"
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
return CLMNode(
|
|
373
|
+
hash=pcard_hash,
|
|
374
|
+
clm_type=clm_type,
|
|
375
|
+
abstract=abstract,
|
|
376
|
+
concrete=concrete,
|
|
377
|
+
balanced=balanced,
|
|
378
|
+
chapter=chapter,
|
|
379
|
+
composition_type=composition_type,
|
|
380
|
+
children=[],
|
|
381
|
+
parent_hash=parent_hash,
|
|
382
|
+
depth=depth,
|
|
383
|
+
runtime=runtime,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
def _classify_clm(self, clm_data: dict[str, Any]) -> CLMType:
|
|
387
|
+
"""Classify the CLM by its structure."""
|
|
388
|
+
if "chapter" in clm_data:
|
|
389
|
+
return CLMType.CHAPTER
|
|
390
|
+
|
|
391
|
+
abstract = clm_data.get("clm", {}).get("abstract") or clm_data.get(
|
|
392
|
+
"abstract", {}
|
|
393
|
+
)
|
|
394
|
+
abstract_type = abstract.get("type", "") if isinstance(abstract, dict) else ""
|
|
395
|
+
|
|
396
|
+
if abstract_type == "sequential_composition":
|
|
397
|
+
return CLMType.COMPOSED
|
|
398
|
+
elif abstract_type == "tensor_product":
|
|
399
|
+
return CLMType.PARALLEL
|
|
400
|
+
elif "clm" in clm_data or "abstract" in clm_data:
|
|
401
|
+
return CLMType.PCARD
|
|
402
|
+
|
|
403
|
+
return CLMType.UNKNOWN
|
|
404
|
+
|
|
405
|
+
def _count_runtimes(self) -> dict[str, int]:
|
|
406
|
+
"""Count nodes by runtime."""
|
|
407
|
+
runtimes: dict[str, int] = {}
|
|
408
|
+
for node in self._nodes.values():
|
|
409
|
+
rt = node.runtime
|
|
410
|
+
runtimes[rt] = runtimes.get(rt, 0) + 1
|
|
411
|
+
return runtimes
|
|
412
|
+
|
|
413
|
+
def _count_compositions(self) -> tuple[int, int, int]:
|
|
414
|
+
"""Count sequential, parallel, and leaf compositions."""
|
|
415
|
+
seq = 0
|
|
416
|
+
par = 0
|
|
417
|
+
leaf = 0
|
|
418
|
+
|
|
419
|
+
for node in self._nodes.values():
|
|
420
|
+
if node.composition_type == CompositionType.SEQUENTIAL:
|
|
421
|
+
seq += 1
|
|
422
|
+
elif node.composition_type == CompositionType.PARALLEL:
|
|
423
|
+
par += 1
|
|
424
|
+
else:
|
|
425
|
+
leaf += 1
|
|
426
|
+
|
|
427
|
+
return seq, par, leaf
|
|
428
|
+
|
|
429
|
+
def to_tree_string(self, result: CLMIntrospectionResult) -> str:
|
|
430
|
+
"""Generate a tree visualization of the CLM hierarchy.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
ASCII tree representation.
|
|
434
|
+
"""
|
|
435
|
+
lines: list[str] = []
|
|
436
|
+
self._tree_to_string(result.root, lines, "", True)
|
|
437
|
+
return "\n".join(lines)
|
|
438
|
+
|
|
439
|
+
def _tree_to_string(
|
|
440
|
+
self, node: CLMNode, lines: list[str], prefix: str, is_last: bool
|
|
441
|
+
) -> None:
|
|
442
|
+
"""Recursive helper for tree visualization."""
|
|
443
|
+
connector = "└── " if is_last else "├── "
|
|
444
|
+
|
|
445
|
+
# Build node label
|
|
446
|
+
label_parts = [node.hash[:8]]
|
|
447
|
+
if node.chapter:
|
|
448
|
+
title = node.chapter.get("title", "")
|
|
449
|
+
if title:
|
|
450
|
+
label_parts.append(f"({title})")
|
|
451
|
+
label_parts.append(f"[{node.clm_type.value}]")
|
|
452
|
+
if node.runtime != "unknown":
|
|
453
|
+
label_parts.append(f"@{node.runtime}")
|
|
454
|
+
|
|
455
|
+
lines.append(prefix + connector + " ".join(label_parts))
|
|
456
|
+
|
|
457
|
+
# Process children
|
|
458
|
+
new_prefix = prefix + (" " if is_last else "│ ")
|
|
459
|
+
for i, child in enumerate(node.children):
|
|
460
|
+
self._tree_to_string(child, lines, new_prefix, i == len(node.children) - 1)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
__all__ = [
|
|
464
|
+
"classify_clm",
|
|
465
|
+
"detect_dialect",
|
|
466
|
+
"CLMType",
|
|
467
|
+
"CompositionType",
|
|
468
|
+
"CLMNode",
|
|
469
|
+
"CLMIntrospectionResult",
|
|
470
|
+
"CLMIntrospector",
|
|
471
|
+
]
|
|
472
|
+
|
|
473
|
+
# Register classifier hooks with cross_cutting (Dependency Inversion)
|
|
474
|
+
try:
|
|
475
|
+
from clm.cross_cutting.pcard import register_classifier_hook
|
|
476
|
+
|
|
477
|
+
register_classifier_hook(classify_clm, detect_dialect)
|
|
478
|
+
except ImportError:
|
|
479
|
+
pass
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|