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,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Free Variables - Analysis of free and bound variables in Lambda terms
|
|
3
|
+
|
|
4
|
+
Provides utilities for computing free variables, bound variables,
|
|
5
|
+
and checking if variables are captured during substitution.
|
|
6
|
+
|
|
7
|
+
This module provides Python parity with mcard-js/src/ptr/lambda/FreeVariables.ts
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from clm.layer2.card_collection import CardCollection
|
|
11
|
+
from clm.layer3.satori.lambda_term import (
|
|
12
|
+
is_abs,
|
|
13
|
+
is_app,
|
|
14
|
+
is_var,
|
|
15
|
+
load_term,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
# Free Variables
|
|
20
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def free_variables(collection: CardCollection, hash_value: str) -> set[str] | None:
|
|
24
|
+
"""
|
|
25
|
+
Compute the set of free variables in a Lambda term.
|
|
26
|
+
|
|
27
|
+
FV(x) = {x}
|
|
28
|
+
FV(λx.M) = FV(M) - {x}
|
|
29
|
+
FV(M N) = FV(M) ∪ FV(N)
|
|
30
|
+
"""
|
|
31
|
+
term = load_term(collection, hash_value)
|
|
32
|
+
if term is None:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
if is_var(term):
|
|
36
|
+
return {term.name}
|
|
37
|
+
|
|
38
|
+
elif is_abs(term):
|
|
39
|
+
body_fv = free_variables(collection, term.body)
|
|
40
|
+
if body_fv is None:
|
|
41
|
+
return None
|
|
42
|
+
return body_fv - {term.param}
|
|
43
|
+
|
|
44
|
+
elif is_app(term):
|
|
45
|
+
func_fv = free_variables(collection, term.func)
|
|
46
|
+
arg_fv = free_variables(collection, term.arg)
|
|
47
|
+
if func_fv is None or arg_fv is None:
|
|
48
|
+
return None
|
|
49
|
+
return func_fv | arg_fv
|
|
50
|
+
|
|
51
|
+
return set()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
55
|
+
# Bound Variables
|
|
56
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def bound_variables(collection: CardCollection, hash_value: str) -> set[str] | None:
|
|
60
|
+
"""
|
|
61
|
+
Compute the set of bound variables in a Lambda term.
|
|
62
|
+
|
|
63
|
+
BV(x) = {}
|
|
64
|
+
BV(λx.M) = BV(M) ∪ {x}
|
|
65
|
+
BV(M N) = BV(M) ∪ BV(N)
|
|
66
|
+
"""
|
|
67
|
+
term = load_term(collection, hash_value)
|
|
68
|
+
if term is None:
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
if is_var(term):
|
|
72
|
+
return set()
|
|
73
|
+
|
|
74
|
+
elif is_abs(term):
|
|
75
|
+
body_bv = bound_variables(collection, term.body)
|
|
76
|
+
if body_bv is None:
|
|
77
|
+
return None
|
|
78
|
+
return body_bv | {term.param}
|
|
79
|
+
|
|
80
|
+
elif is_app(term):
|
|
81
|
+
func_bv = bound_variables(collection, term.func)
|
|
82
|
+
arg_bv = bound_variables(collection, term.arg)
|
|
83
|
+
if func_bv is None or arg_bv is None:
|
|
84
|
+
return None
|
|
85
|
+
return func_bv | arg_bv
|
|
86
|
+
|
|
87
|
+
return set()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
91
|
+
# Variable Checks
|
|
92
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def is_free_in(
|
|
96
|
+
collection: CardCollection, var_name: str, hash_value: str
|
|
97
|
+
) -> bool | None:
|
|
98
|
+
"""Check if a variable is free in a term"""
|
|
99
|
+
fv = free_variables(collection, hash_value)
|
|
100
|
+
if fv is None:
|
|
101
|
+
return None
|
|
102
|
+
return var_name in fv
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def is_closed(collection: CardCollection, hash_value: str) -> bool | None:
|
|
106
|
+
"""Check if a term is closed (has no free variables)"""
|
|
107
|
+
fv = free_variables(collection, hash_value)
|
|
108
|
+
if fv is None:
|
|
109
|
+
return None
|
|
110
|
+
return len(fv) == 0
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
114
|
+
# Fresh Name Generation
|
|
115
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def generate_fresh(base: str, avoid: set[str]) -> str:
|
|
119
|
+
"""
|
|
120
|
+
Generate a fresh variable name that doesn't conflict with avoid set.
|
|
121
|
+
Uses primes: x, x', x'', x'''...
|
|
122
|
+
"""
|
|
123
|
+
candidate = base
|
|
124
|
+
while candidate in avoid:
|
|
125
|
+
candidate = candidate + "'"
|
|
126
|
+
return candidate
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def generate_fresh_for(collection: CardCollection, base: str, *hashes: str) -> str:
|
|
130
|
+
"""
|
|
131
|
+
Generate a fresh variable name avoiding all variables in the given terms.
|
|
132
|
+
"""
|
|
133
|
+
avoid: set[str] = set()
|
|
134
|
+
|
|
135
|
+
for h in hashes:
|
|
136
|
+
fv = free_variables(collection, h)
|
|
137
|
+
if fv:
|
|
138
|
+
avoid |= fv
|
|
139
|
+
bv = bound_variables(collection, h)
|
|
140
|
+
if bv:
|
|
141
|
+
avoid |= bv
|
|
142
|
+
|
|
143
|
+
return generate_fresh(base, avoid)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
147
|
+
# Set Operations
|
|
148
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def set_difference(a: set[str], b: set[str]) -> set[str]:
|
|
152
|
+
"""Set difference: A - B"""
|
|
153
|
+
return a - b
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def set_intersection(a: set[str], b: set[str]) -> set[str]:
|
|
157
|
+
"""Set intersection: A ∩ B"""
|
|
158
|
+
return a & b
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def set_union(a: set[str], b: set[str]) -> set[str]:
|
|
162
|
+
"""Set union: A ∪ B"""
|
|
163
|
+
return a | b
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IO Effects - Observable side-effects for Lambda Calculus computations
|
|
3
|
+
|
|
4
|
+
Implements the IO Monad pattern for CLM:
|
|
5
|
+
IO a = World → (a, World')
|
|
6
|
+
|
|
7
|
+
In our context:
|
|
8
|
+
Reduction = TermHash → (TermHash', IOEffects)
|
|
9
|
+
|
|
10
|
+
IO effects are purely observational - they do not affect computation results.
|
|
11
|
+
The same input will always produce the same output regardless of IO configuration.
|
|
12
|
+
|
|
13
|
+
This module provides Python parity with mcard-js/src/ptr/lambda/IOEffects.ts
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from typing import Any, Literal
|
|
20
|
+
|
|
21
|
+
import requests
|
|
22
|
+
|
|
23
|
+
from clm.cross_cutting.config.config_constants import URL_FETCH_TIMEOUT_SECS
|
|
24
|
+
|
|
25
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
# Types
|
|
27
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
IOFormat = Literal["minimal", "verbose", "json"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class NetworkConfig:
|
|
34
|
+
"""Network output configuration."""
|
|
35
|
+
|
|
36
|
+
enabled: bool = False
|
|
37
|
+
endpoint: str | None = None
|
|
38
|
+
method: str = "POST"
|
|
39
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class IOEffectsConfig:
|
|
44
|
+
"""Configuration for IO effects."""
|
|
45
|
+
|
|
46
|
+
enabled: bool = False
|
|
47
|
+
console: bool = True
|
|
48
|
+
network: NetworkConfig | None = None
|
|
49
|
+
on_step: bool = False
|
|
50
|
+
on_complete: bool = True
|
|
51
|
+
on_error: bool = True
|
|
52
|
+
format: IOFormat = "minimal"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class StepEvent:
|
|
57
|
+
"""Event emitted after each reduction step."""
|
|
58
|
+
|
|
59
|
+
type: str = "step"
|
|
60
|
+
step_number: int = 0
|
|
61
|
+
term_hash: str = ""
|
|
62
|
+
pretty_print: str = ""
|
|
63
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class CompleteEvent:
|
|
68
|
+
"""Event emitted when normalization completes."""
|
|
69
|
+
|
|
70
|
+
type: str = "complete"
|
|
71
|
+
normal_form: str = ""
|
|
72
|
+
pretty_print: str = ""
|
|
73
|
+
total_steps: int = 0
|
|
74
|
+
reduction_path: list[str] = field(default_factory=list)
|
|
75
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class ErrorEvent:
|
|
80
|
+
"""Event emitted on execution failure."""
|
|
81
|
+
|
|
82
|
+
type: str = "error"
|
|
83
|
+
message: str = ""
|
|
84
|
+
partial_steps: int = 0
|
|
85
|
+
last_term_hash: str | None = None
|
|
86
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
IOEvent = StepEvent | CompleteEvent | ErrorEvent
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
93
|
+
# ANSI Colors
|
|
94
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Colors:
|
|
98
|
+
CYAN = "\033[36m"
|
|
99
|
+
RED = "\033[31m"
|
|
100
|
+
RESET = "\033[0m"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
104
|
+
# IO Effects Handler
|
|
105
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class IOEffectsHandler:
|
|
109
|
+
"""
|
|
110
|
+
Handler for IO effects during lambda computation.
|
|
111
|
+
|
|
112
|
+
Dispatches events to configured outputs (console, network, file).
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
def __init__(self, config: IOEffectsConfig | None = None):
|
|
116
|
+
self.config = config or IOEffectsConfig()
|
|
117
|
+
self.events: list[IOEvent] = []
|
|
118
|
+
|
|
119
|
+
def is_enabled(self) -> bool:
|
|
120
|
+
"""Check if IO effects are enabled."""
|
|
121
|
+
return self.config.enabled
|
|
122
|
+
|
|
123
|
+
def emit_step(self, step_number: int, term_hash: str, pretty_print: str) -> None:
|
|
124
|
+
"""Emit a step event."""
|
|
125
|
+
if not self.config.enabled or not self.config.on_step:
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
event = StepEvent(
|
|
129
|
+
step_number=step_number,
|
|
130
|
+
term_hash=term_hash,
|
|
131
|
+
pretty_print=pretty_print,
|
|
132
|
+
timestamp=datetime.now(),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
self.events.append(event)
|
|
136
|
+
self._dispatch(event)
|
|
137
|
+
|
|
138
|
+
def emit_complete(
|
|
139
|
+
self,
|
|
140
|
+
normal_form: str,
|
|
141
|
+
pretty_print: str,
|
|
142
|
+
total_steps: int,
|
|
143
|
+
reduction_path: list[str],
|
|
144
|
+
) -> None:
|
|
145
|
+
"""Emit a completion event."""
|
|
146
|
+
if not self.config.enabled or not self.config.on_complete:
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
event = CompleteEvent(
|
|
150
|
+
normal_form=normal_form,
|
|
151
|
+
pretty_print=pretty_print,
|
|
152
|
+
total_steps=total_steps,
|
|
153
|
+
reduction_path=reduction_path,
|
|
154
|
+
timestamp=datetime.now(),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
self.events.append(event)
|
|
158
|
+
self._dispatch(event)
|
|
159
|
+
|
|
160
|
+
def emit_error(
|
|
161
|
+
self, message: str, partial_steps: int, last_term_hash: str | None = None
|
|
162
|
+
) -> None:
|
|
163
|
+
"""Emit an error event."""
|
|
164
|
+
if not self.config.enabled or not self.config.on_error:
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
event = ErrorEvent(
|
|
168
|
+
message=message,
|
|
169
|
+
partial_steps=partial_steps,
|
|
170
|
+
last_term_hash=last_term_hash,
|
|
171
|
+
timestamp=datetime.now(),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
self.events.append(event)
|
|
175
|
+
self._dispatch(event)
|
|
176
|
+
|
|
177
|
+
def get_events(self) -> list[IOEvent]:
|
|
178
|
+
"""Get all collected events."""
|
|
179
|
+
return list(self.events)
|
|
180
|
+
|
|
181
|
+
def clear_events(self) -> None:
|
|
182
|
+
"""Clear collected events."""
|
|
183
|
+
self.events = []
|
|
184
|
+
|
|
185
|
+
def _dispatch(self, event: IOEvent) -> None:
|
|
186
|
+
"""Dispatch event to configured outputs."""
|
|
187
|
+
formatted = self._format(event)
|
|
188
|
+
|
|
189
|
+
# Console output
|
|
190
|
+
if self.config.console:
|
|
191
|
+
self._log_to_console(event, formatted)
|
|
192
|
+
|
|
193
|
+
# Network output
|
|
194
|
+
if self.config.network and self.config.network.enabled:
|
|
195
|
+
self._send_to_network(event, formatted)
|
|
196
|
+
|
|
197
|
+
def _format(self, event: IOEvent) -> str:
|
|
198
|
+
"""Format event for output."""
|
|
199
|
+
if self.config.format == "json":
|
|
200
|
+
return self._format_json(event)
|
|
201
|
+
elif self.config.format == "verbose":
|
|
202
|
+
return self._format_verbose(event)
|
|
203
|
+
else:
|
|
204
|
+
return self._format_minimal(event)
|
|
205
|
+
|
|
206
|
+
def _format_minimal(self, event: IOEvent) -> str:
|
|
207
|
+
"""Format event as single line."""
|
|
208
|
+
if isinstance(event, StepEvent):
|
|
209
|
+
return f"[IO:step {event.step_number}] {event.pretty_print}"
|
|
210
|
+
elif isinstance(event, CompleteEvent):
|
|
211
|
+
return f"[IO:complete] {event.total_steps} steps → {event.pretty_print}"
|
|
212
|
+
elif isinstance(event, ErrorEvent):
|
|
213
|
+
return f"[IO:error] {event.message}"
|
|
214
|
+
return ""
|
|
215
|
+
|
|
216
|
+
def _format_verbose(self, event: IOEvent) -> str:
|
|
217
|
+
"""Format event with full context."""
|
|
218
|
+
ts = event.timestamp.isoformat()
|
|
219
|
+
|
|
220
|
+
if isinstance(event, StepEvent):
|
|
221
|
+
return "\n".join(
|
|
222
|
+
[
|
|
223
|
+
f"┌─ IO Effect: Reduction Step {event.step_number}",
|
|
224
|
+
f"│ Time: {ts}",
|
|
225
|
+
f"│ Hash: {event.term_hash[:16]}...",
|
|
226
|
+
f"│ Term: {event.pretty_print}",
|
|
227
|
+
"└─────────────────────────────────────────",
|
|
228
|
+
]
|
|
229
|
+
)
|
|
230
|
+
elif isinstance(event, CompleteEvent):
|
|
231
|
+
return "\n".join(
|
|
232
|
+
[
|
|
233
|
+
"╔═══════════════════════════════════════════",
|
|
234
|
+
"║ IO Effect: Normalization Complete",
|
|
235
|
+
"╠═══════════════════════════════════════════",
|
|
236
|
+
f"║ Time: {ts}",
|
|
237
|
+
f"║ Total Steps: {event.total_steps}",
|
|
238
|
+
f"║ Normal Form: {event.pretty_print}",
|
|
239
|
+
f"║ Hash: {event.normal_form[:16]}...",
|
|
240
|
+
"╚═══════════════════════════════════════════",
|
|
241
|
+
]
|
|
242
|
+
)
|
|
243
|
+
elif isinstance(event, ErrorEvent):
|
|
244
|
+
return "\n".join(
|
|
245
|
+
[
|
|
246
|
+
"╔═══════════════════════════════════════════",
|
|
247
|
+
"║ IO Effect: ERROR",
|
|
248
|
+
"╠═══════════════════════════════════════════",
|
|
249
|
+
f"║ Time: {ts}",
|
|
250
|
+
f"║ Message: {event.message}",
|
|
251
|
+
f"║ Partial Steps: {event.partial_steps}",
|
|
252
|
+
"╚═══════════════════════════════════════════",
|
|
253
|
+
]
|
|
254
|
+
)
|
|
255
|
+
return ""
|
|
256
|
+
|
|
257
|
+
def _format_json(self, event: IOEvent) -> str:
|
|
258
|
+
"""Format event as JSON."""
|
|
259
|
+
data = {"type": event.type, "timestamp": event.timestamp.isoformat()}
|
|
260
|
+
|
|
261
|
+
if isinstance(event, StepEvent):
|
|
262
|
+
data.update(
|
|
263
|
+
{
|
|
264
|
+
"stepNumber": event.step_number,
|
|
265
|
+
"termHash": event.term_hash,
|
|
266
|
+
"prettyPrint": event.pretty_print,
|
|
267
|
+
}
|
|
268
|
+
)
|
|
269
|
+
elif isinstance(event, CompleteEvent):
|
|
270
|
+
data.update(
|
|
271
|
+
{
|
|
272
|
+
"normalForm": event.normal_form,
|
|
273
|
+
"prettyPrint": event.pretty_print,
|
|
274
|
+
"totalSteps": event.total_steps,
|
|
275
|
+
"reductionPath": event.reduction_path,
|
|
276
|
+
}
|
|
277
|
+
)
|
|
278
|
+
elif isinstance(event, ErrorEvent):
|
|
279
|
+
data.update(
|
|
280
|
+
{
|
|
281
|
+
"message": event.message,
|
|
282
|
+
"partialSteps": event.partial_steps,
|
|
283
|
+
"lastTermHash": event.last_term_hash,
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
return json.dumps(data)
|
|
288
|
+
|
|
289
|
+
def _log_to_console(self, event: IOEvent, formatted: str) -> None:
|
|
290
|
+
"""Log event to console with color."""
|
|
291
|
+
if isinstance(event, ErrorEvent):
|
|
292
|
+
print(f"{Colors.RED}{formatted}{Colors.RESET}")
|
|
293
|
+
else:
|
|
294
|
+
print(f"{Colors.CYAN}{formatted}{Colors.RESET}")
|
|
295
|
+
|
|
296
|
+
def _send_to_network(self, event: IOEvent, formatted: str) -> None:
|
|
297
|
+
"""Send event to network endpoint."""
|
|
298
|
+
if not self.config.network or not self.config.network.endpoint:
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
payload = {
|
|
303
|
+
"event": event.type,
|
|
304
|
+
"data": self._format_json(event),
|
|
305
|
+
"formatted": formatted,
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
response = requests.request(
|
|
309
|
+
method=self.config.network.method,
|
|
310
|
+
url=self.config.network.endpoint,
|
|
311
|
+
headers={
|
|
312
|
+
"Content-Type": "application/json",
|
|
313
|
+
**self.config.network.headers,
|
|
314
|
+
},
|
|
315
|
+
json=payload,
|
|
316
|
+
timeout=URL_FETCH_TIMEOUT_SECS,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
if not response.ok:
|
|
320
|
+
print(f"IO network effect failed: {response.status_code}")
|
|
321
|
+
except (OSError, ConnectionError, ValueError) as e:
|
|
322
|
+
print(f"IO network effect error: {e}")
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
326
|
+
# Factory and Utilities
|
|
327
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def create_io_handler(config: dict[str, Any] | None = None) -> IOEffectsHandler:
|
|
331
|
+
"""
|
|
332
|
+
Create an IO effects handler from CLM config.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
config: Dictionary containing io_effects configuration
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
IOEffectsHandler configured based on the input
|
|
339
|
+
"""
|
|
340
|
+
if not config or not config.get("io_effects"):
|
|
341
|
+
return IOEffectsHandler(IOEffectsConfig(enabled=False))
|
|
342
|
+
|
|
343
|
+
io_config = config["io_effects"]
|
|
344
|
+
|
|
345
|
+
# Parse network config if present
|
|
346
|
+
network = None
|
|
347
|
+
if io_config.get("network"):
|
|
348
|
+
net_cfg = io_config["network"]
|
|
349
|
+
network = NetworkConfig(
|
|
350
|
+
enabled=net_cfg.get("enabled", False),
|
|
351
|
+
endpoint=net_cfg.get("endpoint"),
|
|
352
|
+
method=net_cfg.get("method", "POST"),
|
|
353
|
+
headers=net_cfg.get("headers", {}),
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
return IOEffectsHandler(
|
|
357
|
+
IOEffectsConfig(
|
|
358
|
+
enabled=io_config.get("enabled", False),
|
|
359
|
+
console=io_config.get("console", True),
|
|
360
|
+
network=network,
|
|
361
|
+
on_step=io_config.get("on_step", io_config.get("onStep", False)),
|
|
362
|
+
on_complete=io_config.get("on_complete", io_config.get("onComplete", True)),
|
|
363
|
+
on_error=io_config.get("on_error", io_config.get("onError", True)),
|
|
364
|
+
format=io_config.get("format", "minimal"),
|
|
365
|
+
)
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
# No-op handler for when IO effects are disabled
|
|
370
|
+
noop_io_handler = IOEffectsHandler(IOEffectsConfig(enabled=False))
|