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,190 @@
|
|
|
1
|
+
"""Layer 2 Storage Substrate (Sprint 387).
|
|
2
|
+
|
|
3
|
+
Canonical home for the Tri-Database persistence engine, connection pools,
|
|
4
|
+
CRUD repositories, bitemporal timelines, and hyperlink graph queries.
|
|
5
|
+
Relocated from Layer 0 to preserve Kenotic Microkernel Purity (INV-01, INV-09, INV-10).
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from clm.cross_cutting.clm_logger import get_logger
|
|
12
|
+
from clm.layer0.relations import _scan_cards_containing
|
|
13
|
+
from clm.layer0.relations import query_card_relations as _qcr
|
|
14
|
+
from clm.layer0.utils import clm_hash_string
|
|
15
|
+
|
|
16
|
+
from .db_bitemporal import BitemporalMixin
|
|
17
|
+
from .db_driver import _ENGINES, CLMDB, reset_shared_socket
|
|
18
|
+
from .db_hyperlinks import HyperlinkMixin
|
|
19
|
+
from .db_pool import ClmDbPool
|
|
20
|
+
from .db_repository import RepositoryBase
|
|
21
|
+
from .savepoint import DisposableList, savepoint
|
|
22
|
+
from .sqlite_wal import SqliteWalPool, TriDatabasePoolRegistry, get_tridb_pools
|
|
23
|
+
|
|
24
|
+
_logger = get_logger("clm.layer2.storage")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CLMRepository(BitemporalMixin, HyperlinkMixin, RepositoryBase):
|
|
28
|
+
"""DAO/Repository for the MCard three-table schema across the Sovereign Triad.
|
|
29
|
+
|
|
30
|
+
Sprint 387: Relocated to Layer 2 Storage. Assembled from RepositoryBase (CRUD),
|
|
31
|
+
BitemporalMixin (time-travel queries), and HyperlinkMixin (graph traversal).
|
|
32
|
+
Administrative CLI flows (init_tridb, memorize_mcard) are delegated to
|
|
33
|
+
clm.layer4.cli.tridb_admin.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def init_tridb(self) -> int:
|
|
37
|
+
"""CLI flow: initialize all 3 pillar databases (delegated to Layer 4 CLI)."""
|
|
38
|
+
target = "clm.layer4.cli.tridb_admin"
|
|
39
|
+
mod = sys.modules.get(target)
|
|
40
|
+
if mod is None:
|
|
41
|
+
__import__(target)
|
|
42
|
+
mod = sys.modules.get(target)
|
|
43
|
+
return mod.init_tridb_method(self)
|
|
44
|
+
|
|
45
|
+
def memorize_mcard(self, mcard_file: str, pillar: str = "auto") -> int:
|
|
46
|
+
"""CLI flow: memorize an MCard file (delegated to Layer 4 CLI)."""
|
|
47
|
+
target = "clm.layer4.cli.tridb_admin"
|
|
48
|
+
mod = sys.modules.get(target)
|
|
49
|
+
if mod is None:
|
|
50
|
+
__import__(target)
|
|
51
|
+
mod = sys.modules.get(target)
|
|
52
|
+
return mod.memorize_mcard_method(self, mcard_file, pillar)
|
|
53
|
+
|
|
54
|
+
def query_card_relations(self, card_hash: str, pillar: str = "execution_log") -> dict[str, list]:
|
|
55
|
+
"""Resolve related cards via SQLite relational calculus."""
|
|
56
|
+
return _qcr(self, card_hash, pillar)
|
|
57
|
+
|
|
58
|
+
def _scan_cards_containing(self, pillar: str, needle: str) -> list:
|
|
59
|
+
"""Scan all cards in a pillar and return parsed Cards whose content mentions needle."""
|
|
60
|
+
return _scan_cards_containing(self, pillar, needle)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def parse_yaml_mcard(mcard_content_str: str):
|
|
64
|
+
"""Extract content_hash, g_time, source filename from MCard YAML."""
|
|
65
|
+
from clm.cross_cutting.mcard import parse_yaml_mcard as _impl
|
|
66
|
+
return _impl(mcard_content_str)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Module-level convenience functions
|
|
70
|
+
|
|
71
|
+
def init_tridb(clm_root: str) -> int:
|
|
72
|
+
db = CLMDB(clm_root)
|
|
73
|
+
return CLMRepository(db, clm_root).init_tridb()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def memorize_mcard(clm_root: str, mcard_file: str, pillar: str = "auto") -> int:
|
|
77
|
+
db = CLMDB(clm_root)
|
|
78
|
+
return CLMRepository(db, clm_root).memorize_mcard(mcard_file, pillar)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def resolve_handle_hash(clm_root: str, handle_name: str, pillar: str = "content_memory") -> str:
|
|
82
|
+
db = CLMDB(clm_root)
|
|
83
|
+
return CLMRepository(db, clm_root).resolve_handle_hash(handle_name, pillar)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def resolve_handle_content(clm_root: str, handle_name: str, pillar: str = "content_memory") -> str:
|
|
87
|
+
db = CLMDB(clm_root)
|
|
88
|
+
return CLMRepository(db, clm_root).resolve_handle_content(handle_name, pillar)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def register_handle(clm_root: str, handle_name: str, content_source: str) -> int:
|
|
92
|
+
db = CLMDB(clm_root)
|
|
93
|
+
return CLMRepository(db, clm_root).register_handle(handle_name, content_source)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def resolve_handle_hash_at(clm_root: str, handle_name: str, query_time: str, pillar: str = "content_memory") -> str | None:
|
|
97
|
+
db = CLMDB(clm_root)
|
|
98
|
+
return CLMRepository(db, clm_root).resolve_handle_hash_at(handle_name, query_time, pillar)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def resolve_handle_content_at(clm_root: str, handle_name: str, query_time: str, pillar: str = "content_memory") -> str | None:
|
|
102
|
+
db = CLMDB(clm_root)
|
|
103
|
+
return CLMRepository(db, clm_root).resolve_handle_content_at(handle_name, query_time, pillar)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class CardCollection:
|
|
107
|
+
"""Compatibility facade over CLMRepository providing MCard-TDD CardCollection surface.
|
|
108
|
+
|
|
109
|
+
Adheres to INV-REF-03 (G-Set Immortality): hard deletes are mathematically forbidden.
|
|
110
|
+
Calling delete(handle) emits a DeprecationWarning and delegates to tombstone_handle.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, repo: CLMRepository | None = None, clm_root: str = "."):
|
|
114
|
+
if repo is None:
|
|
115
|
+
db = CLMDB(clm_root)
|
|
116
|
+
self.repo = CLMRepository(db, clm_root)
|
|
117
|
+
else:
|
|
118
|
+
self.repo = repo
|
|
119
|
+
|
|
120
|
+
def add(self, card_or_content, pillar: str = "content_memory") -> str:
|
|
121
|
+
if hasattr(card_or_content, "hash") and hasattr(card_or_content, "content"):
|
|
122
|
+
h = str(card_or_content.hash)
|
|
123
|
+
c = card_or_content.content
|
|
124
|
+
if isinstance(c, bytes):
|
|
125
|
+
c = c.decode("utf-8", errors="replace")
|
|
126
|
+
self.repo.save_card(h, c, pillar)
|
|
127
|
+
return h
|
|
128
|
+
|
|
129
|
+
content_str = str(card_or_content)
|
|
130
|
+
h = clm_hash_string(content_str)
|
|
131
|
+
self.repo.save_card(h, content_str, pillar)
|
|
132
|
+
return h
|
|
133
|
+
|
|
134
|
+
def get(self, hash_val: str, pillar: str = "content_memory") -> str | None:
|
|
135
|
+
return self.repo.get_card_content(hash_val, pillar)
|
|
136
|
+
|
|
137
|
+
def delete(self, handle: str, pillar: str = "content_memory") -> bool:
|
|
138
|
+
import warnings
|
|
139
|
+
|
|
140
|
+
warnings.warn(
|
|
141
|
+
"Hard deletion violates G-Set CRDT invariants. Tombstoning handle in handle_history.",
|
|
142
|
+
DeprecationWarning,
|
|
143
|
+
stacklevel=2,
|
|
144
|
+
)
|
|
145
|
+
return self.tombstone_handle(handle, pillar)
|
|
146
|
+
|
|
147
|
+
def tombstone_handle(self, handle: str, pillar: str = "content_memory") -> bool:
|
|
148
|
+
return self.repo.tombstone_handle(handle, pillar)
|
|
149
|
+
|
|
150
|
+
def clear(self, pillar: str = "content_memory") -> None:
|
|
151
|
+
import warnings
|
|
152
|
+
|
|
153
|
+
warnings.warn(
|
|
154
|
+
"clear() violates G-Set CRDT invariants. Operation deprecated on CLMRepository.",
|
|
155
|
+
DeprecationWarning,
|
|
156
|
+
stacklevel=2,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def __getattr__(name):
|
|
161
|
+
if name == "_SHARED_SOCKET":
|
|
162
|
+
from . import db_driver
|
|
163
|
+
return db_driver._SHARED_SOCKET
|
|
164
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
__all__ = [
|
|
168
|
+
"CLMDB",
|
|
169
|
+
"CLMRepository",
|
|
170
|
+
"CardCollection",
|
|
171
|
+
"ClmDbPool",
|
|
172
|
+
"RepositoryBase",
|
|
173
|
+
"BitemporalMixin",
|
|
174
|
+
"HyperlinkMixin",
|
|
175
|
+
"savepoint",
|
|
176
|
+
"DisposableList",
|
|
177
|
+
"reset_shared_socket",
|
|
178
|
+
"init_tridb",
|
|
179
|
+
"memorize_mcard",
|
|
180
|
+
"resolve_handle_hash",
|
|
181
|
+
"resolve_handle_content",
|
|
182
|
+
"register_handle",
|
|
183
|
+
"resolve_handle_hash_at",
|
|
184
|
+
"resolve_handle_content_at",
|
|
185
|
+
"parse_yaml_mcard",
|
|
186
|
+
"_ENGINES",
|
|
187
|
+
"SqliteWalPool",
|
|
188
|
+
"TriDatabasePoolRegistry",
|
|
189
|
+
"get_tridb_pools",
|
|
190
|
+
]
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Bitemporal handle queries (Sprint 133.1 / Sprint 387).
|
|
2
|
+
|
|
3
|
+
Extracted from ``db.py``. Time-travel queries over the handle_history table:
|
|
4
|
+
resolve handle content/hash at a specific point in time, list all versions,
|
|
5
|
+
and diff two versions.
|
|
6
|
+
|
|
7
|
+
Canonical Path: clm.layer2.storage.db_bitemporal
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import difflib
|
|
13
|
+
|
|
14
|
+
from clm.layer0.exceptions import CLMError, DatabaseError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BitemporalMixin:
|
|
18
|
+
"""Bitemporal query methods for handle version history.
|
|
19
|
+
|
|
20
|
+
Mixed into ``CLMRepository`` to provide time-travel queries over the
|
|
21
|
+
``handle_history`` table (the η-conversion witness).
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def get_handle_versions(self, handle_name: str, pillar: str = "content_memory") -> list[dict]:
|
|
25
|
+
"""Returns the chronological list of all active versions of this handle."""
|
|
26
|
+
sql_registry = "SELECT current_hash, created_at, updated_at FROM handle_registry WHERE handle = ?;"
|
|
27
|
+
try:
|
|
28
|
+
res_reg = self.db.execute(pillar, sql_registry, [handle_name])
|
|
29
|
+
if not res_reg or not res_reg.strip():
|
|
30
|
+
return []
|
|
31
|
+
parts = res_reg.strip().split("|")
|
|
32
|
+
if len(parts) < 3:
|
|
33
|
+
return []
|
|
34
|
+
current_hash = parts[0]
|
|
35
|
+
created_at = parts[1]
|
|
36
|
+
updated_at = parts[2]
|
|
37
|
+
except Exception as e:
|
|
38
|
+
raise DatabaseError(f"Failed to query registry for {handle_name}: {e}") from e
|
|
39
|
+
|
|
40
|
+
sql_history = "SELECT previous_hash, changed_at FROM handle_history WHERE handle = ? ORDER BY changed_at ASC;"
|
|
41
|
+
try:
|
|
42
|
+
res_hist = self.db.execute(pillar, sql_history, [handle_name])
|
|
43
|
+
hist_lines = [line.strip() for line in res_hist.strip().split("\n") if line.strip()] if res_hist else []
|
|
44
|
+
except Exception as e:
|
|
45
|
+
raise DatabaseError(f"Failed to query history for {handle_name}: {e}") from e
|
|
46
|
+
|
|
47
|
+
versions = []
|
|
48
|
+
|
|
49
|
+
if not hist_lines:
|
|
50
|
+
versions.append({
|
|
51
|
+
"version_index": 0,
|
|
52
|
+
"hash": current_hash,
|
|
53
|
+
"active_from": created_at,
|
|
54
|
+
"active_to": None
|
|
55
|
+
})
|
|
56
|
+
return versions
|
|
57
|
+
|
|
58
|
+
last_time = created_at
|
|
59
|
+
idx = 0
|
|
60
|
+
for line in hist_lines:
|
|
61
|
+
h_parts = line.split("|")
|
|
62
|
+
if len(h_parts) >= 2:
|
|
63
|
+
prev_hash = h_parts[0]
|
|
64
|
+
changed_at = h_parts[1]
|
|
65
|
+
versions.append({
|
|
66
|
+
"version_index": idx,
|
|
67
|
+
"hash": prev_hash,
|
|
68
|
+
"active_from": last_time,
|
|
69
|
+
"active_to": changed_at
|
|
70
|
+
})
|
|
71
|
+
last_time = changed_at
|
|
72
|
+
idx += 1
|
|
73
|
+
|
|
74
|
+
versions.append({
|
|
75
|
+
"version_index": idx,
|
|
76
|
+
"hash": current_hash,
|
|
77
|
+
"active_from": updated_at,
|
|
78
|
+
"active_to": None
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return versions
|
|
82
|
+
|
|
83
|
+
def resolve_handle_hash_at(self, handle_name: str, query_time: str, pillar: str = "content_memory") -> str | None:
|
|
84
|
+
"""Resolves the hash of a handle as it existed at a specific ISO-8601 query_time."""
|
|
85
|
+
versions = self.get_handle_versions(handle_name, pillar)
|
|
86
|
+
if not versions:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
q_time = query_time.strip()
|
|
90
|
+
for ver in versions:
|
|
91
|
+
active_from = ver["active_from"].strip()
|
|
92
|
+
active_to = ver["active_to"]
|
|
93
|
+
if q_time >= active_from and (active_to is None or q_time < active_to.strip()):
|
|
94
|
+
return ver["hash"]
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
def resolve_handle_content_at(self, handle_name: str, query_time: str, pillar: str = "content_memory") -> str | None:
|
|
98
|
+
"""Resolves the content of a handle as it existed at a specific ISO-8601 query_time."""
|
|
99
|
+
hash_val = self.resolve_handle_hash_at(handle_name, query_time, pillar)
|
|
100
|
+
if not hash_val:
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
sql = "SELECT content FROM card WHERE hash = ?;"
|
|
104
|
+
try:
|
|
105
|
+
res = self.db.execute(pillar, sql, [hash_val])
|
|
106
|
+
return res.strip() if res else None
|
|
107
|
+
except Exception as e:
|
|
108
|
+
raise DatabaseError(f"Failed to resolve content for hash {hash_val} in {pillar}: {e}") from e
|
|
109
|
+
|
|
110
|
+
def diff_handle_versions(self, handle_name: str, version_idx1: int, version_idx2: int, pillar: str = "content_memory") -> str:
|
|
111
|
+
"""Compares two versions of a handle's content by chronological indices and returns a diff string."""
|
|
112
|
+
versions = self.get_handle_versions(handle_name, pillar)
|
|
113
|
+
if not versions:
|
|
114
|
+
raise CLMError(f"No versions found for handle '{handle_name}'")
|
|
115
|
+
|
|
116
|
+
v1_hash, v2_hash = None, None
|
|
117
|
+
for ver in versions:
|
|
118
|
+
if ver["version_index"] == version_idx1:
|
|
119
|
+
v1_hash = ver["hash"]
|
|
120
|
+
if ver["version_index"] == version_idx2:
|
|
121
|
+
v2_hash = ver["hash"]
|
|
122
|
+
|
|
123
|
+
if not v1_hash:
|
|
124
|
+
raise CLMError(f"Version index {version_idx1} not found for handle '{handle_name}'")
|
|
125
|
+
if not v2_hash:
|
|
126
|
+
raise CLMError(f"Version index {version_idx2} not found for handle '{handle_name}'")
|
|
127
|
+
|
|
128
|
+
sql = "SELECT content FROM card WHERE hash = ?;"
|
|
129
|
+
c1 = self.db.execute(pillar, sql, [v1_hash])
|
|
130
|
+
c2 = self.db.execute(pillar, sql, [v2_hash])
|
|
131
|
+
|
|
132
|
+
content1 = c1.strip() if c1 else ""
|
|
133
|
+
content2 = c2.strip() if c2 else ""
|
|
134
|
+
|
|
135
|
+
diff = difflib.unified_diff(
|
|
136
|
+
content1.splitlines(keepends=True),
|
|
137
|
+
content2.splitlines(keepends=True),
|
|
138
|
+
fromfile=f"{handle_name} (v{version_idx1}: {v1_hash[:8]})",
|
|
139
|
+
tofile=f"{handle_name} (v{version_idx2}: {v2_hash[:8]})"
|
|
140
|
+
)
|
|
141
|
+
return "".join(diff)
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""CLMDB — Low-level database driver for the Sovereign Triad (Sprint 133.1 / Sprint 387).
|
|
2
|
+
|
|
3
|
+
Provides unified SQL execution across the three pillar databases
|
|
4
|
+
(agent_identities, content_memory, execution_log). Supports file-based
|
|
5
|
+
SQLite (via SQLAlchemy / ClmDbPool) or in-memory broker mode
|
|
6
|
+
(CLM_ZERO_FS=true) communicating via TCP socket to clm_memdb_broker.py.
|
|
7
|
+
|
|
8
|
+
Canonical Path: clm.layer2.storage.db_driver
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import contextlib
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import socket
|
|
17
|
+
import sqlite3
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
import time
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from sqlalchemy import create_engine, text
|
|
24
|
+
from sqlalchemy.pool import NullPool
|
|
25
|
+
|
|
26
|
+
from clm.cross_cutting.clm_logger import get_logger
|
|
27
|
+
from clm.cross_cutting.resources import get_schema_content
|
|
28
|
+
|
|
29
|
+
_logger = get_logger("clm.db")
|
|
30
|
+
|
|
31
|
+
_SHARED_SOCKET = None
|
|
32
|
+
_ENGINES: dict[str, Any] = {}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def reset_shared_socket() -> None:
|
|
36
|
+
"""Reset the cached shared broker socket (used by tests between runs)."""
|
|
37
|
+
global _SHARED_SOCKET
|
|
38
|
+
_SHARED_SOCKET = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class CLMDB:
|
|
42
|
+
"""Low-level database driver for the Sovereign Triad.
|
|
43
|
+
|
|
44
|
+
Provides unified SQL execution across the three pillar databases
|
|
45
|
+
(agent_identities, content_memory, execution_log). Supports two
|
|
46
|
+
modes: file-based SQLite (via SQLAlchemy) for persistent operation,
|
|
47
|
+
or in-memory broker mode (CLM_ZERO_FS=true) communicating via TCP
|
|
48
|
+
socket to clm_memdb_broker.py.
|
|
49
|
+
|
|
50
|
+
Each pillar database contains the same three-table MCard Schema,
|
|
51
|
+
implementing the Three Angles of data identity:
|
|
52
|
+
card -> Content addressing (beta / Exponent / CLM C-axis)
|
|
53
|
+
handle_registry -> Semantic naming (alpha / Sum / CLM A-axis)
|
|
54
|
+
handle_history -> Temporal lineage (eta / Product / CLM B-axis)
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, clm_root: str | None = None) -> None:
|
|
58
|
+
self.clm_root = clm_root or os.environ.get("CLM_ROOT") or os.getcwd()
|
|
59
|
+
self.zero_fs = os.environ.get("CLM_ZERO_FS", "").lower() == "true"
|
|
60
|
+
self.port = int(os.environ.get("CLM_MEMBROKER_PORT", "43740"))
|
|
61
|
+
self.db_dir = os.path.join(self.clm_root, "db")
|
|
62
|
+
|
|
63
|
+
def _interpolate_sql(self, sql: str, params: Any) -> str:
|
|
64
|
+
if not params:
|
|
65
|
+
return sql
|
|
66
|
+
idx = 0
|
|
67
|
+
result = []
|
|
68
|
+
in_str = False
|
|
69
|
+
str_char = None
|
|
70
|
+
i = 0
|
|
71
|
+
n = len(sql)
|
|
72
|
+
while i < n:
|
|
73
|
+
c = sql[i]
|
|
74
|
+
if c in ("'", '"'):
|
|
75
|
+
if not in_str:
|
|
76
|
+
in_str = True
|
|
77
|
+
str_char = c
|
|
78
|
+
elif str_char == c:
|
|
79
|
+
if i + 1 < n and sql[i + 1] == c:
|
|
80
|
+
result.append(c * 2)
|
|
81
|
+
i += 2
|
|
82
|
+
continue
|
|
83
|
+
else:
|
|
84
|
+
in_str = False
|
|
85
|
+
result.append(c)
|
|
86
|
+
elif c == "?" and not in_str:
|
|
87
|
+
if idx >= len(params):
|
|
88
|
+
raise ValueError("Too few parameters for SQL query")
|
|
89
|
+
val = params[idx]
|
|
90
|
+
idx += 1
|
|
91
|
+
if val is None:
|
|
92
|
+
result.append("NULL")
|
|
93
|
+
elif isinstance(val, int | float):
|
|
94
|
+
result.append(str(val))
|
|
95
|
+
elif isinstance(val, bytes):
|
|
96
|
+
result.append(f"X'{val.hex()}'")
|
|
97
|
+
else:
|
|
98
|
+
escaped = str(val).replace("'", "''")
|
|
99
|
+
result.append(f"'{escaped}'")
|
|
100
|
+
else:
|
|
101
|
+
result.append(c)
|
|
102
|
+
i += 1
|
|
103
|
+
if idx < len(params):
|
|
104
|
+
raise ValueError("Too many parameters for SQL query")
|
|
105
|
+
return "".join(result)
|
|
106
|
+
|
|
107
|
+
def _send_to_broker(self, pillar: str, sql: str) -> str:
|
|
108
|
+
global _SHARED_SOCKET
|
|
109
|
+
sql_bytes = sql.encode("utf-8")
|
|
110
|
+
req_header = f"CLMQ\t{pillar}\t{len(sql_bytes)}\t\n".encode()
|
|
111
|
+
|
|
112
|
+
for attempt in range(2):
|
|
113
|
+
s = _SHARED_SOCKET
|
|
114
|
+
if s is None:
|
|
115
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
116
|
+
s.settimeout(10.0)
|
|
117
|
+
try:
|
|
118
|
+
s.connect(("127.0.0.1", self.port))
|
|
119
|
+
_SHARED_SOCKET = s
|
|
120
|
+
except ConnectionRefusedError:
|
|
121
|
+
is_default_port = self.port == 43740
|
|
122
|
+
is_externally_managed = "CLM_MEMBROKER_NO_DISK" in os.environ
|
|
123
|
+
if self.zero_fs and is_default_port and not is_externally_managed:
|
|
124
|
+
broker_script = os.path.join(self.clm_root, "scripts", "bootstrap", "layer0_core", "clm_memdb_broker.py")
|
|
125
|
+
if not os.path.exists(broker_script):
|
|
126
|
+
broker_script = os.path.join(self.clm_root, "scripts", "layer0_microkernel", "db_core", "clm_memdb_broker.py")
|
|
127
|
+
if os.path.exists(broker_script):
|
|
128
|
+
subprocess.Popen([sys.executable, broker_script], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
129
|
+
|
|
130
|
+
retries = 0
|
|
131
|
+
connected = False
|
|
132
|
+
while retries < 30:
|
|
133
|
+
try:
|
|
134
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
135
|
+
s.settimeout(10.0)
|
|
136
|
+
s.connect(("127.0.0.1", self.port))
|
|
137
|
+
connected = True
|
|
138
|
+
_SHARED_SOCKET = s
|
|
139
|
+
break
|
|
140
|
+
except Exception:
|
|
141
|
+
time.sleep(0.1)
|
|
142
|
+
retries += 1
|
|
143
|
+
if not connected:
|
|
144
|
+
raise Exception("Failed to lazily start clm_memdb_broker.py") from None
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
schema_sql = get_schema_content("mcard_schema.sql")
|
|
148
|
+
except Exception:
|
|
149
|
+
schema_sql = ""
|
|
150
|
+
if schema_sql:
|
|
151
|
+
for p in ("agent_identities", "content_memory", "execution_log"):
|
|
152
|
+
s_schema = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
153
|
+
s_schema.settimeout(10.0)
|
|
154
|
+
s_schema.connect(("127.0.0.1", self.port))
|
|
155
|
+
s_bytes = schema_sql.encode("utf-8")
|
|
156
|
+
r_header = f"CLMQ\t{p}\t{len(s_bytes)}\t\n".encode()
|
|
157
|
+
s_schema.sendall(r_header + s_bytes)
|
|
158
|
+
resp_header = b""
|
|
159
|
+
while b"\n" not in resp_header:
|
|
160
|
+
chunk = s_schema.recv(1)
|
|
161
|
+
if not chunk:
|
|
162
|
+
break
|
|
163
|
+
resp_header += chunk
|
|
164
|
+
if resp_header:
|
|
165
|
+
parts = resp_header.decode("utf-8").strip().split("\t")
|
|
166
|
+
if len(parts) >= 3:
|
|
167
|
+
rlen = int(parts[2])
|
|
168
|
+
body = b""
|
|
169
|
+
while len(body) < rlen:
|
|
170
|
+
chunk = s_schema.recv(min(4096, rlen - len(body)))
|
|
171
|
+
if not chunk:
|
|
172
|
+
break
|
|
173
|
+
body += chunk
|
|
174
|
+
s_schema.close()
|
|
175
|
+
else:
|
|
176
|
+
raise Exception(f"Broker script not found: {broker_script}") from None
|
|
177
|
+
else:
|
|
178
|
+
raise
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
s.sendall(req_header + sql_bytes)
|
|
182
|
+
|
|
183
|
+
header_data = b""
|
|
184
|
+
while b"\n" not in header_data:
|
|
185
|
+
chunk = s.recv(1)
|
|
186
|
+
if not chunk:
|
|
187
|
+
break
|
|
188
|
+
header_data += chunk
|
|
189
|
+
|
|
190
|
+
if not header_data:
|
|
191
|
+
raise Exception("Connection closed without response")
|
|
192
|
+
|
|
193
|
+
header_line = header_data.decode("utf-8").strip()
|
|
194
|
+
parts = header_line.split("\t")
|
|
195
|
+
if len(parts) < 3 or parts[0] != "CLMR":
|
|
196
|
+
raise Exception(f"Invalid response header: {header_line}")
|
|
197
|
+
|
|
198
|
+
status, rlen_str = parts[1], parts[2]
|
|
199
|
+
rlen = int(rlen_str)
|
|
200
|
+
|
|
201
|
+
body_bytes = b""
|
|
202
|
+
while len(body_bytes) < rlen:
|
|
203
|
+
chunk = s.recv(min(4096, rlen - len(body_bytes)))
|
|
204
|
+
if not chunk:
|
|
205
|
+
break
|
|
206
|
+
body_bytes += chunk
|
|
207
|
+
|
|
208
|
+
if status == "ERR":
|
|
209
|
+
raise Exception(body_bytes.decode("utf-8"))
|
|
210
|
+
|
|
211
|
+
return body_bytes.decode("utf-8")
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
with contextlib.suppress(Exception):
|
|
215
|
+
s.close()
|
|
216
|
+
_SHARED_SOCKET = None
|
|
217
|
+
|
|
218
|
+
if attempt == 1:
|
|
219
|
+
raise e
|
|
220
|
+
return ""
|
|
221
|
+
|
|
222
|
+
def execute(self, pillar: str, sql: str, params: Any = None, as_json: bool = False) -> str:
|
|
223
|
+
if pillar in ("execution_logs", "exec"):
|
|
224
|
+
pillar = "execution_log"
|
|
225
|
+
elif pillar in ("agent_identity", "id"):
|
|
226
|
+
pillar = "agent_identities"
|
|
227
|
+
elif pillar in ("content_memory", "mem"):
|
|
228
|
+
pillar = "content_memory"
|
|
229
|
+
|
|
230
|
+
if self.zero_fs:
|
|
231
|
+
interpolated_sql = self._interpolate_sql(sql, params)
|
|
232
|
+
resp = self._send_to_broker(pillar, interpolated_sql)
|
|
233
|
+
if as_json:
|
|
234
|
+
snap_dir = os.environ.get("CLM_MEMBROKER_SNAP_DIR", "/tmp")
|
|
235
|
+
db_path = os.path.join(snap_dir, f"{pillar}.db")
|
|
236
|
+
columns = []
|
|
237
|
+
if os.path.exists(db_path):
|
|
238
|
+
try:
|
|
239
|
+
conn = sqlite3.connect(db_path)
|
|
240
|
+
cursor = conn.cursor()
|
|
241
|
+
if "select" in sql.lower():
|
|
242
|
+
cursor.execute(sql, params or [])
|
|
243
|
+
columns = [col[0] for col in cursor.description] if cursor.description else []
|
|
244
|
+
cursor.close()
|
|
245
|
+
conn.close()
|
|
246
|
+
except Exception:
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
rows = []
|
|
250
|
+
if resp.strip():
|
|
251
|
+
for line in resp.strip().split("\n"):
|
|
252
|
+
if not line:
|
|
253
|
+
continue
|
|
254
|
+
vals = line.split("|")
|
|
255
|
+
if columns and len(vals) == len(columns):
|
|
256
|
+
rows.append(dict(zip(columns, vals, strict=False)))
|
|
257
|
+
else:
|
|
258
|
+
rows.append({f"col_{i}": val for i, val in enumerate(vals)})
|
|
259
|
+
return json.dumps(rows)
|
|
260
|
+
else:
|
|
261
|
+
return resp
|
|
262
|
+
else:
|
|
263
|
+
db_path = os.path.join(self.db_dir, f"{pillar}.db")
|
|
264
|
+
engine = _ENGINES.get(db_path)
|
|
265
|
+
if not engine:
|
|
266
|
+
engine = create_engine(f"sqlite:///{db_path}", poolclass=NullPool)
|
|
267
|
+
_ENGINES[db_path] = engine
|
|
268
|
+
|
|
269
|
+
# Ensure schema exists
|
|
270
|
+
with engine.begin() as conn:
|
|
271
|
+
res = conn.execute(text("SELECT 1 FROM sqlite_master WHERE type='table' AND name='card';")).fetchone()
|
|
272
|
+
if not res:
|
|
273
|
+
try:
|
|
274
|
+
schema_content = get_schema_content("mcard_schema.sql")
|
|
275
|
+
except Exception:
|
|
276
|
+
schema_content = ""
|
|
277
|
+
if schema_content:
|
|
278
|
+
dbapi_conn = conn.connection
|
|
279
|
+
if hasattr(dbapi_conn, "executescript"):
|
|
280
|
+
dbapi_conn.executescript(schema_content)
|
|
281
|
+
elif hasattr(dbapi_conn, "dbapi_connection") and hasattr(dbapi_conn.dbapi_connection, "executescript"):
|
|
282
|
+
dbapi_conn.dbapi_connection.executescript(schema_content)
|
|
283
|
+
else:
|
|
284
|
+
temp_conn = sqlite3.connect(db_path)
|
|
285
|
+
temp_conn.executescript(schema_content)
|
|
286
|
+
temp_conn.commit()
|
|
287
|
+
temp_conn.close()
|
|
288
|
+
|
|
289
|
+
with engine.begin() as conn:
|
|
290
|
+
if params:
|
|
291
|
+
if isinstance(params, dict):
|
|
292
|
+
result = conn.exec_driver_sql(sql, params)
|
|
293
|
+
else:
|
|
294
|
+
result = conn.exec_driver_sql(sql, tuple(params))
|
|
295
|
+
else:
|
|
296
|
+
if "BEGIN;" in sql or ";" in sql[:-1]:
|
|
297
|
+
dbapi_conn = conn.connection
|
|
298
|
+
if hasattr(dbapi_conn, "executescript"):
|
|
299
|
+
dbapi_conn.executescript(sql)
|
|
300
|
+
elif hasattr(dbapi_conn, "dbapi_connection") and hasattr(dbapi_conn.dbapi_connection, "executescript"):
|
|
301
|
+
dbapi_conn.dbapi_connection.executescript(sql)
|
|
302
|
+
else:
|
|
303
|
+
temp_conn = sqlite3.connect(db_path)
|
|
304
|
+
temp_conn.executescript(sql)
|
|
305
|
+
temp_conn.commit()
|
|
306
|
+
temp_conn.close()
|
|
307
|
+
result = None
|
|
308
|
+
else:
|
|
309
|
+
result = conn.execute(text(sql))
|
|
310
|
+
|
|
311
|
+
rows = []
|
|
312
|
+
columns = []
|
|
313
|
+
if result is not None:
|
|
314
|
+
if result.returns_rows:
|
|
315
|
+
rows = result.fetchall()
|
|
316
|
+
columns = list(result.keys())
|
|
317
|
+
else:
|
|
318
|
+
pass
|
|
319
|
+
|
|
320
|
+
if as_json:
|
|
321
|
+
res_list = []
|
|
322
|
+
for row in rows:
|
|
323
|
+
res_list.append(dict(zip(columns, row, strict=False)))
|
|
324
|
+
result_str = json.dumps(res_list)
|
|
325
|
+
else:
|
|
326
|
+
output = []
|
|
327
|
+
for row in rows:
|
|
328
|
+
output.append("|".join("" if val is None else str(val) for val in row))
|
|
329
|
+
result_str = "\n".join(output) + "\n" if output else ""
|
|
330
|
+
|
|
331
|
+
return result_str
|