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,74 @@
|
|
|
1
|
+
"""Layer 3 Reticulum Satori Bridge (DEL-390-01).
|
|
2
|
+
|
|
3
|
+
Bridges conversational Satori AST messaging with authenticated Reticulum link sessions.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import hashlib
|
|
8
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
from clm.layer3.reticulum.transport import ReticulumLinkSession
|
|
11
|
+
from clm.layer3.satori.speech_act import decode_satori_binary, encode_satori_binary
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SatoriReticulumBridge:
|
|
15
|
+
"""Bridges Satori conversational messaging with an authenticated Reticulum link."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, session: ReticulumLinkSession) -> None:
|
|
18
|
+
self.session = session
|
|
19
|
+
|
|
20
|
+
def send_message(self, node: Dict[str, Any]) -> Tuple[bytes, str]:
|
|
21
|
+
"""Encodes AST, encrypts frame over session, and computes receipt token."""
|
|
22
|
+
binary_ast = encode_satori_binary(node)
|
|
23
|
+
frame = self.session.encrypt(binary_ast)
|
|
24
|
+
|
|
25
|
+
msg_hash = hashlib.sha256(binary_ast).digest()
|
|
26
|
+
receipt_token = hashlib.sha256(msg_hash + self.session.link_id).hexdigest()
|
|
27
|
+
|
|
28
|
+
return frame, receipt_token
|
|
29
|
+
|
|
30
|
+
def receive_message(
|
|
31
|
+
self,
|
|
32
|
+
frame: bytes,
|
|
33
|
+
cas_store: Optional[Dict[str, Any]] = None,
|
|
34
|
+
) -> Tuple[Dict[str, Any], str, List[str]]:
|
|
35
|
+
"""Decrypts frame, parses AST, ingests multi-media cards into CAS, and computes receipt token."""
|
|
36
|
+
decrypted = self.session.decrypt(frame)
|
|
37
|
+
node = decode_satori_binary(decrypted)
|
|
38
|
+
|
|
39
|
+
ingested_cards: List[str] = []
|
|
40
|
+
if cas_store is not None:
|
|
41
|
+
self._ingest_media_cards(node, cas_store, ingested_cards)
|
|
42
|
+
|
|
43
|
+
msg_hash = hashlib.sha256(decrypted).digest()
|
|
44
|
+
receipt_token = hashlib.sha256(msg_hash + self.session.link_id).hexdigest()
|
|
45
|
+
|
|
46
|
+
return node, receipt_token, ingested_cards
|
|
47
|
+
|
|
48
|
+
def _ingest_media_cards(
|
|
49
|
+
self,
|
|
50
|
+
node: Dict[str, Any],
|
|
51
|
+
cas_store: Dict[str, Any],
|
|
52
|
+
ingested: List[str],
|
|
53
|
+
) -> None:
|
|
54
|
+
elements = node.get("elements", [node])
|
|
55
|
+
for el in elements:
|
|
56
|
+
el_type = el.get("type")
|
|
57
|
+
if el_type in ("image", "audio", "card"):
|
|
58
|
+
hash_ref = el.get("hash") or el.get("id") or el.get("src") or ""
|
|
59
|
+
if "blake3:" in hash_ref:
|
|
60
|
+
clean_hash = hash_ref.split("blake3:")[-1]
|
|
61
|
+
if clean_hash not in cas_store:
|
|
62
|
+
cas_store[clean_hash] = {
|
|
63
|
+
"hash": f"blake3:{clean_hash}",
|
|
64
|
+
"uri": f"rns:media/{clean_hash}",
|
|
65
|
+
"payload": el.get("text", hash_ref).encode("utf-8"),
|
|
66
|
+
}
|
|
67
|
+
ingested.append(clean_hash)
|
|
68
|
+
|
|
69
|
+
if "children" in el:
|
|
70
|
+
for ch in el["children"]:
|
|
71
|
+
self._ingest_media_cards(ch, cas_store, ingested)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
__all__ = ["SatoriReticulumBridge"]
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Headless Reticulum Daemon & Shared Instance Peering Wrapper for CLM Layer 3 (DEL-390-01).
|
|
2
|
+
|
|
3
|
+
Implements:
|
|
4
|
+
- Headless local mesh daemon anchor for edge clusters
|
|
5
|
+
- Multi-client shared instance routing across Python, Rust, and JavaScript nodes
|
|
6
|
+
- Loopback mesh router with partition simulation for chaos canaries (INV-L3-RET-358-03)
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import threading
|
|
11
|
+
from typing import Callable, Dict, Set, Tuple
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ReticulumLocalDaemon:
|
|
15
|
+
"""In-process or IPC Reticulum daemon coordinating message routing,
|
|
16
|
+
announcement broadcast, and chaos partition injection.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, port: int = 4242, app_name: str = "clm.pkc.node") -> None:
|
|
20
|
+
self.port = port
|
|
21
|
+
self.app_name = app_name
|
|
22
|
+
self.running = False
|
|
23
|
+
self._nodes: Dict[str, Callable[[bytes, str], None]] = {}
|
|
24
|
+
self._announces: Dict[str, bytes] = {}
|
|
25
|
+
self._disabled_links: Set[Tuple[str, str]] = set()
|
|
26
|
+
self._lock = threading.Lock()
|
|
27
|
+
|
|
28
|
+
def start(self) -> None:
|
|
29
|
+
self.running = True
|
|
30
|
+
|
|
31
|
+
def stop(self) -> None:
|
|
32
|
+
self.running = False
|
|
33
|
+
|
|
34
|
+
def register_node(self, dest_hex: str, handler: Callable[[bytes, str], None]) -> None:
|
|
35
|
+
with self._lock:
|
|
36
|
+
self._nodes[dest_hex] = handler
|
|
37
|
+
|
|
38
|
+
def unregister_node(self, dest_hex: str) -> None:
|
|
39
|
+
with self._lock:
|
|
40
|
+
self._nodes.pop(dest_hex, None)
|
|
41
|
+
|
|
42
|
+
def broadcast_announce(self, sender_dest_hex: str, announce_packet: bytes) -> None:
|
|
43
|
+
with self._lock:
|
|
44
|
+
self._announces[sender_dest_hex] = announce_packet
|
|
45
|
+
for dest_hex, handler in list(self._nodes.items()):
|
|
46
|
+
if dest_hex != sender_dest_hex and (sender_dest_hex, dest_hex) not in self._disabled_links:
|
|
47
|
+
try:
|
|
48
|
+
handler(announce_packet, sender_dest_hex)
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
def route_frame(self, sender_hex: str, target_hex: str, payload: bytes) -> bool:
|
|
53
|
+
"""Routes a frame from sender to target. If direct link is partitioned,
|
|
54
|
+
searches for an alternate multi-hop path through available peers.
|
|
55
|
+
"""
|
|
56
|
+
with self._lock:
|
|
57
|
+
# Check if direct link is severed
|
|
58
|
+
if (sender_hex, target_hex) not in self._disabled_links and (target_hex, sender_hex) not in self._disabled_links:
|
|
59
|
+
if target_hex in self._nodes:
|
|
60
|
+
self._nodes[target_hex](payload, sender_hex)
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
# Link severed! Try self-healing mesh rerouting through intermediate hops
|
|
64
|
+
for intermediary_hex in self._nodes:
|
|
65
|
+
if intermediary_hex != sender_hex and intermediary_hex != target_hex:
|
|
66
|
+
hop1_ok = (sender_hex, intermediary_hex) not in self._disabled_links and (intermediary_hex, sender_hex) not in self._disabled_links
|
|
67
|
+
hop2_ok = (intermediary_hex, target_hex) not in self._disabled_links and (target_hex, intermediary_hex) not in self._disabled_links
|
|
68
|
+
if hop1_ok and hop2_ok and target_hex in self._nodes:
|
|
69
|
+
# Reroute via intermediate relay
|
|
70
|
+
self._nodes[target_hex](payload, sender_hex)
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def sever_link(self, node_a_hex: str, node_b_hex: str) -> None:
|
|
76
|
+
"""Simulates network partition / link severance between two nodes."""
|
|
77
|
+
with self._lock:
|
|
78
|
+
self._disabled_links.add((node_a_hex, node_b_hex))
|
|
79
|
+
self._disabled_links.add((node_b_hex, node_a_hex))
|
|
80
|
+
|
|
81
|
+
def heal_link(self, node_a_hex: str, node_b_hex: str) -> None:
|
|
82
|
+
"""Restores network connectivity between two nodes."""
|
|
83
|
+
with self._lock:
|
|
84
|
+
self._disabled_links.discard((node_a_hex, node_b_hex))
|
|
85
|
+
self._disabled_links.discard((node_b_hex, node_a_hex))
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
__all__ = ["ReticulumLocalDaemon"]
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Layer 3 Reticulum Discovery Service (DEL-390-01).
|
|
2
|
+
|
|
3
|
+
Implements:
|
|
4
|
+
- PKC node discovery and MCard capability announcement
|
|
5
|
+
- AutoInterface integration on UDP 29716 / 42671
|
|
6
|
+
- Reference Ed25519 signature and destination hash verification
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import time
|
|
14
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
RETICULUM_PATH = os.path.expanduser("~/Documents/Development/Networking/Reticulum")
|
|
17
|
+
if RETICULUM_PATH not in sys.path and os.path.exists(RETICULUM_PATH):
|
|
18
|
+
sys.path.insert(0, RETICULUM_PATH)
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
import RNS
|
|
22
|
+
except (ImportError, ModuleNotFoundError):
|
|
23
|
+
RNS = None # type: ignore
|
|
24
|
+
|
|
25
|
+
RETICULUM_AUTOINTERFACE_PORT = 29716
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ReticulumDiscoveryService:
|
|
29
|
+
"""Manages Reticulum-based PKC node presence, MCard namespace announcements,
|
|
30
|
+
and peer discovery in CLM Layer 3.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
app_name: str = "clm",
|
|
36
|
+
aspects: Optional[List[str]] = None,
|
|
37
|
+
identity: Optional[Any] = None,
|
|
38
|
+
config_dir: Optional[str] = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
self.app_name = app_name
|
|
41
|
+
self.aspects = aspects or ["pkc", "node"]
|
|
42
|
+
self.config_dir = config_dir
|
|
43
|
+
if RNS is not None:
|
|
44
|
+
if RNS.Reticulum.get_instance() is None:
|
|
45
|
+
self.reticulum = RNS.Reticulum(configdir=self.config_dir)
|
|
46
|
+
else:
|
|
47
|
+
self.reticulum = RNS.Reticulum.get_instance()
|
|
48
|
+
self.identity = identity or RNS.Identity()
|
|
49
|
+
self.destination = RNS.Destination(
|
|
50
|
+
self.identity,
|
|
51
|
+
RNS.Destination.IN,
|
|
52
|
+
RNS.Destination.SINGLE,
|
|
53
|
+
self.app_name,
|
|
54
|
+
*self.aspects,
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
self.reticulum = None
|
|
58
|
+
self.identity = identity
|
|
59
|
+
self.destination = None
|
|
60
|
+
self.discovered_peers: Dict[str, Dict[str, Any]] = {}
|
|
61
|
+
self._callbacks: List[Callable[[Dict[str, Any]], None]] = []
|
|
62
|
+
self._is_running = False
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def destination_hash(self) -> bytes:
|
|
66
|
+
if self.destination is not None:
|
|
67
|
+
return self.destination.hash
|
|
68
|
+
return b"\x00" * 16
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def destination_hex(self) -> str:
|
|
72
|
+
if self.destination is not None:
|
|
73
|
+
return self.destination.hexhash
|
|
74
|
+
return "00" * 16
|
|
75
|
+
|
|
76
|
+
def start(self) -> None:
|
|
77
|
+
"""Starts listening for incoming Reticulum announces."""
|
|
78
|
+
if self._is_running:
|
|
79
|
+
return
|
|
80
|
+
if self.destination is not None and RNS is not None:
|
|
81
|
+
self.destination.set_link_established_callback(self._on_link_established)
|
|
82
|
+
RNS.Transport.register_announce_handler(self)
|
|
83
|
+
self._is_running = True
|
|
84
|
+
|
|
85
|
+
def stop(self) -> None:
|
|
86
|
+
"""Deregisters handlers and stops the discovery service."""
|
|
87
|
+
if not self._is_running:
|
|
88
|
+
return
|
|
89
|
+
if RNS is not None:
|
|
90
|
+
RNS.Transport.deregister_announce_handler(self)
|
|
91
|
+
self._is_running = False
|
|
92
|
+
|
|
93
|
+
def announce_presence(
|
|
94
|
+
self,
|
|
95
|
+
agent_did: str,
|
|
96
|
+
capabilities: Optional[List[str]] = None,
|
|
97
|
+
namespaces: Optional[List[str]] = None,
|
|
98
|
+
extra_metadata: Optional[Dict[str, Any]] = None,
|
|
99
|
+
) -> Dict[str, Any]:
|
|
100
|
+
"""Broadcasts a cryptographically signed Reticulum announce containing
|
|
101
|
+
the node's sovereign identity, MCard capabilities, and hosted namespaces.
|
|
102
|
+
"""
|
|
103
|
+
payload = {
|
|
104
|
+
"did": agent_did,
|
|
105
|
+
"capabilities": capabilities or ["mcard:text", "mcard:media", "satori:dialogue"],
|
|
106
|
+
"namespaces": namespaces or ["clm:pkc/local"],
|
|
107
|
+
"timestamp": time.time(),
|
|
108
|
+
}
|
|
109
|
+
if extra_metadata:
|
|
110
|
+
payload.update(extra_metadata)
|
|
111
|
+
|
|
112
|
+
if self.destination is not None:
|
|
113
|
+
app_data = json.dumps(payload).encode("utf-8")
|
|
114
|
+
self.destination.announce(app_data=app_data)
|
|
115
|
+
return payload
|
|
116
|
+
|
|
117
|
+
def on_peer_discovered(self, callback: Callable[[Dict[str, Any]], None]) -> None:
|
|
118
|
+
"""Registers a callback when a valid peer announce is received."""
|
|
119
|
+
self._callbacks.append(callback)
|
|
120
|
+
|
|
121
|
+
def received_announce(self, destination_hash: bytes, announced_identity: Any, app_data: bytes) -> None:
|
|
122
|
+
dest_hex = RNS.hexrep(destination_hash, delimit=False) if RNS else destination_hash.hex()
|
|
123
|
+
|
|
124
|
+
if self.destination is not None and destination_hash == self.destination.hash:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
parsed_data = {}
|
|
128
|
+
if app_data:
|
|
129
|
+
try:
|
|
130
|
+
parsed_data = json.loads(app_data.decode("utf-8"))
|
|
131
|
+
except Exception:
|
|
132
|
+
parsed_data = {"raw": app_data}
|
|
133
|
+
|
|
134
|
+
peer_info = {
|
|
135
|
+
"destination_hash": dest_hex,
|
|
136
|
+
"identity_hash": announced_identity.hexhash if announced_identity and hasattr(announced_identity, "hexhash") else None,
|
|
137
|
+
"agent_did": parsed_data.get("did"),
|
|
138
|
+
"capabilities": parsed_data.get("capabilities", []),
|
|
139
|
+
"namespaces": parsed_data.get("namespaces", []),
|
|
140
|
+
"app_data": parsed_data,
|
|
141
|
+
"discovered_at": time.time(),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
self.discovered_peers[dest_hex] = peer_info
|
|
145
|
+
|
|
146
|
+
for cb in self._callbacks:
|
|
147
|
+
try:
|
|
148
|
+
cb(peer_info)
|
|
149
|
+
except Exception as e:
|
|
150
|
+
if RNS is not None:
|
|
151
|
+
RNS.log(f"Error in peer discovery callback: {e}", RNS.LOG_ERROR)
|
|
152
|
+
|
|
153
|
+
def _on_link_established(self, link: Any) -> None:
|
|
154
|
+
if RNS is not None:
|
|
155
|
+
RNS.log(f"Link established with peer on destination {self.destination_hex}", RNS.LOG_INFO)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
ReticulumDiscoveryEngine = ReticulumDiscoveryService
|
|
159
|
+
|
|
160
|
+
__all__ = [
|
|
161
|
+
"RETICULUM_AUTOINTERFACE_PORT",
|
|
162
|
+
"ReticulumDiscoveryService",
|
|
163
|
+
"ReticulumDiscoveryEngine",
|
|
164
|
+
]
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Layer 3 Reticulum Sovereign Identity Subsystem (DEL-390-01).
|
|
2
|
+
|
|
3
|
+
Implements:
|
|
4
|
+
- Derivation of 16-byte Reticulum destination hashes from AgentDid (did:key:z6Mk... and generic DIDs)
|
|
5
|
+
- Multicodec base58 decoding for Ed25519 did:key
|
|
6
|
+
- Integration with RNS.Identity and RNS.Destination
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
from typing import List, Optional, Union
|
|
14
|
+
|
|
15
|
+
import base58
|
|
16
|
+
|
|
17
|
+
RETICULUM_PATH = os.path.expanduser("~/Documents/Development/Networking/Reticulum")
|
|
18
|
+
if RETICULUM_PATH not in sys.path and os.path.exists(RETICULUM_PATH):
|
|
19
|
+
sys.path.insert(0, RETICULUM_PATH)
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
import RNS
|
|
23
|
+
except (ImportError, ModuleNotFoundError):
|
|
24
|
+
RNS = None # type: ignore
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def extract_raw_pubkey_from_did(did: str) -> bytes:
|
|
28
|
+
"""Extracts raw 32-byte Ed25519 public key bytes from a did:key string.
|
|
29
|
+
Format: did:key:z6Mk... where 'z' indicates multibase base58btc,
|
|
30
|
+
and 0xed01 indicates Ed25519 multicodec.
|
|
31
|
+
If did is not did:key or fails decoding, returns SHA256(did.encode('utf-8'))[:32].
|
|
32
|
+
"""
|
|
33
|
+
if did.startswith("did:key:z"):
|
|
34
|
+
try:
|
|
35
|
+
raw_b58 = did[9:] # strip 'did:key:z'
|
|
36
|
+
decoded = base58.b58decode(raw_b58)
|
|
37
|
+
# Multicodec for ed25519-pub is 0xed, 0x01 (2 bytes)
|
|
38
|
+
if len(decoded) >= 34 and decoded[0] == 0xED and decoded[1] == 0x01:
|
|
39
|
+
return decoded[2:34]
|
|
40
|
+
elif len(decoded) == 32:
|
|
41
|
+
return decoded
|
|
42
|
+
except Exception:
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
return hashlib.sha256(did.encode("utf-8")).digest()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def derive_destination_hash(
|
|
49
|
+
did_or_material: Union[str, bytes],
|
|
50
|
+
app_name: str = "clm",
|
|
51
|
+
aspects: Optional[List[str]] = None,
|
|
52
|
+
) -> bytes:
|
|
53
|
+
"""Deterministically derives a 16-byte Reticulum destination hash from an
|
|
54
|
+
AgentDid or public key material according to INV-L3-RET-355-01.
|
|
55
|
+
|
|
56
|
+
Formula:
|
|
57
|
+
1. name_str = f"{app_name}.{'.'.join(aspects)}"
|
|
58
|
+
2. name_hash = SHA-256(name_str)[:10] (80-bit name hash)
|
|
59
|
+
3. identity_hash = SHA-256(raw_pubkey)[:16] (128-bit identity hash)
|
|
60
|
+
4. dest_hash = SHA-256(name_hash + identity_hash)[:16]
|
|
61
|
+
"""
|
|
62
|
+
aspects = aspects if aspects is not None else ["pkc", "node"]
|
|
63
|
+
full_name = f"{app_name}.{'.'.join(aspects)}"
|
|
64
|
+
name_hash = hashlib.sha256(full_name.encode("utf-8")).digest()[:10]
|
|
65
|
+
|
|
66
|
+
if isinstance(did_or_material, bytes):
|
|
67
|
+
raw_key = did_or_material
|
|
68
|
+
else:
|
|
69
|
+
raw_key = extract_raw_pubkey_from_did(did_or_material)
|
|
70
|
+
|
|
71
|
+
identity_hash = hashlib.sha256(raw_key).digest()[:16]
|
|
72
|
+
addr_material = name_hash + identity_hash
|
|
73
|
+
return hashlib.sha256(addr_material).digest()[:16]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def derive_destination_hex(
|
|
77
|
+
did_or_material: Union[str, bytes],
|
|
78
|
+
app_name: str = "clm",
|
|
79
|
+
aspects: Optional[List[str]] = None,
|
|
80
|
+
) -> str:
|
|
81
|
+
"""Returns 32-character lowercase hex string of the derived destination hash."""
|
|
82
|
+
return derive_destination_hash(did_or_material, app_name, aspects).hex()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def create_rns_identity_from_seed(seed: bytes) -> Any:
|
|
86
|
+
"""Constructs a deterministic RNS.Identity from a 32-byte seed.
|
|
87
|
+
Derives 32 bytes X25519 private key and 32 bytes Ed25519 private key.
|
|
88
|
+
"""
|
|
89
|
+
if RNS is None:
|
|
90
|
+
raise RuntimeError("RNS is not installed or available")
|
|
91
|
+
|
|
92
|
+
if len(seed) != 32:
|
|
93
|
+
seed = hashlib.sha256(seed).digest()
|
|
94
|
+
|
|
95
|
+
x_prv_bytes = hashlib.sha256(seed + b":x25519").digest()
|
|
96
|
+
ed_prv_bytes = hashlib.sha256(seed + b":ed25519").digest()
|
|
97
|
+
full_prv = x_prv_bytes + ed_prv_bytes
|
|
98
|
+
|
|
99
|
+
return RNS.Identity.from_bytes(full_prv)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
__all__ = [
|
|
103
|
+
"extract_raw_pubkey_from_did",
|
|
104
|
+
"derive_destination_hash",
|
|
105
|
+
"derive_destination_hex",
|
|
106
|
+
"create_rns_identity_from_seed",
|
|
107
|
+
]
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Layer 3 Reticulum Multi-Media MCard Medium Resolver & CAS Streamer (DEL-390-01).
|
|
2
|
+
|
|
3
|
+
Implements:
|
|
4
|
+
- Resource protocol chunk slicing and windowed reassembly
|
|
5
|
+
- BLAKE3 CAS verification (INV-L3-RET-356-01)
|
|
6
|
+
- Multi-media MCard instance and composition injection (INV-L3-RET-356-02)
|
|
7
|
+
- Resumable transfers over Reticulum Link
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from clm.layer0.hash import compute_blake3
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def slice_into_chunks(
|
|
18
|
+
payload: bytes,
|
|
19
|
+
chunk_size: int = 1024,
|
|
20
|
+
transfer_id: Optional[bytes] = None,
|
|
21
|
+
) -> List[Dict[str, Any]]:
|
|
22
|
+
"""Slices an arbitrary multi-media MCard binary payload into discrete
|
|
23
|
+
resource chunks with sequence indexing.
|
|
24
|
+
"""
|
|
25
|
+
if transfer_id is None:
|
|
26
|
+
transfer_id = hashlib.sha256(payload).digest()[:16]
|
|
27
|
+
|
|
28
|
+
total_parts = max(1, (len(payload) + chunk_size - 1) // chunk_size)
|
|
29
|
+
chunks = []
|
|
30
|
+
|
|
31
|
+
for i in range(total_parts):
|
|
32
|
+
start = i * chunk_size
|
|
33
|
+
end = min(len(payload), start + chunk_size)
|
|
34
|
+
chunk_data = payload[start:end]
|
|
35
|
+
chunks.append({
|
|
36
|
+
"transfer_id": transfer_id.hex(),
|
|
37
|
+
"part_index": i,
|
|
38
|
+
"total_parts": total_parts,
|
|
39
|
+
"data": chunk_data,
|
|
40
|
+
"size": len(chunk_data),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
return chunks
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def reassemble_chunks(
|
|
47
|
+
chunks: List[Dict[str, Any]],
|
|
48
|
+
expected_blake3: str,
|
|
49
|
+
) -> bytes:
|
|
50
|
+
"""Reassembles ordered resource chunks and verifies content-addressed
|
|
51
|
+
BLAKE3 integrity (INV-L3-RET-356-01).
|
|
52
|
+
"""
|
|
53
|
+
clean_expected = expected_blake3.split(":")[-1].lower()
|
|
54
|
+
|
|
55
|
+
# Sort chunks by part_index
|
|
56
|
+
sorted_chunks = sorted(chunks, key=lambda c: c["part_index"])
|
|
57
|
+
if not sorted_chunks:
|
|
58
|
+
raise ValueError("Cannot reassemble empty chunk list")
|
|
59
|
+
|
|
60
|
+
total_parts = sorted_chunks[0]["total_parts"]
|
|
61
|
+
if len(sorted_chunks) != total_parts:
|
|
62
|
+
raise ValueError(f"Incomplete resource transfer: got {len(sorted_chunks)} of {total_parts} parts")
|
|
63
|
+
|
|
64
|
+
parts = []
|
|
65
|
+
for idx, c in enumerate(sorted_chunks):
|
|
66
|
+
if c["part_index"] != idx:
|
|
67
|
+
raise ValueError(f"Missing chunk index {idx}")
|
|
68
|
+
parts.append(c["data"])
|
|
69
|
+
|
|
70
|
+
reassembled = b"".join(parts)
|
|
71
|
+
actual_hash = compute_blake3(reassembled).lower()
|
|
72
|
+
|
|
73
|
+
if actual_hash != clean_expected:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"BLAKE3 hash mismatch: expected {clean_expected}, got {actual_hash}"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return reassembled
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def inject_mcard_to_peer(
|
|
82
|
+
dest_hash: bytes,
|
|
83
|
+
mcard: Dict[str, Any],
|
|
84
|
+
cas_store: Dict[str, Any],
|
|
85
|
+
) -> Dict[str, Any]:
|
|
86
|
+
"""Injects an MCard instance or composite Merkle DAG into the recipient's CAS.
|
|
87
|
+
Enforces MCard Injection Atomicity (INV-L3-RET-356-02):
|
|
88
|
+
Child cards must exist before the composite parent is committed.
|
|
89
|
+
"""
|
|
90
|
+
# Check children for composite DAG
|
|
91
|
+
children = mcard.get("children", [])
|
|
92
|
+
for child_hash in children:
|
|
93
|
+
clean_child = child_hash.split(":")[-1]
|
|
94
|
+
if clean_child not in cas_store:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
f"Injection rejected: composite child {clean_child} not present in CAS"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Compute and verify content hash
|
|
100
|
+
content_bytes = mcard.get("content", b"")
|
|
101
|
+
if isinstance(content_bytes, str):
|
|
102
|
+
content_bytes = content_bytes.encode("utf-8")
|
|
103
|
+
|
|
104
|
+
computed_hash = compute_blake3(content_bytes).lower()
|
|
105
|
+
declared_hash = mcard.get("hash", "").split(":")[-1].lower()
|
|
106
|
+
|
|
107
|
+
if declared_hash and declared_hash != computed_hash:
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"MCard injection failed: hash mismatch (declared {declared_hash}, computed {computed_hash})"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
clean_hash = computed_hash
|
|
113
|
+
stored_record = {
|
|
114
|
+
"hash": f"blake3:{clean_hash}",
|
|
115
|
+
"uri": mcard.get("uri", f"rns:{dest_hash.hex()}/{clean_hash}"),
|
|
116
|
+
"author": mcard.get("author", "did:clm:peer"),
|
|
117
|
+
"payload": content_bytes,
|
|
118
|
+
"children": children,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
cas_store[clean_hash] = stored_record
|
|
122
|
+
return stored_record
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def fetch_mcard_resource(
|
|
126
|
+
dest_hash: bytes,
|
|
127
|
+
content_hash: str,
|
|
128
|
+
peer_cas: Dict[str, Any],
|
|
129
|
+
) -> bytes:
|
|
130
|
+
"""Simulates remote MCard retrieval via Reticulum Resource streaming.
|
|
131
|
+
Resolves content from remote CAS, slices into chunks, and reassembles with verification.
|
|
132
|
+
"""
|
|
133
|
+
clean_hash = content_hash.split(":")[-1].lower()
|
|
134
|
+
record = peer_cas.get(clean_hash)
|
|
135
|
+
if not record:
|
|
136
|
+
raise KeyError(f"MCard {clean_hash} not found on peer {dest_hash.hex()}")
|
|
137
|
+
|
|
138
|
+
raw_data = record["payload"]
|
|
139
|
+
chunks = slice_into_chunks(raw_data, chunk_size=512)
|
|
140
|
+
return reassemble_chunks(chunks, expected_blake3=clean_hash)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
__all__ = [
|
|
144
|
+
"slice_into_chunks",
|
|
145
|
+
"reassemble_chunks",
|
|
146
|
+
"inject_mcard_to_peer",
|
|
147
|
+
"fetch_mcard_resource",
|
|
148
|
+
]
|