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,463 @@
|
|
|
1
|
+
"""VCard Application Vocabulary - Data-driven patterns for side effects.
|
|
2
|
+
|
|
3
|
+
This module provides application-specific vocabulary for VCard following the
|
|
4
|
+
Empty Schema principle. Resource types are defined as DATA in vcard_ext/,
|
|
5
|
+
making the system fully extensible without code changes.
|
|
6
|
+
|
|
7
|
+
Usage
|
|
8
|
+
=====
|
|
9
|
+
|
|
10
|
+
from clm.layer0.model.vcard_vocabulary import Resource, ApplicationResources
|
|
11
|
+
|
|
12
|
+
# Direct factory usage (any registered type)
|
|
13
|
+
ref = Resource.create("env", "DATABASE_URL", required=True)
|
|
14
|
+
ref = Resource.create("turso", "my-database", group="us-east")
|
|
15
|
+
|
|
16
|
+
# ApplicationResources for managing multiple resources
|
|
17
|
+
resources = ApplicationResources()
|
|
18
|
+
resources.add("env", "DATABASE_URL")
|
|
19
|
+
resources.add("turso", "my-database")
|
|
20
|
+
resources.add_lgtm_stack("http://localhost", "mcard-service")
|
|
21
|
+
|
|
22
|
+
Extending with new types:
|
|
23
|
+
# Add to vcard_ext/storage.py, network.py, or observability.py
|
|
24
|
+
# Or create a new file and register in vcard_ext/__init__.py
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import os
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
from clm.layer0.model.vcard import ExternalRef
|
|
34
|
+
from clm.layer0.model.vcard_ext import RESOURCE_REGISTRY, ResourceCategory, ResourceType
|
|
35
|
+
|
|
36
|
+
# Re-export for convenience
|
|
37
|
+
__all__ = [
|
|
38
|
+
"Resource",
|
|
39
|
+
"ResourceCategory",
|
|
40
|
+
"ResourceType",
|
|
41
|
+
"RESOURCE_REGISTRY",
|
|
42
|
+
"EnvResource",
|
|
43
|
+
"FileResource",
|
|
44
|
+
"StorageResource",
|
|
45
|
+
"NetworkResource",
|
|
46
|
+
"ObservabilityResource",
|
|
47
|
+
"ApplicationResources",
|
|
48
|
+
"create_lgtm_stack",
|
|
49
|
+
"create_from_dotenv",
|
|
50
|
+
"AccessMode",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AccessMode:
|
|
55
|
+
"""Access mode constants for resources."""
|
|
56
|
+
|
|
57
|
+
READ = "read"
|
|
58
|
+
WRITE = "write"
|
|
59
|
+
READ_WRITE = "rw"
|
|
60
|
+
EXECUTE = "execute"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# =============================================================================
|
|
64
|
+
# Core Resource Factory (Single Entry Point)
|
|
65
|
+
# =============================================================================
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Resource:
|
|
69
|
+
"""Universal factory for creating ExternalRefs from registry."""
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def create(resource_type: str, *args, **kwargs) -> ExternalRef:
|
|
73
|
+
"""Create an ExternalRef for any registered resource type."""
|
|
74
|
+
if resource_type not in RESOURCE_REGISTRY:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"Unknown resource type: {resource_type}. "
|
|
77
|
+
f"Available: {list(RESOURCE_REGISTRY.keys())}"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
rtype = RESOURCE_REGISTRY[resource_type]
|
|
81
|
+
options = {**rtype.default_options, **kwargs}
|
|
82
|
+
|
|
83
|
+
# Map positional args using type's arg_names
|
|
84
|
+
template_vars = dict(options)
|
|
85
|
+
for i, arg in enumerate(args):
|
|
86
|
+
if i < len(rtype.arg_names):
|
|
87
|
+
template_vars[rtype.arg_names[i]] = arg
|
|
88
|
+
|
|
89
|
+
# Build URI
|
|
90
|
+
uri = Resource._build_uri(rtype.uri_template, template_vars)
|
|
91
|
+
|
|
92
|
+
# Compute hash
|
|
93
|
+
hash_content = Resource._build_hash_content(rtype.hash_template, template_vars)
|
|
94
|
+
content_hash = hashlib.sha256(hash_content.encode()).hexdigest()
|
|
95
|
+
|
|
96
|
+
# Determine status
|
|
97
|
+
status = Resource._determine_status(resource_type, template_vars, options)
|
|
98
|
+
|
|
99
|
+
# Build QoS metrics
|
|
100
|
+
qos_metrics = {
|
|
101
|
+
"category": (
|
|
102
|
+
rtype.category
|
|
103
|
+
if isinstance(rtype.category, str)
|
|
104
|
+
else rtype.category.value
|
|
105
|
+
),
|
|
106
|
+
**{
|
|
107
|
+
k: v
|
|
108
|
+
for k, v in template_vars.items()
|
|
109
|
+
if v is not None and not k.startswith("_")
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return ExternalRef(
|
|
114
|
+
uri=uri, content_hash=content_hash, status=status, qos_metrics=qos_metrics
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _build_uri(template: str, vars: dict) -> str:
|
|
119
|
+
preserve_uri = vars.get("_preserve_uri", False)
|
|
120
|
+
uri = template
|
|
121
|
+
for key, value in vars.items():
|
|
122
|
+
if value is not None and not key.startswith("_"):
|
|
123
|
+
str_val = str(value)
|
|
124
|
+
if not preserve_uri and key in ("endpoint", "collector_url"):
|
|
125
|
+
str_val = str_val.replace("http://", "").replace("https://", "")
|
|
126
|
+
uri = uri.replace(f"{{{key}}}", str_val)
|
|
127
|
+
return uri
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def _build_hash_content(template: str, vars: dict) -> str:
|
|
131
|
+
content = template
|
|
132
|
+
for key, value in vars.items():
|
|
133
|
+
content = content.replace(
|
|
134
|
+
f"{{{key}}}", str(value) if value is not None else "None"
|
|
135
|
+
)
|
|
136
|
+
return content
|
|
137
|
+
|
|
138
|
+
@staticmethod
|
|
139
|
+
def _determine_status(resource_type: str, vars: dict, options: dict) -> str:
|
|
140
|
+
required = options.get("required", True)
|
|
141
|
+
if resource_type == "env":
|
|
142
|
+
actual = os.environ.get(vars.get("name", ""), options.get("default"))
|
|
143
|
+
if actual is not None:
|
|
144
|
+
return "verified"
|
|
145
|
+
return "invalid" if required else "pending"
|
|
146
|
+
if resource_type in ("file", "directory", "sqlite"):
|
|
147
|
+
path = Path(vars.get("path", ""))
|
|
148
|
+
if path.exists():
|
|
149
|
+
return "verified"
|
|
150
|
+
return "invalid" if required else "pending"
|
|
151
|
+
return "pending"
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def types() -> list[str]:
|
|
155
|
+
return list(RESOURCE_REGISTRY.keys())
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def register(resource_type: ResourceType) -> None:
|
|
159
|
+
RESOURCE_REGISTRY[resource_type.name] = resource_type
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# =============================================================================
|
|
163
|
+
# Helper Functions
|
|
164
|
+
# =============================================================================
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def create_lgtm_stack(
|
|
168
|
+
base_url: str,
|
|
169
|
+
service_name: str,
|
|
170
|
+
environment: str = "production",
|
|
171
|
+
ports: dict[str, int] | None = None,
|
|
172
|
+
) -> list[ExternalRef]:
|
|
173
|
+
"""Create ExternalRefs for a complete Grafana LGTM stack."""
|
|
174
|
+
p = {
|
|
175
|
+
"grafana": 3000,
|
|
176
|
+
"prometheus": 9090,
|
|
177
|
+
"loki": 3100,
|
|
178
|
+
"tempo": 4317,
|
|
179
|
+
**(ports or {}),
|
|
180
|
+
}
|
|
181
|
+
return [
|
|
182
|
+
Resource.create(
|
|
183
|
+
"grafana",
|
|
184
|
+
f"{base_url}:{p['grafana']}",
|
|
185
|
+
description=f"Grafana for {service_name}",
|
|
186
|
+
),
|
|
187
|
+
Resource.create(
|
|
188
|
+
"prometheus", f"{base_url}:{p['prometheus']}", job_name=service_name
|
|
189
|
+
),
|
|
190
|
+
Resource.create(
|
|
191
|
+
"loki",
|
|
192
|
+
f"{base_url}:{p['loki']}",
|
|
193
|
+
labels={"service": service_name, "environment": environment},
|
|
194
|
+
),
|
|
195
|
+
Resource.create("tempo", f"{base_url}:{p['tempo']}", service_name=service_name),
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def create_from_dotenv(dotenv_path: str = ".env") -> list[ExternalRef]:
|
|
200
|
+
"""Create ExternalRefs for all variables in a .env file."""
|
|
201
|
+
refs = []
|
|
202
|
+
path = Path(dotenv_path)
|
|
203
|
+
if path.exists():
|
|
204
|
+
with open(path) as f:
|
|
205
|
+
for line in f:
|
|
206
|
+
line = line.strip()
|
|
207
|
+
if line and not line.startswith("#") and "=" in line:
|
|
208
|
+
name = line.split("=")[0].strip()
|
|
209
|
+
secret = any(
|
|
210
|
+
s in name.upper()
|
|
211
|
+
for s in ["KEY", "SECRET", "TOKEN", "PASSWORD"]
|
|
212
|
+
)
|
|
213
|
+
refs.append(Resource.create("env", name, secret=secret))
|
|
214
|
+
return refs
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# =============================================================================
|
|
218
|
+
# Convenience Wrappers (Backward Compatibility)
|
|
219
|
+
# =============================================================================
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class EnvResource:
|
|
223
|
+
@staticmethod
|
|
224
|
+
def create(
|
|
225
|
+
name: str,
|
|
226
|
+
required: bool = True,
|
|
227
|
+
secret: bool = False,
|
|
228
|
+
default: str | None = None,
|
|
229
|
+
description: str | None = None,
|
|
230
|
+
) -> ExternalRef:
|
|
231
|
+
return Resource.create(
|
|
232
|
+
"env",
|
|
233
|
+
name,
|
|
234
|
+
required=required,
|
|
235
|
+
secret=secret,
|
|
236
|
+
default=default,
|
|
237
|
+
description=description,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
@staticmethod
|
|
241
|
+
def create_from_dotenv(path: str = ".env") -> list[ExternalRef]:
|
|
242
|
+
return create_from_dotenv(path)
|
|
243
|
+
|
|
244
|
+
@staticmethod
|
|
245
|
+
def resolve(ref: ExternalRef) -> str | None:
|
|
246
|
+
if not ref.uri.startswith("env://"):
|
|
247
|
+
raise ValueError(f"Not an env:// URI: {ref.uri}")
|
|
248
|
+
return os.environ.get(ref.uri.replace("env://", ""))
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class FileResource:
|
|
252
|
+
@staticmethod
|
|
253
|
+
def create(
|
|
254
|
+
path: str,
|
|
255
|
+
mode: str = "read",
|
|
256
|
+
required: bool = True,
|
|
257
|
+
description: str | None = None,
|
|
258
|
+
) -> ExternalRef:
|
|
259
|
+
abs_path = Path(path).resolve()
|
|
260
|
+
exists = abs_path.exists()
|
|
261
|
+
if exists:
|
|
262
|
+
with open(abs_path, "rb") as f:
|
|
263
|
+
content_hash = hashlib.sha256(f.read()).hexdigest()
|
|
264
|
+
status = "verified"
|
|
265
|
+
else:
|
|
266
|
+
content_hash = hashlib.sha256(f"file:{abs_path}".encode()).hexdigest()
|
|
267
|
+
status = "invalid" if required else "pending"
|
|
268
|
+
return ExternalRef(
|
|
269
|
+
uri=f"file://{abs_path}",
|
|
270
|
+
content_hash=content_hash,
|
|
271
|
+
status=status,
|
|
272
|
+
qos_metrics={
|
|
273
|
+
"category": "file",
|
|
274
|
+
"mode": mode,
|
|
275
|
+
"required": required,
|
|
276
|
+
"exists": exists,
|
|
277
|
+
"description": description,
|
|
278
|
+
},
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
@staticmethod
|
|
282
|
+
def create_directory(
|
|
283
|
+
path: str, recursive: bool = True, description: str | None = None
|
|
284
|
+
) -> ExternalRef:
|
|
285
|
+
return Resource.create(
|
|
286
|
+
"directory",
|
|
287
|
+
path,
|
|
288
|
+
recursive=recursive,
|
|
289
|
+
description=description,
|
|
290
|
+
is_directory=True,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
@staticmethod
|
|
294
|
+
def verify(ref: ExternalRef) -> bool:
|
|
295
|
+
if not ref.uri.startswith("file://"):
|
|
296
|
+
raise ValueError(f"Not a file:// URI: {ref.uri}")
|
|
297
|
+
path = Path(ref.uri.replace("file://", "").rstrip("/"))
|
|
298
|
+
if not path.exists():
|
|
299
|
+
return False
|
|
300
|
+
with open(path, "rb") as f:
|
|
301
|
+
return hashlib.sha256(f.read()).hexdigest() == ref.content_hash
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class StorageResource:
|
|
305
|
+
@staticmethod
|
|
306
|
+
def create_sqlite(path: str, **kwargs) -> ExternalRef:
|
|
307
|
+
return Resource.create("sqlite", path, **kwargs)
|
|
308
|
+
|
|
309
|
+
@staticmethod
|
|
310
|
+
def create_postgres(connection: str, **kwargs) -> ExternalRef:
|
|
311
|
+
return Resource.create("postgres", connection, **kwargs)
|
|
312
|
+
|
|
313
|
+
@staticmethod
|
|
314
|
+
def create_s3(bucket: str, key: str, **kwargs) -> ExternalRef:
|
|
315
|
+
return Resource.create("s3", bucket, key, **kwargs)
|
|
316
|
+
|
|
317
|
+
@staticmethod
|
|
318
|
+
def create_litefs(path: str, **kwargs) -> ExternalRef:
|
|
319
|
+
return Resource.create("litefs", path, **kwargs)
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
def create_turso(database: str, **kwargs) -> ExternalRef:
|
|
323
|
+
return Resource.create("turso", database, **kwargs)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class NetworkResource:
|
|
327
|
+
@staticmethod
|
|
328
|
+
def create_api(endpoint: str, **kwargs) -> ExternalRef:
|
|
329
|
+
return Resource.create("api", endpoint, **kwargs)
|
|
330
|
+
|
|
331
|
+
@staticmethod
|
|
332
|
+
def create_webhook(endpoint: str, **kwargs) -> ExternalRef:
|
|
333
|
+
return Resource.create("webhook", endpoint, **kwargs)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class ObservabilityResource:
|
|
337
|
+
@staticmethod
|
|
338
|
+
def create_grafana(endpoint: str, **kwargs) -> ExternalRef:
|
|
339
|
+
return Resource.create("grafana", endpoint, endpoint=endpoint, **kwargs)
|
|
340
|
+
|
|
341
|
+
@staticmethod
|
|
342
|
+
def create_prometheus(endpoint: str, **kwargs) -> ExternalRef:
|
|
343
|
+
return Resource.create("prometheus", endpoint, endpoint=endpoint, **kwargs)
|
|
344
|
+
|
|
345
|
+
@staticmethod
|
|
346
|
+
def create_loki(endpoint: str, **kwargs) -> ExternalRef:
|
|
347
|
+
return Resource.create("loki", endpoint, endpoint=endpoint, **kwargs)
|
|
348
|
+
|
|
349
|
+
@staticmethod
|
|
350
|
+
def create_tempo(endpoint: str, **kwargs) -> ExternalRef:
|
|
351
|
+
return Resource.create("tempo", endpoint, endpoint=endpoint, **kwargs)
|
|
352
|
+
|
|
353
|
+
@staticmethod
|
|
354
|
+
def create_faro(collector_url: str, app_name: str, **kwargs) -> ExternalRef:
|
|
355
|
+
return Resource.create(
|
|
356
|
+
"faro", collector_url, app_name, collector_url=collector_url, **kwargs
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
@staticmethod
|
|
360
|
+
def create_otlp(endpoint: str, **kwargs) -> ExternalRef:
|
|
361
|
+
return Resource.create("otlp", endpoint, endpoint=endpoint, **kwargs)
|
|
362
|
+
|
|
363
|
+
@staticmethod
|
|
364
|
+
def create_lgtm_stack(
|
|
365
|
+
base_url: str, service_name: str, **kwargs
|
|
366
|
+
) -> list[ExternalRef]:
|
|
367
|
+
return create_lgtm_stack(base_url, service_name, **kwargs)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# =============================================================================
|
|
371
|
+
# Application Resources Manager
|
|
372
|
+
# =============================================================================
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
@dataclass
|
|
376
|
+
class ApplicationResources:
|
|
377
|
+
"""Collection of typed resources for an application."""
|
|
378
|
+
|
|
379
|
+
resources: list[ExternalRef] = field(default_factory=list)
|
|
380
|
+
|
|
381
|
+
def add(self, resource_type: str, *args, **kwargs) -> "ApplicationResources":
|
|
382
|
+
self.resources.append(Resource.create(resource_type, *args, **kwargs))
|
|
383
|
+
return self
|
|
384
|
+
|
|
385
|
+
# Convenience methods (delegate to add)
|
|
386
|
+
def add_env(self, name: str, **kwargs):
|
|
387
|
+
return self.add("env", name, **kwargs)
|
|
388
|
+
|
|
389
|
+
def add_env_from_dotenv(self, path: str = ".env"):
|
|
390
|
+
self.resources.extend(create_from_dotenv(path))
|
|
391
|
+
return self
|
|
392
|
+
|
|
393
|
+
def add_file(self, path: str, **kwargs):
|
|
394
|
+
return self.add("file", path, **kwargs)
|
|
395
|
+
|
|
396
|
+
def add_directory(self, path: str, **kwargs):
|
|
397
|
+
return self.add("directory", path, **kwargs)
|
|
398
|
+
|
|
399
|
+
def add_sqlite(self, path: str, **kwargs):
|
|
400
|
+
return self.add("sqlite", path, **kwargs)
|
|
401
|
+
|
|
402
|
+
def add_litefs(self, path: str, **kwargs):
|
|
403
|
+
return self.add("litefs", path, **kwargs)
|
|
404
|
+
|
|
405
|
+
def add_turso(self, database: str, **kwargs):
|
|
406
|
+
return self.add("turso", database, **kwargs)
|
|
407
|
+
|
|
408
|
+
def add_api(self, endpoint: str, **kwargs):
|
|
409
|
+
return self.add("api", endpoint, **kwargs)
|
|
410
|
+
|
|
411
|
+
def add_grafana(self, endpoint: str, **kwargs):
|
|
412
|
+
return self.add("grafana", endpoint, **kwargs)
|
|
413
|
+
|
|
414
|
+
def add_prometheus(self, endpoint: str, **kwargs):
|
|
415
|
+
return self.add("prometheus", endpoint, **kwargs)
|
|
416
|
+
|
|
417
|
+
def add_loki(self, endpoint: str, **kwargs):
|
|
418
|
+
return self.add("loki", endpoint, **kwargs)
|
|
419
|
+
|
|
420
|
+
def add_tempo(self, endpoint: str, **kwargs):
|
|
421
|
+
return self.add("tempo", endpoint, **kwargs)
|
|
422
|
+
|
|
423
|
+
def add_faro(self, url: str, app: str, **kwargs):
|
|
424
|
+
return self.add("faro", url, app, **kwargs)
|
|
425
|
+
|
|
426
|
+
def add_otlp(self, endpoint: str, **kwargs):
|
|
427
|
+
return self.add("otlp", endpoint, **kwargs)
|
|
428
|
+
|
|
429
|
+
def add_lgtm_stack(self, base_url: str, service_name: str, **kwargs):
|
|
430
|
+
self.resources.extend(create_lgtm_stack(base_url, service_name, **kwargs))
|
|
431
|
+
return self
|
|
432
|
+
|
|
433
|
+
def get_all(self) -> list[ExternalRef]:
|
|
434
|
+
return self.resources
|
|
435
|
+
|
|
436
|
+
def get_by_category(self, category: ResourceCategory) -> list[ExternalRef]:
|
|
437
|
+
cat = category.value if isinstance(category, ResourceCategory) else category
|
|
438
|
+
return [
|
|
439
|
+
r
|
|
440
|
+
for r in self.resources
|
|
441
|
+
if r.qos_metrics and r.qos_metrics.get("category") == cat
|
|
442
|
+
]
|
|
443
|
+
|
|
444
|
+
def get_observability_resources(self) -> list[ExternalRef]:
|
|
445
|
+
return [
|
|
446
|
+
r
|
|
447
|
+
for r in self.resources
|
|
448
|
+
if r.qos_metrics and r.qos_metrics.get("category") == "observability"
|
|
449
|
+
]
|
|
450
|
+
|
|
451
|
+
def get_invalid(self) -> list[ExternalRef]:
|
|
452
|
+
return [r for r in self.resources if r.status == "invalid"]
|
|
453
|
+
|
|
454
|
+
def validate(self) -> dict[str, Any]:
|
|
455
|
+
invalid = self.get_invalid()
|
|
456
|
+
return {
|
|
457
|
+
"valid": len(invalid) == 0,
|
|
458
|
+
"total": len(self.resources),
|
|
459
|
+
"verified": len([r for r in self.resources if r.status == "verified"]),
|
|
460
|
+
"pending": len([r for r in self.resources if r.status == "pending"]),
|
|
461
|
+
"invalid": len(invalid),
|
|
462
|
+
"invalid_resources": [r.uri for r in invalid],
|
|
463
|
+
}
|