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,274 @@
|
|
|
1
|
+
"""Declarative Workflow Manifest Domain Models (Sprint 395 / DEL-395-01).
|
|
2
|
+
|
|
3
|
+
Implements Pydantic V2 domain models for canonical CLM Workflow specifications (clm.workflow.v1).
|
|
4
|
+
A Workflow represents a Category-Theoretic Monadic Composition (Kleisli Category)
|
|
5
|
+
chaining multiple Actions algebraically across the Pocket Flow Scheduler.
|
|
6
|
+
|
|
7
|
+
Zero dependencies on Layer 1 through Layer 5.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import re
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Literal
|
|
16
|
+
|
|
17
|
+
import yaml
|
|
18
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
19
|
+
|
|
20
|
+
from clm.layer0.exceptions import CLMError
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CyclicDependencyError(CLMError):
|
|
24
|
+
"""Raised when a workflow contains a circular dependency between steps."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WorkflowValidationError(CLMError):
|
|
29
|
+
"""Raised when static validation of a workflow manifest fails (e.g. dangling reference)."""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class WorkflowMetadata(BaseModel):
|
|
34
|
+
"""Metadata block identifying the workflow and its role in CLM."""
|
|
35
|
+
|
|
36
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
37
|
+
|
|
38
|
+
type: Literal["workflow", "pocketflow_dag"] = "workflow"
|
|
39
|
+
name: str = Field(..., pattern=r"^[a-z0-9_\-]+$")
|
|
40
|
+
description: str | None = None
|
|
41
|
+
author: str = "did:clm:system"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class WorkflowInput(BaseModel):
|
|
45
|
+
"""Specification of an input parameter to the workflow."""
|
|
46
|
+
|
|
47
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
48
|
+
|
|
49
|
+
name: str
|
|
50
|
+
format: str | None = None
|
|
51
|
+
description: str | None = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class WorkflowStep(BaseModel):
|
|
55
|
+
"""A discrete Action step in the workflow DAG."""
|
|
56
|
+
|
|
57
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
58
|
+
|
|
59
|
+
id: str = Field(..., pattern=r"^[a-z0-9_\-]+$")
|
|
60
|
+
action: str
|
|
61
|
+
inputs: dict[str, str] = Field(default_factory=dict)
|
|
62
|
+
depends_on: list[str] = Field(default_factory=list)
|
|
63
|
+
|
|
64
|
+
def extract_implicit_dependencies(self) -> set[str]:
|
|
65
|
+
"""Extract referenced step IDs from input binding expressions ($step_id.output...)."""
|
|
66
|
+
deps: set[str] = set()
|
|
67
|
+
step_ref_pattern = re.compile(r"^\$([a-z0-9_\-]+)\.output")
|
|
68
|
+
for val in self.inputs.values():
|
|
69
|
+
if isinstance(val, str):
|
|
70
|
+
match = step_ref_pattern.match(val.strip())
|
|
71
|
+
if match:
|
|
72
|
+
ref = match.group(1)
|
|
73
|
+
if ref != "workflow":
|
|
74
|
+
deps.add(ref)
|
|
75
|
+
return deps
|
|
76
|
+
|
|
77
|
+
def all_dependencies(self) -> set[str]:
|
|
78
|
+
"""Return combined explicit and implicit dependencies for this step."""
|
|
79
|
+
return set(self.depends_on).union(self.extract_implicit_dependencies())
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class WorkflowOutput(BaseModel):
|
|
83
|
+
"""Specification of an output produced by the workflow."""
|
|
84
|
+
|
|
85
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
86
|
+
|
|
87
|
+
name: str
|
|
88
|
+
source: str
|
|
89
|
+
format: str | None = None
|
|
90
|
+
description: str | None = None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class WorkflowManifest(BaseModel):
|
|
94
|
+
"""Canonical CLM Workflow specification (clm.workflow.v1).
|
|
95
|
+
|
|
96
|
+
Orchestrates multiple Actions in a directed acyclic graph (DAG) with
|
|
97
|
+
strict Kleisli monadic composition (>>>), parallel tensor branching (⊗),
|
|
98
|
+
and Merkle causal lineage.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
102
|
+
|
|
103
|
+
version: Literal["1.0", "1.0.0"] = "1.0"
|
|
104
|
+
clm: WorkflowMetadata
|
|
105
|
+
inputs: list[WorkflowInput] = Field(default_factory=list)
|
|
106
|
+
steps: list[WorkflowStep] = Field(..., min_length=1)
|
|
107
|
+
outputs: list[WorkflowOutput] = Field(default_factory=list)
|
|
108
|
+
|
|
109
|
+
@model_validator(mode="after")
|
|
110
|
+
def validate_dag_and_references(self) -> WorkflowManifest:
|
|
111
|
+
"""Validate step uniqueness, references, and assert acyclicity."""
|
|
112
|
+
# 1. Step uniqueness
|
|
113
|
+
step_ids: set[str] = set()
|
|
114
|
+
step_map: dict[str, WorkflowStep] = {}
|
|
115
|
+
for s in self.steps:
|
|
116
|
+
if s.id in step_ids:
|
|
117
|
+
raise WorkflowValidationError(f"Duplicate step id '{s.id}' in workflow '{self.clm.name}'")
|
|
118
|
+
step_ids.add(s.id)
|
|
119
|
+
step_map[s.id] = s
|
|
120
|
+
|
|
121
|
+
# Known input names
|
|
122
|
+
input_names = {inp.name for inp in self.inputs}
|
|
123
|
+
|
|
124
|
+
# 2. Dependency & Reference validation
|
|
125
|
+
adj: dict[str, set[str]] = {sid: set() for sid in step_ids}
|
|
126
|
+
|
|
127
|
+
workflow_inp_pattern = re.compile(r"^\$workflow\.input(?:\.([a-z0-9_\-]+))?$")
|
|
128
|
+
step_out_pattern = re.compile(r"^\$([a-z0-9_\-]+)\.output")
|
|
129
|
+
|
|
130
|
+
for s in self.steps:
|
|
131
|
+
all_deps = s.all_dependencies()
|
|
132
|
+
for dep in all_deps:
|
|
133
|
+
if dep not in step_ids:
|
|
134
|
+
raise WorkflowValidationError(
|
|
135
|
+
f"Step '{s.id}' references unknown dependency or step '{dep}'"
|
|
136
|
+
)
|
|
137
|
+
if dep == s.id:
|
|
138
|
+
raise CyclicDependencyError(f"Step '{s.id}' has self-referential dependency")
|
|
139
|
+
adj[dep].add(s.id)
|
|
140
|
+
|
|
141
|
+
# Check workflow inputs
|
|
142
|
+
for in_key, in_val in s.inputs.items():
|
|
143
|
+
if isinstance(in_val, str):
|
|
144
|
+
wf_match = workflow_inp_pattern.match(in_val.strip())
|
|
145
|
+
if wf_match:
|
|
146
|
+
inp_field = wf_match.group(1)
|
|
147
|
+
if inp_field and input_names and inp_field not in input_names:
|
|
148
|
+
raise WorkflowValidationError(
|
|
149
|
+
f"Step '{s.id}' references undeclared workflow input '{inp_field}'"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# 3. Output source validation
|
|
153
|
+
for out in self.outputs:
|
|
154
|
+
match = step_out_pattern.match(out.source.strip())
|
|
155
|
+
if match:
|
|
156
|
+
ref_step = match.group(1)
|
|
157
|
+
if ref_step not in step_ids and ref_step != "workflow":
|
|
158
|
+
raise WorkflowValidationError(
|
|
159
|
+
f"Workflow output '{out.name}' references unknown step '{ref_step}'"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# 4. Topological sort & cycle detection (Kahn's algorithm)
|
|
163
|
+
# Compute in-degree from adj (dep -> dependent)
|
|
164
|
+
calc_in_degree: dict[str, int] = dict.fromkeys(step_ids, 0)
|
|
165
|
+
for dep, dependents in adj.items():
|
|
166
|
+
for target in dependents:
|
|
167
|
+
calc_in_degree[target] += 1
|
|
168
|
+
|
|
169
|
+
queue = [sid for sid, deg in calc_in_degree.items() if deg == 0]
|
|
170
|
+
sorted_order: list[str] = []
|
|
171
|
+
|
|
172
|
+
while queue:
|
|
173
|
+
curr = queue.pop(0)
|
|
174
|
+
sorted_order.append(curr)
|
|
175
|
+
for neighbor in adj[curr]:
|
|
176
|
+
calc_in_degree[neighbor] -= 1
|
|
177
|
+
if calc_in_degree[neighbor] == 0:
|
|
178
|
+
queue.append(neighbor)
|
|
179
|
+
|
|
180
|
+
if len(sorted_order) != len(step_ids):
|
|
181
|
+
cycle_nodes = [sid for sid, deg in calc_in_degree.items() if deg > 0]
|
|
182
|
+
raise CyclicDependencyError(
|
|
183
|
+
f"Workflow '{self.clm.name}' contains a circular dependency involving steps: {cycle_nodes}"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return self
|
|
187
|
+
|
|
188
|
+
def get_step(self, step_id: str) -> WorkflowStep:
|
|
189
|
+
"""Find a step by its identifier."""
|
|
190
|
+
for s in self.steps:
|
|
191
|
+
if s.id == step_id:
|
|
192
|
+
return s
|
|
193
|
+
raise KeyError(f"Step '{step_id}' not found in workflow")
|
|
194
|
+
|
|
195
|
+
def get_topological_order(self) -> list[str]:
|
|
196
|
+
"""Compute valid linear execution order of steps."""
|
|
197
|
+
adj: dict[str, set[str]] = {s.id: set() for s in self.steps}
|
|
198
|
+
in_degree: dict[str, int] = {s.id: 0 for s in self.steps}
|
|
199
|
+
|
|
200
|
+
for s in self.steps:
|
|
201
|
+
for dep in s.all_dependencies():
|
|
202
|
+
adj[dep].add(s.id)
|
|
203
|
+
in_degree[s.id] += 1
|
|
204
|
+
|
|
205
|
+
queue = [sid for sid, deg in in_degree.items() if deg == 0]
|
|
206
|
+
order: list[str] = []
|
|
207
|
+
|
|
208
|
+
while queue:
|
|
209
|
+
curr = queue.pop(0)
|
|
210
|
+
order.append(curr)
|
|
211
|
+
for neighbor in adj[curr]:
|
|
212
|
+
in_degree[neighbor] -= 1
|
|
213
|
+
if in_degree[neighbor] == 0:
|
|
214
|
+
queue.append(neighbor)
|
|
215
|
+
|
|
216
|
+
return order
|
|
217
|
+
|
|
218
|
+
def get_execution_stages(self) -> list[list[str]]:
|
|
219
|
+
"""Group steps into parallel execution stages based on dependency depth.
|
|
220
|
+
|
|
221
|
+
Steps in the same stage have no mutual dependencies and their
|
|
222
|
+
ancestor dependencies are fulfilled by preceding stages.
|
|
223
|
+
"""
|
|
224
|
+
top_order = self.get_topological_order()
|
|
225
|
+
depths: dict[str, int] = {}
|
|
226
|
+
for sid in top_order:
|
|
227
|
+
step = self.get_step(sid)
|
|
228
|
+
deps = step.all_dependencies()
|
|
229
|
+
if not deps:
|
|
230
|
+
depths[sid] = 0
|
|
231
|
+
else:
|
|
232
|
+
depths[sid] = max(depths[d] for d in deps) + 1
|
|
233
|
+
|
|
234
|
+
max_depth = max(depths.values()) if depths else 0
|
|
235
|
+
stages: list[list[str]] = [[] for _ in range(max_depth + 1)]
|
|
236
|
+
for sid in top_order:
|
|
237
|
+
stages[depths[sid]].append(sid)
|
|
238
|
+
|
|
239
|
+
return stages
|
|
240
|
+
|
|
241
|
+
@classmethod
|
|
242
|
+
def from_dict(cls, data: dict[str, Any]) -> WorkflowManifest:
|
|
243
|
+
"""Instantiate from a raw Python dictionary."""
|
|
244
|
+
return cls.model_validate(data)
|
|
245
|
+
|
|
246
|
+
@classmethod
|
|
247
|
+
def from_yaml(cls, source: str | Path) -> WorkflowManifest:
|
|
248
|
+
"""Parse from YAML string or file path."""
|
|
249
|
+
p = Path(source) if isinstance(source, (str, Path)) else None
|
|
250
|
+
if p and p.exists() and p.is_file():
|
|
251
|
+
content = p.read_text(encoding="utf-8")
|
|
252
|
+
else:
|
|
253
|
+
content = str(source)
|
|
254
|
+
data = yaml.safe_load(content)
|
|
255
|
+
return cls.from_dict(data)
|
|
256
|
+
|
|
257
|
+
@classmethod
|
|
258
|
+
def from_json(cls, source: str | Path) -> WorkflowManifest:
|
|
259
|
+
"""Parse from JSON string or file path."""
|
|
260
|
+
p = Path(source) if isinstance(source, (str, Path)) else None
|
|
261
|
+
if p and p.exists() and p.is_file():
|
|
262
|
+
content = p.read_text(encoding="utf-8")
|
|
263
|
+
else:
|
|
264
|
+
content = str(source)
|
|
265
|
+
data = json.loads(content)
|
|
266
|
+
return cls.from_dict(data)
|
|
267
|
+
|
|
268
|
+
def to_dict(self) -> dict[str, Any]:
|
|
269
|
+
"""Convert manifest to Python dictionary."""
|
|
270
|
+
return self.model_dump(exclude_none=True)
|
|
271
|
+
|
|
272
|
+
def to_yaml(self) -> str:
|
|
273
|
+
"""Convert manifest to YAML string."""
|
|
274
|
+
return yaml.dump(self.to_dict(), sort_keys=False)
|
clm/layer0/narrative.py
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
"""CLM Monadic Template & Narrative Spacetime Monad (Layer 0).
|
|
2
|
+
|
|
3
|
+
Part of Phase 7 (Sprint 383: Layer 0 Kenotic Core & Parser Unification).
|
|
4
|
+
Provides the pure, stateless monadic substrate for chapter-based narrative transformations:
|
|
5
|
+
- Monad Archetype
|
|
6
|
+
- Narrative Threads: Reader, State, Writer, IO, Either (Left, Right), Maybe (Just, Nothing)
|
|
7
|
+
- CLMConfiguration: Abstract (VCard/Why), Concrete (PCard/How), Balanced (MCard/What)
|
|
8
|
+
- NarrativeMonad: Composition of Reader, State, Writer, and IO
|
|
9
|
+
- Chapter: Encapsulates individual narrative transformation steps
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from abc import ABC, abstractmethod
|
|
15
|
+
from collections.abc import Callable
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from typing import Any, Generic, TypeVar
|
|
18
|
+
|
|
19
|
+
# Type Variables
|
|
20
|
+
T = TypeVar("T") # Result Type
|
|
21
|
+
U = TypeVar("U") # Transformed Result Type
|
|
22
|
+
R = TypeVar("R") # Reader Context Type
|
|
23
|
+
S = TypeVar("S") # State Type
|
|
24
|
+
W = TypeVar("W") # Writer Log Type
|
|
25
|
+
E = TypeVar("E") # Error Type
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# =============================================================================
|
|
29
|
+
# 1. The Monad Archetype
|
|
30
|
+
# =============================================================================
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Monad(Generic[T], ABC):
|
|
34
|
+
"""The Archetype of the Function.
|
|
35
|
+
Encapsulates Purity, Manages Impurity, Enables Composition.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def bind(self, func: Callable[[T], Monad[U]]) -> Monad[U]:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
def map(self, func: Callable[[T], U]) -> Monad[U]:
|
|
43
|
+
"""Standard map operation."""
|
|
44
|
+
return self.bind(lambda x: self.pure(func(x)))
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def pure(cls, value: T) -> Monad[T]:
|
|
48
|
+
return cls.unit(value)
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def unit(cls, value: T) -> Monad[T]:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# =============================================================================
|
|
57
|
+
# 2. The Narrative Threads (Specific Monads)
|
|
58
|
+
# =============================================================================
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class Reader(Monad[T], Generic[R, T]):
|
|
63
|
+
"""The Reader Monad: Carries the Cultural Context and Configuration.
|
|
64
|
+
Immutable and accessible everywhere.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
run: Callable[[R], T]
|
|
68
|
+
|
|
69
|
+
def bind(self, func: Callable[[T], Reader[R, U]]) -> Reader[R, U]:
|
|
70
|
+
return Reader(lambda r: func(self.run(r)).run(r))
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def unit(cls, value: T) -> Reader[R, T]:
|
|
74
|
+
return Reader(lambda _: value)
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def pure(cls, value: T) -> Reader[R, T]:
|
|
78
|
+
return cls.unit(value)
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def ask() -> Reader[R, R]:
|
|
82
|
+
return Reader(lambda env: env)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class State(Monad[T], Generic[S, T]):
|
|
87
|
+
"""The State Monad: Carries the evolving World State.
|
|
88
|
+
(Village Prosperity, Network Topology, User Progress).
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
run: Callable[[S], tuple[T, S]]
|
|
92
|
+
|
|
93
|
+
def bind(self, func: Callable[[T], State[S, U]]) -> State[S, U]:
|
|
94
|
+
def new_run(s: S) -> tuple[U, S]:
|
|
95
|
+
val, new_s = self.run(s)
|
|
96
|
+
return func(val).run(new_s)
|
|
97
|
+
|
|
98
|
+
return State(new_run)
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def unit(cls, value: T) -> State[S, T]:
|
|
102
|
+
return State(lambda s: (value, s))
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def pure(cls, value: T) -> State[S, T]:
|
|
106
|
+
return cls.unit(value)
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def get() -> State[S, S]:
|
|
110
|
+
return State(lambda s: (s, s))
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def put(new_state: S) -> State[S, None]:
|
|
114
|
+
return State(lambda _: (None, new_state))
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class CallableTuple(tuple):
|
|
118
|
+
"""A tuple subclass that is callable, returning itself."""
|
|
119
|
+
|
|
120
|
+
def __call__(self) -> tuple:
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class Writer(Monad[T], Generic[W, T]):
|
|
125
|
+
"""The Writer Monad: Accumulates the Log of the journey.
|
|
126
|
+
(The Story Text, The Audit Trail).
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
def __init__(self, run: Any):
|
|
130
|
+
if callable(run) and not isinstance(run, tuple):
|
|
131
|
+
res = run()
|
|
132
|
+
self.run = CallableTuple(res)
|
|
133
|
+
elif isinstance(run, tuple):
|
|
134
|
+
self.run = CallableTuple(run)
|
|
135
|
+
else:
|
|
136
|
+
self.run = CallableTuple((run, []))
|
|
137
|
+
|
|
138
|
+
def bind(self, func: Callable[[T], Writer[W, U]]) -> Writer[W, U]:
|
|
139
|
+
val, log = self.run
|
|
140
|
+
new_writer = func(val)
|
|
141
|
+
new_val, new_log = new_writer.run
|
|
142
|
+
return Writer((new_val, list(log) + list(new_log)))
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def unit(cls, value: T) -> Writer[W, T]:
|
|
146
|
+
return cls((value, []))
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def pure(cls, value: T) -> Writer[W, T]:
|
|
150
|
+
return cls((value, []))
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def tell(log: list[W]) -> Writer[W, None]:
|
|
154
|
+
return Writer((None, log))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass
|
|
158
|
+
class IO(Monad[T]):
|
|
159
|
+
"""The IO Monad: Handles Effects at the boundaries.
|
|
160
|
+
(User Interaction, System Deployment).
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
effect: Callable[[], T]
|
|
164
|
+
|
|
165
|
+
def unsafe_run(self) -> T:
|
|
166
|
+
"""Execute the side effect."""
|
|
167
|
+
return self.effect()
|
|
168
|
+
|
|
169
|
+
def __call__(self) -> T:
|
|
170
|
+
return self.effect()
|
|
171
|
+
|
|
172
|
+
def bind(self, func: Callable[[T], IO[U]]) -> IO[U]:
|
|
173
|
+
return IO(lambda: func(self.effect()).effect())
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def unit(cls, value: T) -> IO[T]:
|
|
177
|
+
return IO(lambda: value)
|
|
178
|
+
|
|
179
|
+
@classmethod
|
|
180
|
+
def pure(cls, value: T) -> IO[T]:
|
|
181
|
+
return IO(lambda: value)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class CallableBool(int):
|
|
185
|
+
"""An int subclass representing a bool that is callable, returning itself."""
|
|
186
|
+
|
|
187
|
+
def __call__(self) -> bool:
|
|
188
|
+
return bool(self)
|
|
189
|
+
|
|
190
|
+
def __bool__(self) -> bool:
|
|
191
|
+
return self != 0
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class Either(Monad[T], Generic[E, T]):
|
|
195
|
+
"""The Either Monad: Represents a value that can be one of two types.
|
|
196
|
+
Used for Error Handling (Left=Error, Right=Success) or Branching Logic.
|
|
197
|
+
Supports both Either[T] and Either[E, T] typing specializations.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
def __init__(self, value: Any, is_left: bool = False):
|
|
201
|
+
self.value = value
|
|
202
|
+
self._is_left = CallableBool(1 if is_left else 0)
|
|
203
|
+
|
|
204
|
+
def __class_getitem__(cls, item: Any) -> Any:
|
|
205
|
+
if not isinstance(item, tuple):
|
|
206
|
+
item = (Any, item)
|
|
207
|
+
return super().__class_getitem__(item)
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def is_left(self) -> CallableBool:
|
|
211
|
+
return self._is_left
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def is_right(self) -> CallableBool:
|
|
215
|
+
return CallableBool(0 if self._is_left else 1)
|
|
216
|
+
|
|
217
|
+
def bind(self, func: Callable[[T], Either[Any, U]]) -> Either[Any, U]:
|
|
218
|
+
if self._is_left:
|
|
219
|
+
return self # Propagate failure/left value
|
|
220
|
+
return func(self.value)
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def unit(cls, value: T) -> Either[Any, T]:
|
|
224
|
+
return Right(value)
|
|
225
|
+
|
|
226
|
+
@classmethod
|
|
227
|
+
def pure(cls, value: T) -> Either[Any, T]:
|
|
228
|
+
return Right(value)
|
|
229
|
+
|
|
230
|
+
@classmethod
|
|
231
|
+
def left(cls, value: Any) -> Left[Any]:
|
|
232
|
+
return Left(value)
|
|
233
|
+
|
|
234
|
+
@classmethod
|
|
235
|
+
def right(cls, value: T) -> Right[T]:
|
|
236
|
+
return Right(value)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class Left(Either[E, Any], Generic[E]):
|
|
240
|
+
def __init__(self, value: E):
|
|
241
|
+
super().__init__(value, is_left=True)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class Right(Either[Any, T], Generic[T]):
|
|
245
|
+
def __init__(self, value: T):
|
|
246
|
+
super().__init__(value, is_left=False)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class Maybe(Monad[T], Generic[T]):
|
|
250
|
+
"""The Maybe Monad: Represents an optional value (Just val or Nothing).
|
|
251
|
+
Used for handling absence of value without null checks.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
def __init__(self, value: T | None, is_nothing: bool = False):
|
|
255
|
+
self.value = value
|
|
256
|
+
self._is_nothing = CallableBool(1 if is_nothing else 0)
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def is_nothing(self) -> CallableBool:
|
|
260
|
+
return self._is_nothing
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def is_just(self) -> CallableBool:
|
|
264
|
+
return CallableBool(0 if self._is_nothing else 1)
|
|
265
|
+
|
|
266
|
+
def bind(self, func: Callable[[T], Maybe[U]]) -> Maybe[U]:
|
|
267
|
+
if self._is_nothing:
|
|
268
|
+
return self # Propagate Nothing
|
|
269
|
+
return func(self.value) # type: ignore
|
|
270
|
+
|
|
271
|
+
@classmethod
|
|
272
|
+
def unit(cls, value: T) -> Maybe[T]:
|
|
273
|
+
return Just(value)
|
|
274
|
+
|
|
275
|
+
@classmethod
|
|
276
|
+
def pure(cls, value: T) -> Maybe[T]:
|
|
277
|
+
return Just(value)
|
|
278
|
+
|
|
279
|
+
@classmethod
|
|
280
|
+
def nothing(cls) -> Nothing:
|
|
281
|
+
return Nothing()
|
|
282
|
+
|
|
283
|
+
@classmethod
|
|
284
|
+
def just(cls, value: T) -> Just:
|
|
285
|
+
return Just(value)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class Nothing(Maybe[Any]):
|
|
289
|
+
def __init__(self):
|
|
290
|
+
super().__init__(None, is_nothing=True)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class Just(Maybe[T]):
|
|
294
|
+
def __init__(self, value: T):
|
|
295
|
+
super().__init__(value, is_nothing=False)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
# =============================================================================
|
|
299
|
+
# 3. The Cubical Logic Model (CLM)
|
|
300
|
+
# =============================================================================
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@dataclass
|
|
304
|
+
class CLMConfiguration:
|
|
305
|
+
"""Defines the three orthogonal dimensions of any well-formed concept."""
|
|
306
|
+
|
|
307
|
+
abstract: str # VCard (Value/Type): The Principle ("Why")
|
|
308
|
+
concrete: str # PCard (Process/Instance): The Character ("How")
|
|
309
|
+
balanced: str # MCard (Memory/Property): The Evidence ("What")
|
|
310
|
+
|
|
311
|
+
def __repr__(self) -> str:
|
|
312
|
+
return (
|
|
313
|
+
f"🧊 CLM Cube:\n"
|
|
314
|
+
f" - Abstract (Why): {self.abstract}\n"
|
|
315
|
+
f" - Concrete (How): {self.concrete}\n"
|
|
316
|
+
f" - Balanced (What): {self.balanced}"
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
# =============================================================================
|
|
321
|
+
# 4. The Narrative Monad (Composition)
|
|
322
|
+
# =============================================================================
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
class NarrativeMonad(Generic[R, S, W, T]):
|
|
326
|
+
"""The 'Spacetime' Monad.
|
|
327
|
+
A composition of Reader, State, Writer, and IO.
|
|
328
|
+
|
|
329
|
+
Signature: Context -> State -> IO(Result, NewState, Log)
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
def __init__(self, run: Callable[[R, S], IO[tuple[T, S, list[W]]]]):
|
|
333
|
+
self.run = run
|
|
334
|
+
|
|
335
|
+
def bind(
|
|
336
|
+
self, func: Callable[[T], NarrativeMonad[R, S, W, U]]
|
|
337
|
+
) -> NarrativeMonad[R, S, W, U]:
|
|
338
|
+
def new_run(r: R, s: S) -> IO[tuple[U, S, list[W]]]:
|
|
339
|
+
# Execute first step
|
|
340
|
+
io_result = self.run(r, s)
|
|
341
|
+
|
|
342
|
+
# Define continuation inside IO to handle effects sequentially
|
|
343
|
+
def continuation() -> tuple[U, S, list[W]]:
|
|
344
|
+
val, s_prime, log1 = io_result.effect()
|
|
345
|
+
|
|
346
|
+
# Execute second step
|
|
347
|
+
next_monad = func(val)
|
|
348
|
+
io_result2 = next_monad.run(r, s_prime)
|
|
349
|
+
val2, s_double_prime, log2 = io_result2.effect()
|
|
350
|
+
|
|
351
|
+
return (val2, s_double_prime, log1 + log2)
|
|
352
|
+
|
|
353
|
+
return IO(continuation)
|
|
354
|
+
|
|
355
|
+
return NarrativeMonad(new_run)
|
|
356
|
+
|
|
357
|
+
@classmethod
|
|
358
|
+
def unit(cls, value: T) -> NarrativeMonad[R, S, W, T]:
|
|
359
|
+
return NarrativeMonad(lambda r, s: IO.unit((value, s, [])))
|
|
360
|
+
|
|
361
|
+
@classmethod
|
|
362
|
+
def lift_io(cls, io_action: IO[T]) -> NarrativeMonad[R, S, W, T]:
|
|
363
|
+
"""Lift a pure IO action into the Narrative Monad"""
|
|
364
|
+
return NarrativeMonad(lambda r, s: IO(lambda: (io_action.effect(), s, [])))
|
|
365
|
+
|
|
366
|
+
@classmethod
|
|
367
|
+
def log(cls, message: W) -> NarrativeMonad[R, S, W, None]:
|
|
368
|
+
"""Log a message (Writer effect)"""
|
|
369
|
+
return NarrativeMonad(lambda r, s: IO.unit((None, s, [message])))
|
|
370
|
+
|
|
371
|
+
@classmethod
|
|
372
|
+
def get_context(cls) -> NarrativeMonad[R, S, W, R]:
|
|
373
|
+
"""Read the context (Reader effect)"""
|
|
374
|
+
return NarrativeMonad(lambda r, s: IO.unit((r, s, [])))
|
|
375
|
+
|
|
376
|
+
@classmethod
|
|
377
|
+
def get_state(cls) -> NarrativeMonad[R, S, W, S]:
|
|
378
|
+
"""Read the state (State effect)"""
|
|
379
|
+
return NarrativeMonad(lambda r, s: IO.unit((s, s, [])))
|
|
380
|
+
|
|
381
|
+
@classmethod
|
|
382
|
+
def put_state(cls, new_state: S) -> NarrativeMonad[R, S, W, None]:
|
|
383
|
+
"""Update the state (State effect)"""
|
|
384
|
+
return NarrativeMonad(lambda r, s: IO.unit((None, new_state, [])))
|
|
385
|
+
|
|
386
|
+
def execute(self, context: R, initial_state: S) -> tuple[T, S, list[W]]:
|
|
387
|
+
"""Run the monadic chain"""
|
|
388
|
+
return self.run(context, initial_state).effect()
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
# =============================================================================
|
|
392
|
+
# 5. Chapter Template
|
|
393
|
+
# =============================================================================
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
@dataclass
|
|
397
|
+
class Chapter:
|
|
398
|
+
"""A Chapter in the Prologue of Spacetime."""
|
|
399
|
+
|
|
400
|
+
id: int
|
|
401
|
+
title: str
|
|
402
|
+
clm: CLMConfiguration
|
|
403
|
+
mvp_card: str # The Archetype Name
|
|
404
|
+
pkc_task: str # The Container Task
|
|
405
|
+
action: NarrativeMonad[dict, dict, str, Any]
|
|
406
|
+
|
|
407
|
+
def run(self, context: dict, state: dict) -> tuple[Any, dict, list[str]]:
|
|
408
|
+
print(f"\n--- Executing {self.title} ---")
|
|
409
|
+
print(self.clm)
|
|
410
|
+
result, new_state, log = self.action.execute(context, state)
|
|
411
|
+
print(f"Result: {result}")
|
|
412
|
+
print(f"Log: {log}")
|
|
413
|
+
return result, new_state, log
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
__all__ = [
|
|
417
|
+
"CLMConfiguration",
|
|
418
|
+
"Chapter",
|
|
419
|
+
"Either",
|
|
420
|
+
"IO",
|
|
421
|
+
"Just",
|
|
422
|
+
"Left",
|
|
423
|
+
"Maybe",
|
|
424
|
+
"Monad",
|
|
425
|
+
"NarrativeMonad",
|
|
426
|
+
"Nothing",
|
|
427
|
+
"Reader",
|
|
428
|
+
"Right",
|
|
429
|
+
"State",
|
|
430
|
+
"Writer",
|
|
431
|
+
]
|