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.
Files changed (333) hide show
  1. clm/__init__.py +230 -0
  2. clm/__main__.py +25 -0
  3. clm/cross_cutting/__init__.py +50 -0
  4. clm/cross_cutting/_test_runner.py +36 -0
  5. clm/cross_cutting/async_utils.py +25 -0
  6. clm/cross_cutting/cel_builder.py +123 -0
  7. clm/cross_cutting/cel_converter.py +697 -0
  8. clm/cross_cutting/cel_engine.py +252 -0
  9. clm/cross_cutting/cel_ops.py +72 -0
  10. clm/cross_cutting/cel_ops_db.py +157 -0
  11. clm/cross_cutting/cel_ops_files.py +99 -0
  12. clm/cross_cutting/cel_ops_state.py +71 -0
  13. clm/cross_cutting/cel_ops_strings.py +64 -0
  14. clm/cross_cutting/cel_resolver.py +63 -0
  15. clm/cross_cutting/cel_slices.py +56 -0
  16. clm/cross_cutting/cel_store.py +330 -0
  17. clm/cross_cutting/clm_logger.py +140 -0
  18. clm/cross_cutting/config/__init__.py +155 -0
  19. clm/cross_cutting/config/config_constants.py +392 -0
  20. clm/cross_cutting/config/env_parameters.py +90 -0
  21. clm/cross_cutting/config/logging.py +378 -0
  22. clm/cross_cutting/config/settings.py +504 -0
  23. clm/cross_cutting/context.py +5 -0
  24. clm/cross_cutting/domain_models.py +55 -0
  25. clm/cross_cutting/domain_types.py +61 -0
  26. clm/cross_cutting/effects.py +67 -0
  27. clm/cross_cutting/errors.py +153 -0
  28. clm/cross_cutting/governance.py +331 -0
  29. clm/cross_cutting/guards.py +41 -0
  30. clm/cross_cutting/mcard.py +321 -0
  31. clm/cross_cutting/native.py +185 -0
  32. clm/cross_cutting/observability.py +289 -0
  33. clm/cross_cutting/pcard.py +434 -0
  34. clm/cross_cutting/registry.py +71 -0
  35. clm/cross_cutting/resources.py +84 -0
  36. clm/cross_cutting/secrets.py +44 -0
  37. clm/cross_cutting/subprocess_utils.py +118 -0
  38. clm/cross_cutting/telemetry.py +164 -0
  39. clm/cross_cutting/testing/__init__.py +8 -0
  40. clm/cross_cutting/testing/comparator.py +187 -0
  41. clm/cross_cutting/testing/datasets.py +87 -0
  42. clm/cross_cutting/timeutil.py +20 -0
  43. clm/cross_cutting/types/new_type_check.py +0 -0
  44. clm/cross_cutting/utils/__init__.py +1 -0
  45. clm/cross_cutting/utils/url_safety.py +50 -0
  46. clm/cross_cutting/vcard.py +560 -0
  47. clm/cross_cutting/writer.py +118 -0
  48. clm/layer0/README.md +50 -0
  49. clm/layer0/__init__.py +223 -0
  50. clm/layer0/algebra.py +60 -0
  51. clm/layer0/algebra_baldwin.py +133 -0
  52. clm/layer0/algebra_baldwin_generators.py +36 -0
  53. clm/layer0/algebra_equivalence.py +262 -0
  54. clm/layer0/algebra_ingest.py +306 -0
  55. clm/layer0/algebra_manifest_io.py +156 -0
  56. clm/layer0/assembler.py +481 -0
  57. clm/layer0/cel/__init__.py +23 -0
  58. clm/layer0/cel_eval.py +491 -0
  59. clm/layer0/codec.py +106 -0
  60. clm/layer0/context.py +122 -0
  61. clm/layer0/db.py +160 -0
  62. clm/layer0/exceptions.py +73 -0
  63. clm/layer0/fibration.py +106 -0
  64. clm/layer0/gates.py +50 -0
  65. clm/layer0/hash.py +80 -0
  66. clm/layer0/loader.py +826 -0
  67. clm/layer0/math_utils.py +163 -0
  68. clm/layer0/mcard.py +96 -0
  69. clm/layer0/model/__init__.py +0 -0
  70. clm/layer0/model/action.py +223 -0
  71. clm/layer0/model/card.py +280 -0
  72. clm/layer0/model/card_triad.py +427 -0
  73. clm/layer0/model/compatibility.py +60 -0
  74. clm/layer0/model/dictionary.py +46 -0
  75. clm/layer0/model/dots.py +331 -0
  76. clm/layer0/model/event_producer.py +88 -0
  77. clm/layer0/model/g_time.py +89 -0
  78. clm/layer0/model/handle.py +163 -0
  79. clm/layer0/model/hash/__init__.py +6 -0
  80. clm/layer0/model/hash/algorithms/__init__.py +3 -0
  81. clm/layer0/model/hash/algorithms/custom_hash.py +7 -0
  82. clm/layer0/model/hash/algorithms/local_sha256.py +178 -0
  83. clm/layer0/model/hash/constants.py +53 -0
  84. clm/layer0/model/hash/enums.py +60 -0
  85. clm/layer0/model/hash/validator.py +307 -0
  86. clm/layer0/model/interpreter.py +275 -0
  87. clm/layer0/model/pagination.py +45 -0
  88. clm/layer0/model/pcard.py +716 -0
  89. clm/layer0/model/schema.py +44 -0
  90. clm/layer0/model/utils/__init__.py +1 -0
  91. clm/layer0/model/utils/content_analyzer.py +152 -0
  92. clm/layer0/model/validators/__init__.py +1 -0
  93. clm/layer0/model/validators/base_validator.py +44 -0
  94. clm/layer0/model/validators/binary_validator.py +66 -0
  95. clm/layer0/model/validators/text_validator.py +82 -0
  96. clm/layer0/model/validators/validation_registry.py +49 -0
  97. clm/layer0/model/vcard.py +823 -0
  98. clm/layer0/model/vcard_ext/__init__.py +39 -0
  99. clm/layer0/model/vcard_ext/core.py +60 -0
  100. clm/layer0/model/vcard_ext/network.py +23 -0
  101. clm/layer0/model/vcard_ext/observability.py +63 -0
  102. clm/layer0/model/vcard_ext/storage.py +60 -0
  103. clm/layer0/model/vcard_ext/vendors.py +211 -0
  104. clm/layer0/model/vcard_sandwich.py +119 -0
  105. clm/layer0/model/vcard_vocabulary.py +463 -0
  106. clm/layer0/model/workflow.py +274 -0
  107. clm/layer0/narrative.py +431 -0
  108. clm/layer0/ontology/collection.py +125 -0
  109. clm/layer0/parser.py +429 -0
  110. clm/layer0/relations.py +73 -0
  111. clm/layer0/rule_evaluator.py +251 -0
  112. clm/layer0/schema/__init__.py +38 -0
  113. clm/layer0/schema/ddl.py +97 -0
  114. clm/layer0/templates/abstract.yaml +99 -0
  115. clm/layer0/templates/balanced.yaml +253 -0
  116. clm/layer0/templates/concrete.yaml +172 -0
  117. clm/layer0/type_registry.py +223 -0
  118. clm/layer0/types.py +204 -0
  119. clm/layer0/utils.py +886 -0
  120. clm/layer0/verifier_core.py +262 -0
  121. clm/layer1/__init__.py +31 -0
  122. clm/layer1/action_dispatcher.py +275 -0
  123. clm/layer1/adapter_registry.py +233 -0
  124. clm/layer1/algebra_baldwin_generators.py +192 -0
  125. clm/layer1/arrow_compiler.py +525 -0
  126. clm/layer1/batch_processor.py +152 -0
  127. clm/layer1/cel/__init__.py +27 -0
  128. clm/layer1/cel/builder.py +173 -0
  129. clm/layer1/engine.py +759 -0
  130. clm/layer1/engine_async.py +89 -0
  131. clm/layer1/eval_callback.py +49 -0
  132. clm/layer1/gate_composer.py +229 -0
  133. clm/layer1/hoare_sandwich.py +298 -0
  134. clm/layer1/identity_guard.py +348 -0
  135. clm/layer1/matrix.py +138 -0
  136. clm/layer1/neural_runtime.py +221 -0
  137. clm/layer1/operations/__init__.py +123 -0
  138. clm/layer1/operations/augment.py +156 -0
  139. clm/layer1/operations/base.py +109 -0
  140. clm/layer1/operations/builtins.py +338 -0
  141. clm/layer1/operations/dispatch.py +144 -0
  142. clm/layer1/operations/exclude.py +107 -0
  143. clm/layer1/operations/handle.py +197 -0
  144. clm/layer1/operations/identity.py +139 -0
  145. clm/layer1/operations/invert.py +66 -0
  146. clm/layer1/operations/loader.py +116 -0
  147. clm/layer1/operations/manipulators/__init__.py +23 -0
  148. clm/layer1/operations/manipulators/document.py +211 -0
  149. clm/layer1/operations/manipulators/media.py +134 -0
  150. clm/layer1/operations/manipulators/structured.py +159 -0
  151. clm/layer1/operations/manipulators/tabular.py +334 -0
  152. clm/layer1/operations/port.py +65 -0
  153. clm/layer1/operations/services.py +529 -0
  154. clm/layer1/operations/split.py +117 -0
  155. clm/layer1/operations/substitute.py +75 -0
  156. clm/layer1/petri.py +1052 -0
  157. clm/layer1/pocketflow_orchestrator.py +408 -0
  158. clm/layer1/pocketflow_scheduler.py +519 -0
  159. clm/layer1/protocols.py +197 -0
  160. clm/layer1/runtime_adapter.py +718 -0
  161. clm/layer1/runtimes/__init__.py +71 -0
  162. clm/layer1/runtimes/_generated_assertions.py +125 -0
  163. clm/layer1/runtimes/_generated_types.py +42 -0
  164. clm/layer1/runtimes/base.py +265 -0
  165. clm/layer1/runtimes/binary.py +101 -0
  166. clm/layer1/runtimes/factory.py +203 -0
  167. clm/layer1/runtimes/javascript.py +321 -0
  168. clm/layer1/runtimes/javascript_runtime.js +33 -0
  169. clm/layer1/runtimes/js_scripts/isomorphism.mjs +22 -0
  170. clm/layer1/runtimes/js_scripts/loader.mjs +52 -0
  171. clm/layer1/runtimes/lambda_calc.py +126 -0
  172. clm/layer1/runtimes/python.py +288 -0
  173. clm/layer1/runtimes/python_runtime.py +41 -0
  174. clm/layer1/runtimes/script.py +114 -0
  175. clm/layer1/sandbox.py +334 -0
  176. clm/layer1/savepoint_guard.py +136 -0
  177. clm/layer1/sparse_net.py +122 -0
  178. clm/layer1/spec_tester.py +165 -0
  179. clm/layer1/store.py +295 -0
  180. clm/layer1/test_runner.py +498 -0
  181. clm/layer1/type_inspector.py +166 -0
  182. clm/layer1/verifier.py +772 -0
  183. clm/layer1/vm.py +91 -0
  184. clm/layer2/__init__.py +78 -0
  185. clm/layer2/card_collection.py +678 -0
  186. clm/layer2/classifier.py +479 -0
  187. clm/layer2/engine/__init__.py +1 -0
  188. clm/layer2/engine/abstract_sql_engine.py +320 -0
  189. clm/layer2/engine/base.py +193 -0
  190. clm/layer2/engine/duckdb_engine.py +576 -0
  191. clm/layer2/engine/sqlite_engine.py +518 -0
  192. clm/layer2/fiber/__init__.py +31 -0
  193. clm/layer2/fiber/context.py +89 -0
  194. clm/layer2/fiber/disposable.py +163 -0
  195. clm/layer2/fiber/fiber.py +125 -0
  196. clm/layer2/fiber/hoare.py +191 -0
  197. clm/layer2/file_io.py +376 -0
  198. clm/layer2/file_registrar.py +77 -0
  199. clm/layer2/ingest.py +202 -0
  200. clm/layer2/lineage.py +322 -0
  201. clm/layer2/mcard_fs.py +262 -0
  202. clm/layer2/merkle.py +11 -0
  203. clm/layer2/mime.py +244 -0
  204. clm/layer2/mime_detector.py +245 -0
  205. clm/layer2/private_collection.py +118 -0
  206. clm/layer2/shared_store.py +11 -0
  207. clm/layer2/storage/__init__.py +190 -0
  208. clm/layer2/storage/db_bitemporal.py +141 -0
  209. clm/layer2/storage/db_driver.py +331 -0
  210. clm/layer2/storage/db_hyperlinks.py +214 -0
  211. clm/layer2/storage/db_pool.py +139 -0
  212. clm/layer2/storage/db_repository.py +229 -0
  213. clm/layer2/storage/savepoint.py +78 -0
  214. clm/layer2/storage/sqlite_wal.py +74 -0
  215. clm/layer2/tridb.py +245 -0
  216. clm/layer2/vcard_receipt.py +71 -0
  217. clm/layer2/vfs.py +17 -0
  218. clm/layer3/__init__.py +141 -0
  219. clm/layer3/agency/__init__.py +19 -0
  220. clm/layer3/agency/config.py +220 -0
  221. clm/layer3/agency/providers/__init__.py +15 -0
  222. clm/layer3/agency/providers/base.py +91 -0
  223. clm/layer3/agency/providers/mlc_llm.py +169 -0
  224. clm/layer3/agency/providers/ollama.py +203 -0
  225. clm/layer3/agency/router.py +71 -0
  226. clm/layer3/agency/runtime.py +524 -0
  227. clm/layer3/gateway.py +353 -0
  228. clm/layer3/open_interpreter_adapter.py +159 -0
  229. clm/layer3/rag/__init__.py +70 -0
  230. clm/layer3/rag/cli.py +228 -0
  231. clm/layer3/rag/config.py +153 -0
  232. clm/layer3/rag/embeddings/__init__.py +16 -0
  233. clm/layer3/rag/embeddings/base.py +79 -0
  234. clm/layer3/rag/embeddings/ollama.py +211 -0
  235. clm/layer3/rag/embeddings/vision.py +290 -0
  236. clm/layer3/rag/engine.py +276 -0
  237. clm/layer3/rag/graph/__init__.py +22 -0
  238. clm/layer3/rag/graph/community.py +198 -0
  239. clm/layer3/rag/graph/engine.py +433 -0
  240. clm/layer3/rag/graph/extractor.py +255 -0
  241. clm/layer3/rag/graph/schema.py +115 -0
  242. clm/layer3/rag/graph/store.py +641 -0
  243. clm/layer3/rag/indexer.py +333 -0
  244. clm/layer3/rag/llm_providers.json +52 -0
  245. clm/layer3/rag/semantic_versioning.py +313 -0
  246. clm/layer3/rag/vector/__init__.py +28 -0
  247. clm/layer3/rag/vector/handle_vector_store.py +749 -0
  248. clm/layer3/rag/vector/schema.py +151 -0
  249. clm/layer3/rag/vector/store.py +632 -0
  250. clm/layer3/reticulum/__init__.py +55 -0
  251. clm/layer3/reticulum/bridge.py +74 -0
  252. clm/layer3/reticulum/daemon.py +88 -0
  253. clm/layer3/reticulum/discovery.py +164 -0
  254. clm/layer3/reticulum/identity.py +107 -0
  255. clm/layer3/reticulum/media.py +148 -0
  256. clm/layer3/reticulum/transport.py +133 -0
  257. clm/layer3/satori/__init__.py +209 -0
  258. clm/layer3/satori/alpha_conversion.py +168 -0
  259. clm/layer3/satori/beta_reduction.py +359 -0
  260. clm/layer3/satori/continuation.py +38 -0
  261. clm/layer3/satori/eta_conversion.py +205 -0
  262. clm/layer3/satori/free_variables.py +163 -0
  263. clm/layer3/satori/io_effects.py +370 -0
  264. clm/layer3/satori/lambda_runtime.py +726 -0
  265. clm/layer3/satori/lambda_term.py +230 -0
  266. clm/layer3/satori/sheaf_audit.py +261 -0
  267. clm/layer3/satori/speech_act.py +279 -0
  268. clm/layer4/README.md +9 -0
  269. clm/layer4/__init__.py +67 -0
  270. clm/layer4/_sovereign_executor.py +580 -0
  271. clm/layer4/action_commands.py +719 -0
  272. clm/layer4/api.py +236 -0
  273. clm/layer4/bootstrap/__init__.py +13 -0
  274. clm/layer4/bootstrap/bootstrapper.py +118 -0
  275. clm/layer4/bootstrap/cleanup.py +44 -0
  276. clm/layer4/bootstrap/clm_hooks.py +49 -0
  277. clm/layer4/bootstrap/genesis.py +77 -0
  278. clm/layer4/bootstrap/injection.py +125 -0
  279. clm/layer4/cli.py +549 -0
  280. clm/layer4/collection_manager.py +553 -0
  281. clm/layer4/commands/__init__.py +81 -0
  282. clm/layer4/commands/action_cmd.py +42 -0
  283. clm/layer4/commands/check_cmd.py +82 -0
  284. clm/layer4/commands/envelope.py +73 -0
  285. clm/layer4/commands/evaluate_cmd.py +224 -0
  286. clm/layer4/commands/mcard_cmd.py +24 -0
  287. clm/layer4/commands/petri_cmd.py +92 -0
  288. clm/layer4/commands/tridb_cmd.py +319 -0
  289. clm/layer4/commands/version_cmd.py +27 -0
  290. clm/layer4/diff_commands.py +167 -0
  291. clm/layer4/eoa_sponsor.py +232 -0
  292. clm/layer4/events.py +69 -0
  293. clm/layer4/flux_gateway.py +133 -0
  294. clm/layer4/gateway.py +91 -0
  295. clm/layer4/loader.py +307 -0
  296. clm/layer4/mcard_commands.py +166 -0
  297. clm/layer4/membrane.py +273 -0
  298. clm/layer4/navigation.py +50 -0
  299. clm/layer4/protocol/protocol_entry_python.py +108 -0
  300. clm/layer4/protocol/python_sync.py +126 -0
  301. clm/layer4/runner.py +150 -0
  302. clm/layer4/satori.py +262 -0
  303. clm/layer4/server/python_sync_client.py +150 -0
  304. clm/layer4/server/python_sync_server.py +139 -0
  305. clm/layer4/server/service_api.py +315 -0
  306. clm/layer4/server/start_servers.py +107 -0
  307. clm/layer4/server/stop_servers.py +24 -0
  308. clm/layer4/simulated_optimization_fix.py +0 -0
  309. clm/layer4/storage.py +463 -0
  310. clm/layer4/tridb_admin.py +268 -0
  311. clm/layer4/websocket/test_ws_client.py +104 -0
  312. clm/layer4/websocket/test_ws_server.py +138 -0
  313. clm/layer4/websocket/ws_server_local.py +160 -0
  314. clm/layer5/__init__.py +51 -0
  315. clm/layer5/async_batch_writer.py +133 -0
  316. clm/layer5/learning.py +276 -0
  317. clm/layer5/telemetry_hook.py +107 -0
  318. clm/layer5/type_lattice.py +238 -0
  319. clm/py.typed +0 -0
  320. clm/schemas/__init__.py +652 -0
  321. clm/schemas/abstract_face_schema.yaml +77 -0
  322. clm/schemas/cel_schema.yaml +416 -0
  323. clm/schemas/lifecycle/__init__.py +45 -0
  324. clm/schemas/lifecycle/models.py +123 -0
  325. clm/schemas/lifecycle/service.py +364 -0
  326. clm/schemas/mcard_schema.sql +192 -0
  327. clm/schemas/mcard_vector_schema.sql +283 -0
  328. clm/schemas/mlp-context-v1.json +91 -0
  329. clm_kernel-0.0.0.dist-info/METADATA +121 -0
  330. clm_kernel-0.0.0.dist-info/RECORD +333 -0
  331. clm_kernel-0.0.0.dist-info/WHEEL +4 -0
  332. clm_kernel-0.0.0.dist-info/entry_points.txt +3 -0
  333. 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
+ }