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,519 @@
|
|
|
1
|
+
"""Pocket Flow Graph Scheduling & Monadic Lifecycle (DEL-388-01, INV-388-01..04).
|
|
2
|
+
|
|
3
|
+
Implements the category-theoretic Pocket Flow execution lifecycle:
|
|
4
|
+
Phase 1: prep() — Applicative Functor (<*>), parallel precondition gates
|
|
5
|
+
Phase 2: exec() — Polynomial Functor (A -> B), isolated computation
|
|
6
|
+
Phase 3: post() — Monadic Bind (>>=), output-dependent invariant checks with rollback
|
|
7
|
+
|
|
8
|
+
Enforces Noetherian token conservation (x^T · D = 0) on every transition firing
|
|
9
|
+
and raises NoetherErr on conservation violation.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
import copy
|
|
15
|
+
import logging
|
|
16
|
+
from dataclasses import asdict, dataclass, field
|
|
17
|
+
from typing import Any, Callable
|
|
18
|
+
|
|
19
|
+
from clm.layer0.exceptions import CLMError
|
|
20
|
+
from clm.layer1.petri import NoetherErr
|
|
21
|
+
from clm.layer1.store import PTRSharedStore
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PostGateRollbackError(CLMError):
|
|
27
|
+
"""Raised when Monadic post-gate verification collapses to ⊥, triggering rollback."""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class PrepResult:
|
|
33
|
+
"""Result of the Applicative Pre-Gate (<*>)."""
|
|
34
|
+
success: bool
|
|
35
|
+
error: str | None = None
|
|
36
|
+
context: dict[str, Any] = field(default_factory=dict)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class ExecResult:
|
|
41
|
+
"""Result of the Polynomial Functor execution (A -> B)."""
|
|
42
|
+
success: bool
|
|
43
|
+
output: Any = None
|
|
44
|
+
error: str | None = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class PostResult:
|
|
49
|
+
"""Result of the Monadic Post-Gate (>>=)."""
|
|
50
|
+
success: bool
|
|
51
|
+
error: str | None = None
|
|
52
|
+
witness: Any = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class PrepPhase:
|
|
56
|
+
"""Applicative Pre-Gate (<*>).
|
|
57
|
+
|
|
58
|
+
Evaluates independent preconditions concurrently without observing
|
|
59
|
+
or depending on unproduced outputs.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
checks: list[Callable[[Any, dict[str, Any]], bool | tuple[bool, str]]]
|
|
65
|
+
| Callable[[Any, dict[str, Any]], bool | tuple[bool, str]]
|
|
66
|
+
| None = None,
|
|
67
|
+
):
|
|
68
|
+
if checks is None:
|
|
69
|
+
self.checks = []
|
|
70
|
+
elif isinstance(checks, list):
|
|
71
|
+
self.checks = checks
|
|
72
|
+
else:
|
|
73
|
+
self.checks = [checks]
|
|
74
|
+
|
|
75
|
+
def add_check(self, check: Callable[[Any, dict[str, Any]], bool | tuple[bool, str]]) -> None:
|
|
76
|
+
self.checks.append(check)
|
|
77
|
+
|
|
78
|
+
def evaluate(self, store: Any, context: dict[str, Any] | None = None) -> PrepResult:
|
|
79
|
+
ctx = context or {}
|
|
80
|
+
for check in self.checks:
|
|
81
|
+
try:
|
|
82
|
+
res = check(store, ctx)
|
|
83
|
+
if isinstance(res, tuple):
|
|
84
|
+
passed, msg = res
|
|
85
|
+
if not passed:
|
|
86
|
+
return PrepResult(success=False, error=msg, context=ctx)
|
|
87
|
+
elif not res:
|
|
88
|
+
return PrepResult(success=False, error="Precondition check failed", context=ctx)
|
|
89
|
+
except Exception as e:
|
|
90
|
+
return PrepResult(success=False, error=f"Precondition exception: {e}", context=ctx)
|
|
91
|
+
return PrepResult(success=True, context=ctx)
|
|
92
|
+
|
|
93
|
+
async def evaluate_async(self, store: Any, context: dict[str, Any] | None = None) -> PrepResult:
|
|
94
|
+
"""Evaluate all preconditions concurrently as pure Applicative Functor (<*>)."""
|
|
95
|
+
ctx = context or {}
|
|
96
|
+
if not self.checks:
|
|
97
|
+
return PrepResult(success=True, context=ctx)
|
|
98
|
+
|
|
99
|
+
loop = asyncio.get_running_loop()
|
|
100
|
+
|
|
101
|
+
def _run_sync(c):
|
|
102
|
+
return c(store, ctx)
|
|
103
|
+
|
|
104
|
+
tasks = [loop.run_in_executor(None, _run_sync, c) for c in self.checks]
|
|
105
|
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
106
|
+
|
|
107
|
+
for res in results:
|
|
108
|
+
if isinstance(res, Exception):
|
|
109
|
+
return PrepResult(success=False, error=str(res), context=ctx)
|
|
110
|
+
if isinstance(res, tuple):
|
|
111
|
+
passed, msg = res
|
|
112
|
+
if not passed:
|
|
113
|
+
return PrepResult(success=False, error=msg, context=ctx)
|
|
114
|
+
elif not res:
|
|
115
|
+
return PrepResult(success=False, error="Precondition check failed", context=ctx)
|
|
116
|
+
|
|
117
|
+
return PrepResult(success=True, context=ctx)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class ExecPhase:
|
|
121
|
+
"""Polynomial Functor (A -> B).
|
|
122
|
+
|
|
123
|
+
Morphism computing isolated output from inputs within the declared context.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def __init__(self, execute_fn: Callable[[Any, Any, dict[str, Any]], Any] | None = None):
|
|
127
|
+
self.execute_fn = execute_fn
|
|
128
|
+
|
|
129
|
+
def evaluate(self, inputs: Any, store: Any, context: dict[str, Any] | None = None) -> ExecResult:
|
|
130
|
+
ctx = context or {}
|
|
131
|
+
if self.execute_fn is None:
|
|
132
|
+
return ExecResult(success=True, output=inputs)
|
|
133
|
+
try:
|
|
134
|
+
output = self.execute_fn(inputs, store, ctx)
|
|
135
|
+
return ExecResult(success=True, output=output)
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error(f"[ExecPhase] Computation error: {e}", exc_info=True)
|
|
138
|
+
return ExecResult(success=False, error=str(e))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class PostPhase:
|
|
142
|
+
"""Monadic Post-Gate (>>=).
|
|
143
|
+
|
|
144
|
+
Output-dependent invariant inspection. If verification fails (Meet = ⊥),
|
|
145
|
+
triggers transactional rollback.
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
def __init__(self, verify_fn: Callable[[Any, Any, dict[str, Any]], bool | tuple[bool, str]] | None = None):
|
|
149
|
+
self.verify_fn = verify_fn
|
|
150
|
+
|
|
151
|
+
def evaluate(self, output: Any, store: Any, context: dict[str, Any] | None = None) -> PostResult:
|
|
152
|
+
ctx = context or {}
|
|
153
|
+
if self.verify_fn is None:
|
|
154
|
+
return PostResult(success=True, witness={"status": "default_pass"})
|
|
155
|
+
try:
|
|
156
|
+
res = self.verify_fn(output, store, ctx)
|
|
157
|
+
if isinstance(res, tuple):
|
|
158
|
+
passed, msg = res
|
|
159
|
+
if not passed:
|
|
160
|
+
return PostResult(success=False, error=msg)
|
|
161
|
+
return PostResult(success=True, witness={"message": msg})
|
|
162
|
+
elif not res:
|
|
163
|
+
return PostResult(success=False, error="Monadic post-condition check collapsed to ⊥")
|
|
164
|
+
return PostResult(success=True, witness={"verified": True})
|
|
165
|
+
except Exception as e:
|
|
166
|
+
return PostResult(success=False, error=f"Postcondition exception: {e}")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@dataclass
|
|
170
|
+
class DAGNode:
|
|
171
|
+
"""A node in the Pocket Flow DAG, isomorphic to a CPN Transition (t in T)."""
|
|
172
|
+
id: str
|
|
173
|
+
prep: PrepPhase | Callable | list[Callable] | None = None
|
|
174
|
+
exec: ExecPhase | Callable | None = None
|
|
175
|
+
post: PostPhase | Callable | None = None
|
|
176
|
+
dependencies: list[str] = field(default_factory=list)
|
|
177
|
+
inputs: list[str] = field(default_factory=list)
|
|
178
|
+
outputs: list[str] = field(default_factory=list)
|
|
179
|
+
tokens_consumed: int = 1
|
|
180
|
+
tokens_produced: int = 1
|
|
181
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
182
|
+
|
|
183
|
+
def __post_init__(self):
|
|
184
|
+
# Normalize prep
|
|
185
|
+
if not isinstance(self.prep, PrepPhase):
|
|
186
|
+
self.prep = PrepPhase(self.prep)
|
|
187
|
+
# Normalize exec
|
|
188
|
+
if not isinstance(self.exec, ExecPhase):
|
|
189
|
+
self.exec = ExecPhase(self.exec)
|
|
190
|
+
# Normalize post
|
|
191
|
+
if not isinstance(self.post, PostPhase):
|
|
192
|
+
self.post = PostPhase(self.post)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass
|
|
196
|
+
class SchedulerResult:
|
|
197
|
+
"""Aggregated result from executing a Pocket Flow DAG."""
|
|
198
|
+
success: bool
|
|
199
|
+
node_results: dict[str, Any] = field(default_factory=dict)
|
|
200
|
+
store: Any = None
|
|
201
|
+
error: str | None = None
|
|
202
|
+
telemetry: dict[str, Any] = field(default_factory=dict)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class PocketFlowScheduler:
|
|
206
|
+
"""Coordinates multi-node agentic DAG execution using the Pocket Flow lifecycle.
|
|
207
|
+
|
|
208
|
+
Guarantees:
|
|
209
|
+
- INV-388-01: Applicative prep (<*>) and Monadic post (>>=) typeclass asymmetry.
|
|
210
|
+
- INV-388-02: Noetherian token conservation (x^T · D = 0^T) across transition firings.
|
|
211
|
+
- Transactional rollback on post-phase failure.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
def __init__(self, nodes: list[DAGNode] | None = None):
|
|
215
|
+
self.nodes: dict[str, DAGNode] = {}
|
|
216
|
+
if nodes:
|
|
217
|
+
for n in nodes:
|
|
218
|
+
self.add_node(n)
|
|
219
|
+
|
|
220
|
+
def add_node(self, node: DAGNode) -> None:
|
|
221
|
+
self.nodes[node.id] = node
|
|
222
|
+
|
|
223
|
+
def add_nodes(self, nodes: list[DAGNode]) -> None:
|
|
224
|
+
for n in nodes:
|
|
225
|
+
self.add_node(n)
|
|
226
|
+
|
|
227
|
+
def topological_sort(self) -> list[DAGNode]:
|
|
228
|
+
"""Compute topological order of DAG nodes."""
|
|
229
|
+
in_degree: dict[str, int] = dict.fromkeys(self.nodes, 0)
|
|
230
|
+
adj: dict[str, list[str]] = {nid: [] for nid in self.nodes}
|
|
231
|
+
|
|
232
|
+
for nid, node in self.nodes.items():
|
|
233
|
+
for dep in node.dependencies:
|
|
234
|
+
if dep in self.nodes:
|
|
235
|
+
adj[dep].append(nid)
|
|
236
|
+
in_degree[nid] += 1
|
|
237
|
+
|
|
238
|
+
queue = [nid for nid, deg in in_degree.items() if deg == 0]
|
|
239
|
+
order = []
|
|
240
|
+
|
|
241
|
+
while queue:
|
|
242
|
+
curr = queue.pop(0)
|
|
243
|
+
order.append(self.nodes[curr])
|
|
244
|
+
for neighbor in adj[curr]:
|
|
245
|
+
in_degree[neighbor] -= 1
|
|
246
|
+
if in_degree[neighbor] == 0:
|
|
247
|
+
queue.append(neighbor)
|
|
248
|
+
|
|
249
|
+
if len(order) != len(self.nodes):
|
|
250
|
+
raise CLMError(f"PocketFlowScheduler cycle detected: graph contains a cycle among {[nid for nid, deg in in_degree.items() if deg > 0]}")
|
|
251
|
+
|
|
252
|
+
return order
|
|
253
|
+
|
|
254
|
+
def _snapshot_store(self, store: Any) -> Any:
|
|
255
|
+
"""Create an isolated snapshot of store state for atomic rollback."""
|
|
256
|
+
if isinstance(store, PTRSharedStore):
|
|
257
|
+
snap = PTRSharedStore()
|
|
258
|
+
snap.inputs = copy.deepcopy(store.inputs)
|
|
259
|
+
snap.outputs = copy.deepcopy(store.outputs)
|
|
260
|
+
snap.intermediate = copy.deepcopy(store.intermediate)
|
|
261
|
+
snap.auth_tokens = copy.deepcopy(store.auth_tokens)
|
|
262
|
+
snap.caller_did = store.caller_did
|
|
263
|
+
snap.target_did = store.target_did
|
|
264
|
+
snap.gas_budget = store.gas_budget
|
|
265
|
+
snap.gas_used = store.gas_used
|
|
266
|
+
snap.pcard_hash = store.pcard_hash
|
|
267
|
+
snap.target_hash = store.target_hash
|
|
268
|
+
return snap
|
|
269
|
+
elif isinstance(store, dict):
|
|
270
|
+
return copy.deepcopy(store)
|
|
271
|
+
return copy.deepcopy(store)
|
|
272
|
+
|
|
273
|
+
def _restore_store(self, target_store: Any, snapshot: Any) -> None:
|
|
274
|
+
"""Restore store from snapshot."""
|
|
275
|
+
if isinstance(target_store, PTRSharedStore) and isinstance(snapshot, PTRSharedStore):
|
|
276
|
+
target_store.inputs = copy.deepcopy(snapshot.inputs)
|
|
277
|
+
target_store.outputs = copy.deepcopy(snapshot.outputs)
|
|
278
|
+
target_store.intermediate = copy.deepcopy(snapshot.intermediate)
|
|
279
|
+
target_store.auth_tokens = copy.deepcopy(snapshot.auth_tokens)
|
|
280
|
+
target_store.caller_did = snapshot.caller_did
|
|
281
|
+
target_store.target_did = snapshot.target_did
|
|
282
|
+
target_store.gas_budget = snapshot.gas_budget
|
|
283
|
+
target_store.gas_used = snapshot.gas_used
|
|
284
|
+
elif isinstance(target_store, dict) and isinstance(snapshot, dict):
|
|
285
|
+
target_store.clear()
|
|
286
|
+
target_store.update(copy.deepcopy(snapshot))
|
|
287
|
+
|
|
288
|
+
def _get_store_val(self, store: Any, key: str) -> Any:
|
|
289
|
+
if isinstance(store, PTRSharedStore) or isinstance(store, dict):
|
|
290
|
+
return store.get(key)
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
def _set_store_val(self, store: Any, key: str, val: Any) -> None:
|
|
294
|
+
if isinstance(store, PTRSharedStore):
|
|
295
|
+
store.set(key, val)
|
|
296
|
+
elif isinstance(store, dict):
|
|
297
|
+
store[key] = val
|
|
298
|
+
|
|
299
|
+
def verify_noetherian_invariants(
|
|
300
|
+
self, node: DAGNode, token_markings: dict[str, int]
|
|
301
|
+
) -> None:
|
|
302
|
+
"""Assert Noetherian token conservation (x^T · D = 0) for transition firing.
|
|
303
|
+
|
|
304
|
+
Raises NoetherErr if input tokens are insufficient or consumed != produced.
|
|
305
|
+
"""
|
|
306
|
+
# 1. Input token availability check: M >= D^-
|
|
307
|
+
for inp in node.inputs:
|
|
308
|
+
current = token_markings.get(inp, 0)
|
|
309
|
+
if current < node.tokens_consumed:
|
|
310
|
+
raise NoetherErr(
|
|
311
|
+
f"Noetherian Invariant Violation on transition '{node.id}': "
|
|
312
|
+
f"Place '{inp}' has {current} tokens, but transition requires {node.tokens_consumed}."
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# 2. Token conservation balance check: sum(D) = 0 for standard closed transitions
|
|
316
|
+
delta = node.tokens_produced - node.tokens_consumed
|
|
317
|
+
if delta != 0 and node.metadata.get("enforce_conservation", True):
|
|
318
|
+
raise NoetherErr(
|
|
319
|
+
f"Noetherian Invariant Violation on transition '{node.id}': "
|
|
320
|
+
f"Conservation x^T · D = 0 violated (consumed {node.tokens_consumed}, produced {node.tokens_produced}, delta = {delta})."
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def execute(
|
|
324
|
+
self, store: Any = None, context: dict[str, Any] | None = None
|
|
325
|
+
) -> SchedulerResult:
|
|
326
|
+
"""Synchronously execute the Pocket Flow DAG."""
|
|
327
|
+
if store is None:
|
|
328
|
+
store = PTRSharedStore()
|
|
329
|
+
ctx = context or {}
|
|
330
|
+
order = self.topological_sort()
|
|
331
|
+
|
|
332
|
+
# Initialize Petri Net token markings from store inputs
|
|
333
|
+
token_markings: dict[str, int] = {}
|
|
334
|
+
for nid, node in self.nodes.items():
|
|
335
|
+
for inp in node.inputs:
|
|
336
|
+
if inp not in token_markings:
|
|
337
|
+
val = self._get_store_val(store, f"{inp}_tokens")
|
|
338
|
+
if val is None:
|
|
339
|
+
val = self._get_store_val(store, f"{inp}")
|
|
340
|
+
if val is None:
|
|
341
|
+
val = 1
|
|
342
|
+
token_markings[inp] = int(val) if isinstance(val, (int, float, str)) and str(val).isdigit() else 1
|
|
343
|
+
for outp in node.outputs:
|
|
344
|
+
if outp not in token_markings:
|
|
345
|
+
val = self._get_store_val(store, f"{outp}_tokens")
|
|
346
|
+
if val is None:
|
|
347
|
+
val = 0
|
|
348
|
+
token_markings[outp] = int(val) if isinstance(val, (int, float, str)) and str(val).isdigit() else 0
|
|
349
|
+
|
|
350
|
+
node_results: dict[str, Any] = {}
|
|
351
|
+
|
|
352
|
+
for node in order:
|
|
353
|
+
snapshot = self._snapshot_store(store)
|
|
354
|
+
logger.debug(f"[PocketFlowScheduler] Firing node '{node.id}'")
|
|
355
|
+
|
|
356
|
+
# Invariant check: Noetherian token conservation prior to execution
|
|
357
|
+
self.verify_noetherian_invariants(node, token_markings)
|
|
358
|
+
|
|
359
|
+
# Phase 1: prep (Applicative Functor <*>)
|
|
360
|
+
prep_res = node.prep.evaluate(store, ctx)
|
|
361
|
+
if not prep_res.success:
|
|
362
|
+
err_msg = f"Node '{node.id}' prep phase failed: {prep_res.error}"
|
|
363
|
+
logger.warning(err_msg)
|
|
364
|
+
return SchedulerResult(
|
|
365
|
+
success=False,
|
|
366
|
+
node_results=node_results,
|
|
367
|
+
store=store,
|
|
368
|
+
error=err_msg,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
# Resolve input token payload from store inputs
|
|
372
|
+
input_payload = None
|
|
373
|
+
if node.inputs:
|
|
374
|
+
input_payload = {inp: self._get_store_val(store, inp) for inp in node.inputs}
|
|
375
|
+
if len(node.inputs) == 1:
|
|
376
|
+
input_payload = input_payload[node.inputs[0]]
|
|
377
|
+
|
|
378
|
+
# Phase 2: exec (Polynomial Functor A -> B)
|
|
379
|
+
exec_res = node.exec.evaluate(input_payload, store, ctx)
|
|
380
|
+
if not exec_res.success:
|
|
381
|
+
self._restore_store(store, snapshot)
|
|
382
|
+
err_msg = f"Node '{node.id}' exec phase failed: {exec_res.error}"
|
|
383
|
+
logger.warning(err_msg)
|
|
384
|
+
return SchedulerResult(
|
|
385
|
+
success=False,
|
|
386
|
+
node_results=node_results,
|
|
387
|
+
store=store,
|
|
388
|
+
error=err_msg,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Phase 3: post (Monadic Bind >>=)
|
|
392
|
+
post_res = node.post.evaluate(exec_res.output, store, ctx)
|
|
393
|
+
if not post_res.success:
|
|
394
|
+
# Atomically rollback store to pre-node state (<1ms)
|
|
395
|
+
self._restore_store(store, snapshot)
|
|
396
|
+
err_msg = f"Node '{node.id}' post-gate failed (Meet = ⊥): {post_res.error}"
|
|
397
|
+
logger.warning(f"[ROLLBACK] {err_msg}")
|
|
398
|
+
return SchedulerResult(
|
|
399
|
+
success=False,
|
|
400
|
+
node_results=node_results,
|
|
401
|
+
store=store,
|
|
402
|
+
error=err_msg,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# Update token markings upon successful firing: M' = M + D · y
|
|
406
|
+
for inp in node.inputs:
|
|
407
|
+
token_markings[inp] -= node.tokens_consumed
|
|
408
|
+
for outp in node.outputs:
|
|
409
|
+
token_markings[outp] += node.tokens_produced
|
|
410
|
+
# Commit output value to store
|
|
411
|
+
self._set_store_val(store, outp, exec_res.output)
|
|
412
|
+
|
|
413
|
+
# Record node execution result
|
|
414
|
+
node_results[node.id] = {
|
|
415
|
+
"prep": asdict(prep_res),
|
|
416
|
+
"exec": asdict(exec_res),
|
|
417
|
+
"post": asdict(post_res),
|
|
418
|
+
"output": exec_res.output,
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return SchedulerResult(
|
|
422
|
+
success=True,
|
|
423
|
+
node_results=node_results,
|
|
424
|
+
store=store,
|
|
425
|
+
telemetry={"executed_nodes": len(order), "final_markings": token_markings},
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
async def execute_async(
|
|
429
|
+
self, store: Any = None, context: dict[str, Any] | None = None
|
|
430
|
+
) -> SchedulerResult:
|
|
431
|
+
"""Asynchronously execute the Pocket Flow DAG with parallel Applicative prep."""
|
|
432
|
+
if store is None:
|
|
433
|
+
store = PTRSharedStore()
|
|
434
|
+
ctx = context or {}
|
|
435
|
+
order = self.topological_sort()
|
|
436
|
+
|
|
437
|
+
token_markings: dict[str, int] = {}
|
|
438
|
+
for nid, node in self.nodes.items():
|
|
439
|
+
for inp in node.inputs:
|
|
440
|
+
if inp not in token_markings:
|
|
441
|
+
val = self._get_store_val(store, f"{inp}_tokens")
|
|
442
|
+
if val is None:
|
|
443
|
+
val = self._get_store_val(store, f"{inp}")
|
|
444
|
+
if val is None:
|
|
445
|
+
val = 1
|
|
446
|
+
token_markings[inp] = int(val) if isinstance(val, (int, float, str)) and str(val).isdigit() else 1
|
|
447
|
+
for outp in node.outputs:
|
|
448
|
+
if outp not in token_markings:
|
|
449
|
+
val = self._get_store_val(store, f"{outp}_tokens")
|
|
450
|
+
if val is None:
|
|
451
|
+
val = 0
|
|
452
|
+
token_markings[outp] = int(val) if isinstance(val, (int, float, str)) and str(val).isdigit() else 0
|
|
453
|
+
|
|
454
|
+
node_results: dict[str, Any] = {}
|
|
455
|
+
|
|
456
|
+
for node in order:
|
|
457
|
+
snapshot = self._snapshot_store(store)
|
|
458
|
+
self.verify_noetherian_invariants(node, token_markings)
|
|
459
|
+
|
|
460
|
+
# Phase 1: Async Applicative prep (<*>)
|
|
461
|
+
prep_res = await node.prep.evaluate_async(store, ctx)
|
|
462
|
+
if not prep_res.success:
|
|
463
|
+
err_msg = f"Node '{node.id}' prep phase failed: {prep_res.error}"
|
|
464
|
+
return SchedulerResult(success=False, node_results=node_results, store=store, error=err_msg)
|
|
465
|
+
|
|
466
|
+
input_payload = None
|
|
467
|
+
if node.inputs:
|
|
468
|
+
input_payload = {inp: self._get_store_val(store, inp) for inp in node.inputs}
|
|
469
|
+
if len(node.inputs) == 1:
|
|
470
|
+
input_payload = input_payload[node.inputs[0]]
|
|
471
|
+
|
|
472
|
+
# Phase 2: exec
|
|
473
|
+
loop = asyncio.get_running_loop()
|
|
474
|
+
exec_res = await loop.run_in_executor(None, node.exec.evaluate, input_payload, store, ctx)
|
|
475
|
+
if not exec_res.success:
|
|
476
|
+
self._restore_store(store, snapshot)
|
|
477
|
+
err_msg = f"Node '{node.id}' exec phase failed: {exec_res.error}"
|
|
478
|
+
return SchedulerResult(success=False, node_results=node_results, store=store, error=err_msg)
|
|
479
|
+
|
|
480
|
+
# Phase 3: post
|
|
481
|
+
post_res = node.post.evaluate(exec_res.output, store, ctx)
|
|
482
|
+
if not post_res.success:
|
|
483
|
+
self._restore_store(store, snapshot)
|
|
484
|
+
err_msg = f"Node '{node.id}' post-gate failed (Meet = ⊥): {post_res.error}"
|
|
485
|
+
return SchedulerResult(success=False, node_results=node_results, store=store, error=err_msg)
|
|
486
|
+
|
|
487
|
+
for inp in node.inputs:
|
|
488
|
+
token_markings[inp] -= node.tokens_consumed
|
|
489
|
+
for outp in node.outputs:
|
|
490
|
+
token_markings[outp] += node.tokens_produced
|
|
491
|
+
self._set_store_val(store, outp, exec_res.output)
|
|
492
|
+
|
|
493
|
+
node_results[node.id] = {
|
|
494
|
+
"prep": asdict(prep_res),
|
|
495
|
+
"exec": asdict(exec_res),
|
|
496
|
+
"post": asdict(post_res),
|
|
497
|
+
"output": exec_res.output,
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return SchedulerResult(
|
|
501
|
+
success=True,
|
|
502
|
+
node_results=node_results,
|
|
503
|
+
store=store,
|
|
504
|
+
telemetry={"executed_nodes": len(order), "final_markings": token_markings},
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
__all__ = [
|
|
509
|
+
"PrepResult",
|
|
510
|
+
"ExecResult",
|
|
511
|
+
"PostResult",
|
|
512
|
+
"PrepPhase",
|
|
513
|
+
"ExecPhase",
|
|
514
|
+
"PostPhase",
|
|
515
|
+
"DAGNode",
|
|
516
|
+
"PocketFlowScheduler",
|
|
517
|
+
"SchedulerResult",
|
|
518
|
+
"PostGateRollbackError",
|
|
519
|
+
]
|