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,823 @@
1
+ """VCard Model (Application Plane) - Implementation of Arena + Action.
2
+
3
+ This module defines the VCard, which represents the sovereign decision layer
4
+ in the MVP Cards architecture. VCard is the IO Monad that manages all side effects.
5
+
6
+ Categorical Role:
7
+ - Applicative Functor (<*>): Applies PCard functions with side effects.
8
+ - UPTV Role: Token/Evidence container and Gateway.
9
+
10
+ DOTS Vocabulary Role: Arena + Action
11
+ ====================================
12
+
13
+ VCard is both:
14
+ - **Arena**: The interface type defining what can interact (subject_did, capabilities, external_refs)
15
+ - **Action**: The morphism where interactions (PCards) act on systems (MCards) to produce new systems
16
+
17
+ Empty Schema Principle:
18
+ =======================
19
+ VCard IS an MCard. It has no additional schema fields. Its "VCard-ness" comes
20
+ from its structured YAML content (Abstract/Concrete/Balanced logic mapped to policies).
21
+
22
+ The Four Roles (Content Sections):
23
+ 1. Identity & Credential Container (The "Who") -> `identity`
24
+ 2. Verification Hub (The "Rules") -> `verification`
25
+ 3. Side Effect Manager (The "Bridge") -> `external_refs`
26
+ 4. Input/Output Gatekeeper (The "Gate") -> `gatekeeper`
27
+
28
+ See Also:
29
+ - docs/VCard_Impl.md
30
+ """
31
+
32
+ import json
33
+ from dataclasses import dataclass, field
34
+ from datetime import datetime, timezone
35
+ from enum import Enum
36
+ from typing import Any, cast
37
+
38
+ import yaml
39
+
40
+ from clm.layer0.model.card import MCard
41
+ from clm.layer0.model.dots import DOTSMetadata, create_vcard_dots_metadata
42
+ from clm.layer0.model.hash.validator import HashAlgorithm
43
+ from clm.layer0.model.pcard import PCard
44
+
45
+
46
+ class CapabilityScope(Enum):
47
+ """Scope of a capability token."""
48
+
49
+ READ = "read"
50
+ WRITE = "write"
51
+ EXECUTE = "execute"
52
+ ADMIN = "admin"
53
+ DELEGATE = "delegate"
54
+
55
+
56
+ class GatekeeperDirection(Enum):
57
+ """Direction of gatekeeper authorization."""
58
+
59
+ INGRESS = "ingress" # Content entering the PKC
60
+ EGRESS = "egress" # Content leaving the PKC
61
+
62
+
63
+ @dataclass
64
+ class Capability:
65
+ """A capability token defining authorized actions."""
66
+
67
+ capability_id: str
68
+ actor_did: str
69
+ scope: CapabilityScope
70
+ resource_pattern: str # Regex or glob pattern for resources
71
+ expires_at: datetime | None = None
72
+ constraints: dict[str, Any] = field(default_factory=dict)
73
+ transferable: bool = False
74
+
75
+ def is_valid(self) -> bool:
76
+ """Check if the capability is still valid."""
77
+ if self.expires_at is None:
78
+ return True
79
+ # Simple naive check; in production use timezone-aware UTC
80
+ return datetime.now() < self.expires_at
81
+
82
+ def matches_resource(self, resource_hash: str) -> bool:
83
+ """Check if this capability applies to a resource."""
84
+ import re
85
+
86
+ return bool(re.match(self.resource_pattern, resource_hash))
87
+
88
+ def to_dict(self) -> dict[str, Any]:
89
+ """Serialize to dictionary."""
90
+ return {
91
+ "id": self.capability_id,
92
+ "actor": self.actor_did,
93
+ "scope": self.scope.value,
94
+ "resource_pattern": self.resource_pattern,
95
+ "expires_at": self.expires_at.isoformat() if self.expires_at else None,
96
+ "transferable": self.transferable,
97
+ "constraints": self.constraints,
98
+ }
99
+
100
+
101
+ @dataclass
102
+ class ExternalRef:
103
+ """A verified external reference managed by VCard (IO Monad)."""
104
+
105
+ uri: str
106
+ content_hash: str
107
+ status: str # "verified", "pending", "stale", "invalid"
108
+ signature: str | None = None
109
+ last_verified: datetime | None = None
110
+ qos_metrics: dict[str, Any] = field(default_factory=dict)
111
+
112
+ def to_dict(self) -> dict[str, Any]:
113
+ return {
114
+ "uri": self.uri,
115
+ "content_hash": self.content_hash,
116
+ "status": self.status,
117
+ "qos": self.qos_metrics,
118
+ }
119
+
120
+
121
+ @dataclass
122
+ class GatekeeperEvent:
123
+ """An ingress or egress gatekeeper event."""
124
+
125
+ direction: GatekeeperDirection
126
+ timestamp: datetime
127
+ source_did: str | None # For ingress
128
+ destination_did: str | None # For egress
129
+ content_hash: str
130
+ authorized: bool
131
+ capability_used: str | None = None
132
+ signature: str | None = None
133
+
134
+
135
+ # =============================================================================
136
+ # Functional Helpers (The Preferred API)
137
+ # =============================================================================
138
+
139
+
140
+ def is_vcard(card: MCard) -> bool:
141
+ """Check if an MCard contains VCard content."""
142
+ try:
143
+ content = yaml.safe_load(card.get_content(as_text=True))
144
+ # Support both JSON-style (root keys) and VCard-root style
145
+ if isinstance(content, dict):
146
+ if (
147
+ "vcard" in content
148
+ and content["vcard"].get("type") == "authentication-authorization"
149
+ ):
150
+ return True
151
+ if content.get("type") == "VCard": # Legacy/Direct JSON support
152
+ return True
153
+ return False
154
+ except Exception:
155
+ return False
156
+
157
+
158
+ def create_vcard_content(
159
+ subject_did: str,
160
+ controller_pubkeys: list[str],
161
+ capabilities: list[Capability],
162
+ external_refs: list[ExternalRef],
163
+ pcard_refs: list[dict[str, Any]] | None = None,
164
+ gatekeeper_config: dict[str, Any] | None = None,
165
+ managed_event_types: list[str] | None = None,
166
+ wrapped_pcard_hash: str | None = None,
167
+ ) -> dict[str, Any]:
168
+ """Generate the standard VCard content structure."""
169
+
170
+ # Normalize pcard_refs if likely passed as list of strings or dicts
171
+ final_pcard_refs = pcard_refs or []
172
+
173
+ # Construct the canonical VCard YAML Structure as per docs/VCard_Impl.md
174
+ return {
175
+ "vcard": {
176
+ "version": "1.0",
177
+ "type": "authentication-authorization",
178
+ "identity": {
179
+ "subject_did": subject_did,
180
+ "controller_pubkeys": controller_pubkeys,
181
+ "issued_at": datetime.now(timezone.utc).isoformat(),
182
+ },
183
+ "verification": {"pcard_refs": final_pcard_refs},
184
+ # Map capability objects to list of dicts if needed, or store in gatekeeper
185
+ # Note: The doc puts capabilities in `gatekeeper.capabilities`
186
+ "gatekeeper": gatekeeper_config
187
+ or {
188
+ "ingress_policies": [],
189
+ "egress_policies": [],
190
+ "capabilities": [c.to_dict() for c in capabilities],
191
+ },
192
+ "external_refs": [r.to_dict() for r in external_refs],
193
+ "managed_event_types": managed_event_types or [],
194
+ "wrapped_pcard_hash": wrapped_pcard_hash,
195
+ }
196
+ }
197
+
198
+
199
+ def get_pcard_refs(vcard: MCard) -> list[str]:
200
+ """Extract PCard reference hashes from VCard."""
201
+ content = _parse_vcard_content(vcard)
202
+ refs = content.get("verification", {}).get("pcard_refs", [])
203
+ # Return just hashes if they are dicts
204
+ return [r["hash"] if isinstance(r, dict) else r for r in refs]
205
+
206
+
207
+ def get_subject_did(vcard: MCard) -> str | None:
208
+ """Extract subject DID from VCard."""
209
+ content = _parse_vcard_content(vcard)
210
+ return content.get("identity", {}).get("subject_did") or content.get("subject_did")
211
+
212
+
213
+ def create_vcard(
214
+ subject_did: str,
215
+ controller_pubkeys: list[str],
216
+ capabilities: list[Capability] | None = None,
217
+ external_refs: list[ExternalRef] | None = None,
218
+ managed_event_types: list[str] | None = None,
219
+ wrapped_pcard_hash: str | None = None,
220
+ ) -> MCard:
221
+ """Functional factory to create a VCard (returns VCard instance which IS MCard)."""
222
+ return VCard(
223
+ subject_did=subject_did,
224
+ controller_pubkeys=controller_pubkeys,
225
+ capabilities=capabilities,
226
+ external_refs=external_refs,
227
+ managed_event_types=managed_event_types,
228
+ wrapped_pcard_hash=wrapped_pcard_hash,
229
+ )
230
+
231
+
232
+ def _parse_vcard_content(card: MCard) -> dict[str, Any]:
233
+ """Internal helper to parse VCard content, verifying structure."""
234
+ raw = yaml.safe_load(card.get_content(as_text=True))
235
+ if "vcard" in raw:
236
+ return raw["vcard"]
237
+ # Fallback to direct dict (Legacy/JSON compat)
238
+ return raw
239
+
240
+
241
+ # =============================================================================
242
+ # VCard Class (MCard View / Wrapper)
243
+ # =============================================================================
244
+
245
+
246
+ class VCard(PCard):
247
+ """VCard - The Application Plane unit (Arena + Action).
248
+
249
+ Categorical Hierarchy: MCard (Monad) → PCard (Functor) → **VCard (Applicative)**
250
+
251
+ VCard inherits PCard's structured-content parsing and SMC combinators,
252
+ adding sovereign identity, capability-based authorization, gatekeeper
253
+ logic, and VCard Sandwich witness factories.
254
+
255
+ As the *guard* (precondition) layer for PCard, VCard overrides
256
+ ``_validate_and_parse()`` to accept both JSON (VCard Sandwich pattern)
257
+ and YAML content, providing graceful fallbacks for non-dict payloads.
258
+
259
+ IMPORTANT: While MCard content is immutable (and thus the .hash attribute
260
+ refers to the INITIAL content), this Python object allows in-memory mutation
261
+ of capabilities and logs to support runtime Gatekeeper logic.
262
+ """
263
+
264
+ def __init__(
265
+ self,
266
+ subject_did: str | bytes,
267
+ controller_pubkeys: list[str] | None = None,
268
+ capabilities: list[Capability] | None = None,
269
+ external_refs: list[ExternalRef] | None = None,
270
+ managed_event_types: list[str] | None = None,
271
+ wrapped_pcard_hash: str | None = None,
272
+ hash_function: str | HashAlgorithm = "sha256",
273
+ **kwargs: Any,
274
+ ):
275
+ """Initialize a VCard.
276
+
277
+ This constructor is overloaded to support:
278
+ 1. Creating NEW VCard: pass subject_did, keys, etc.
279
+ 2. Wrapping EXISTING content: pass content as first arg.
280
+ """
281
+
282
+ # Check if we are creating from scratch or wrapping
283
+ is_creation = controller_pubkeys is not None
284
+
285
+ self._gatekeeper_log: list[GatekeeperEvent] = []
286
+ self._export_manifest: list[str] = []
287
+
288
+ if is_creation:
289
+ # Creation Mode
290
+ full_content_dict = create_vcard_content(
291
+ subject_did=cast(str, subject_did),
292
+ controller_pubkeys=controller_pubkeys or [],
293
+ capabilities=capabilities or [],
294
+ external_refs=external_refs or [],
295
+ managed_event_types=managed_event_types or [],
296
+ wrapped_pcard_hash=wrapped_pcard_hash,
297
+ )
298
+
299
+ # Use inner dict for initial state
300
+ initial_data = full_content_dict["vcard"]
301
+
302
+ # Incorporate any additional kwargs directly into the vcard content
303
+ for k, v in kwargs.items():
304
+ initial_data[k] = v
305
+
306
+ # Serialize to JSON for MCard storage (Immutable Base)
307
+ final_content = json.dumps(full_content_dict, sort_keys=True)
308
+ super().__init__(final_content, hash_function)
309
+ else:
310
+ # Wrapping Mode (subject_did is actually content)
311
+ super().__init__(subject_did, hash_function)
312
+ initial_data = self._get_vcard_inner()
313
+
314
+ # Initialize Mutable State from Content
315
+ self._initialize_mutable_state(initial_data)
316
+
317
+ def get_wrapped_pcard_hash(self) -> str | None:
318
+ """Get the explicitly wrapped PCard hash, if provided."""
319
+ return self._get_vcard_inner().get("wrapped_pcard_hash")
320
+
321
+ # =========================================================================
322
+ # VCard Guard: _validate_and_parse override (Precondition for PCard)
323
+ # =========================================================================
324
+
325
+ def _validate_and_parse(self) -> dict[str, Any]:
326
+ """VCard guard override — parse JSON or YAML content gracefully.
327
+
328
+ This overrides PCard's strict YAML-only parser to serve as the
329
+ *precondition guard* for the MCard → PCard → VCard hierarchy:
330
+
331
+ 1. JSON content (VCard Sandwich ``{"vcard": {...}}``) is parsed first.
332
+ 2. YAML content is parsed as a fallback.
333
+ 3. Non-dict content (e.g., ``null``, scalars) returns ``{}``
334
+ instead of raising, providing a graceful guard.
335
+ """
336
+ content_str = self.get_content(as_text=True)
337
+ if not isinstance(content_str, str):
338
+ return {}
339
+
340
+ # 1. Try JSON (VCard Sandwich pattern)
341
+ stripped = content_str.strip()
342
+ if stripped.startswith("{"):
343
+ try:
344
+ parsed = json.loads(stripped)
345
+ if isinstance(parsed, dict):
346
+ return parsed
347
+ except (json.JSONDecodeError, ValueError):
348
+ pass
349
+
350
+ # 2. Try YAML
351
+ try:
352
+ parsed = yaml.safe_load(content_str)
353
+ if isinstance(parsed, dict):
354
+ return parsed
355
+ except yaml.YAMLError:
356
+ pass
357
+
358
+ # 3. Graceful fallback — non-dict content is valid but empty
359
+ return {}
360
+
361
+ def _get_vcard_inner(self) -> dict[str, Any]:
362
+ """Extract the inner VCard dict from parsed content.
363
+
364
+ Handles both wrapped ``{"vcard": {...}}`` and flat dict formats.
365
+ Uses ``self._parsed_clm`` inherited from PCard.
366
+ """
367
+ parsed = self._parsed_clm
368
+ if isinstance(parsed, dict) and "vcard" in parsed:
369
+ return parsed["vcard"]
370
+ return parsed if isinstance(parsed, dict) else {}
371
+
372
+ def _initialize_mutable_state(self, data: dict[str, Any]):
373
+ """Hydrate mutable lists from parsed content dictionary."""
374
+ # 1. Identity
375
+ self._subject_did = data.get("identity", {}).get("subject_did") or data.get(
376
+ "subject_did", ""
377
+ )
378
+ self._controller_pubkeys = data.get("identity", {}).get(
379
+ "controller_pubkeys"
380
+ ) or data.get("controller_pubkeys", [])
381
+
382
+ # 2. Capabilities
383
+ raw_caps = data.get("gatekeeper", {}).get("capabilities") or data.get(
384
+ "capabilities", []
385
+ )
386
+ self._capabilities: list[Capability] = []
387
+ for c in raw_caps:
388
+ try:
389
+ if isinstance(c, dict):
390
+ self._capabilities.append(
391
+ Capability(
392
+ capability_id=c["id"],
393
+ actor_did=c["actor"],
394
+ scope=CapabilityScope(c["scope"]),
395
+ resource_pattern=c["resource_pattern"],
396
+ expires_at=(
397
+ datetime.fromisoformat(c["expires_at"])
398
+ if c.get("expires_at")
399
+ else None
400
+ ),
401
+ transferable=c.get("transferable", False),
402
+ constraints=c.get("constraints", {}),
403
+ )
404
+ )
405
+ except Exception:
406
+ continue
407
+
408
+ # 3. External Refs (including PCard refs)
409
+ raw_refs = data.get("external_refs", [])
410
+ self._external_refs: list[ExternalRef] = []
411
+ for r in raw_refs:
412
+ if isinstance(r, dict):
413
+ self._external_refs.append(
414
+ ExternalRef(
415
+ uri=r.get("uri", ""),
416
+ content_hash=r.get("content_hash", ""),
417
+ status=r.get("status", "pending"),
418
+ qos_metrics=r.get("qos", {}),
419
+ )
420
+ )
421
+
422
+ # 4. PCard Refs (Verification) -> Merged into External Refs conceptually or tracked?
423
+ # The docs says PCard refs are in verification block.
424
+ # But old test 'add_pcard_reference' expects them to be retrievable.
425
+ # We will parse them but `add_pcard_reference` typically adds to external refs
426
+ # or we keep a separate list? The doc says "references PCards as procedures".
427
+ # Let's check `get_pcard_references` implementation below.
428
+
429
+ # We will trust `_external_refs` to hold all refs including pcards if they were stored there.
430
+ # But if they are in `verification.pcard_refs`, we should probably track them too.
431
+ self._pcard_refs_hashes = [
432
+ r["hash"] if isinstance(r, dict) else r
433
+ for r in data.get("verification", {}).get("pcard_refs", [])
434
+ ]
435
+
436
+ # =========================================================================
437
+ # Role 1: Identity & Credential Container
438
+ # =========================================================================
439
+
440
+ @property
441
+ def subject_did(self) -> str:
442
+ return self._subject_did
443
+
444
+ @property
445
+ def controller_pubkeys(self) -> list[str]:
446
+ return self._controller_pubkeys
447
+
448
+ @property
449
+ def capabilities(self) -> list[Capability]:
450
+ return self._capabilities
451
+
452
+ def add_capability(self, capability: Capability) -> None:
453
+ """Add a new capability to this VCard (Runtime Memory Only)."""
454
+ self._capabilities.append(capability)
455
+
456
+ def get_valid_capabilities(self) -> list[Capability]:
457
+ """Get all currently valid capabilities."""
458
+ return [c for c in self._capabilities if c.is_valid()]
459
+
460
+ # =========================================================================
461
+ # Role 2: Verification Hub
462
+ # =========================================================================
463
+
464
+ def add_pcard_reference(self, pcard_hash: str) -> None:
465
+ """Register a PCard for verification."""
466
+ # Add to the specific list
467
+ if pcard_hash not in self._pcard_refs_hashes:
468
+ self._pcard_refs_hashes.append(pcard_hash)
469
+
470
+ # Also add as generic external ref as per old implementation
471
+ ref = ExternalRef(
472
+ uri=f"pcard://{pcard_hash}", content_hash=pcard_hash, status="verified"
473
+ )
474
+ self.add_external_ref(ref)
475
+
476
+ def get_pcard_references(self) -> list[str]:
477
+ """Get all registered PCard hashes."""
478
+ # Return unique set of explicitly tracked + those in external refs
479
+ refs = set(self._pcard_refs_hashes)
480
+ for r in self._external_refs:
481
+ if r.uri.startswith("pcard://"):
482
+ refs.add(r.content_hash)
483
+ return list(refs)
484
+
485
+ # =========================================================================
486
+ # Role 3: Side Effect Manager
487
+ # =========================================================================
488
+
489
+ @property
490
+ def external_refs(self) -> list[ExternalRef]:
491
+ return self._external_refs
492
+
493
+ def add_external_ref(self, ref: ExternalRef) -> None:
494
+ """Add an external reference (describes a side effect)."""
495
+ self._external_refs.append(ref)
496
+
497
+ def get_external_refs_by_status(self, status: str) -> list[ExternalRef]:
498
+ """Get external references by verification status."""
499
+ return [r for r in self._external_refs if r.status == status]
500
+
501
+ def verify_external_ref(self, uri: str, new_hash: str) -> bool:
502
+ """Verify an external reference and update its status."""
503
+ for ref in self._external_refs:
504
+ if ref.uri == uri:
505
+ if ref.content_hash == new_hash:
506
+ ref.status = "verified"
507
+ ref.last_verified = datetime.now()
508
+ return True
509
+ else:
510
+ ref.status = "stale"
511
+ return False
512
+ return False
513
+
514
+ def get_managed_event_types(self) -> list[str]:
515
+ """Get the list of event/MCard types this VCard subscribes to or dispatches."""
516
+ return self._get_vcard_inner().get("managed_event_types", [])
517
+
518
+ # =========================================================================
519
+ # Role 4: Input/Output Gatekeeper
520
+ # =========================================================================
521
+
522
+ def has_capability(self, scope: CapabilityScope, resource_hash: str) -> bool:
523
+ """Check if VCard has a valid capability for a resource."""
524
+ for cap in self.get_valid_capabilities():
525
+ if cap.scope == scope and cap.matches_resource(resource_hash):
526
+ return True
527
+ return False
528
+
529
+ def authorize_ingress(
530
+ self, source_did: str, content_hash: str, capability_id: str | None = None
531
+ ) -> bool:
532
+ """Authorize content entering the PKC (ingress)."""
533
+ authorized = False
534
+ used_capability = None
535
+
536
+ # Check if source has ingress capability
537
+ for cap in self.get_valid_capabilities():
538
+ if cap.actor_did == source_did and cap.scope in [
539
+ CapabilityScope.WRITE,
540
+ CapabilityScope.ADMIN,
541
+ ]:
542
+ if capability_id is None or cap.capability_id == capability_id:
543
+ authorized = True
544
+ used_capability = cap.capability_id
545
+ break
546
+
547
+ # Log the gatekeeper event
548
+ event = GatekeeperEvent(
549
+ direction=GatekeeperDirection.INGRESS,
550
+ timestamp=datetime.now(),
551
+ source_did=source_did,
552
+ destination_did=None,
553
+ content_hash=content_hash,
554
+ authorized=authorized,
555
+ capability_used=used_capability,
556
+ )
557
+ self._gatekeeper_log.append(event)
558
+ return authorized
559
+
560
+ def register_for_egress(self, content_hash: str) -> bool:
561
+ """Register content for potential egress."""
562
+ if content_hash not in self._export_manifest:
563
+ self._export_manifest.append(content_hash)
564
+ return True
565
+ return False
566
+
567
+ def authorize_egress(
568
+ self,
569
+ destination_did: str,
570
+ content_hash: str,
571
+ capability_id: str | None = None,
572
+ ) -> bool:
573
+ """Authorize content leaving the PKC (egress)."""
574
+ if content_hash not in self._export_manifest:
575
+ self._log_egress_attempt(
576
+ destination_did, content_hash, False, "Not registered in manifest"
577
+ )
578
+ return False
579
+
580
+ authorized = False
581
+ used_capability = None
582
+
583
+ for cap in self.get_valid_capabilities():
584
+ if cap.scope in [CapabilityScope.READ, CapabilityScope.ADMIN]:
585
+ if cap.matches_resource(content_hash):
586
+ if capability_id is None or cap.capability_id == capability_id:
587
+ authorized = True
588
+ used_capability = cap.capability_id
589
+ break
590
+
591
+ self._log_egress_attempt(
592
+ destination_did, content_hash, authorized, used_capability
593
+ )
594
+ return authorized
595
+
596
+ def _log_egress_attempt(self, dst, hash_val, auth, cap_id):
597
+ event = GatekeeperEvent(
598
+ direction=GatekeeperDirection.EGRESS,
599
+ timestamp=datetime.now(),
600
+ source_did=None,
601
+ destination_did=dst,
602
+ content_hash=hash_val,
603
+ authorized=auth,
604
+ capability_used=cap_id,
605
+ )
606
+ self._gatekeeper_log.append(event)
607
+
608
+ def get_gatekeeper_log(
609
+ self, direction: GatekeeperDirection | None = None
610
+ ) -> list[GatekeeperEvent]:
611
+ """Get gatekeeper audit log."""
612
+ if direction is None:
613
+ return self._gatekeeper_log
614
+ return [e for e in self._gatekeeper_log if e.direction == direction]
615
+
616
+ def get_dots_metadata(self) -> DOTSMetadata:
617
+ """Get DOTS metadata for this VCard."""
618
+ return create_vcard_dots_metadata(credential_hash=self.hash)
619
+
620
+ # =========================================================================
621
+ # EOS Compliance
622
+ # =========================================================================
623
+
624
+ def simulate_mode(self) -> "VCardSimulation":
625
+ """Enter simulation mode for EOS compliance."""
626
+ return VCardSimulation(self)
627
+
628
+ # =========================================================================
629
+ # Petri Net Token Semantics
630
+ # =========================================================================
631
+
632
+ def get_token_handle(self) -> str:
633
+ """Get the handle for this VCard (Token).
634
+
635
+ If verification VCard, returns the creating PCard's balanced handle.
636
+ Otherwise, derives from content.
637
+
638
+ Returns:
639
+ Handle string (Place name)
640
+ """
641
+ v = self._get_vcard_inner()
642
+
643
+ # explicit handle in content takes precedence (e.g. verification output)
644
+ if v.get("handle"):
645
+ return v["handle"]
646
+ if v.get("token_handle"):
647
+ return v["token_handle"]
648
+
649
+ return f"vcard://{self.hash[:16]}"
650
+
651
+ def is_verification_vcard(self) -> bool:
652
+ """Check if this is a Verification VCard (produced by a PCard execution)."""
653
+ v = self._get_vcard_inner()
654
+ return v.get("type") in ("verification", "verification-result") or bool(
655
+ v.get("verification", {}).get("execution_result")
656
+ )
657
+
658
+ @staticmethod
659
+ def create_verification_vcard(
660
+ pcard: Any,
661
+ execution_result: Any,
662
+ previous_vcard: "VCard | None" = None,
663
+ verified: bool = True,
664
+ hash_function: str = "sha256",
665
+ ) -> "VCard":
666
+ """Create a Verification VCard (Token) from a PCard execution.
667
+
668
+ Args:
669
+ pcard: The PCard (Transition) that executed.
670
+ execution_result: The result of the execution.
671
+ previous_vcard: The input VCard (Token) if any (for provenance).
672
+ verified: Whether execution was successful.
673
+ hash_function: Hash algorithm to use.
674
+
675
+ Returns:
676
+ A new VCard instance.
677
+ """
678
+ # Calculate handle (Place)
679
+ handle = getattr(
680
+ pcard,
681
+ "get_balanced_handle",
682
+ lambda: f"hash/{pcard.hash[:16]}/balanced",
683
+ )()
684
+
685
+ # Construct content
686
+ content = {
687
+ "vcard": {
688
+ "version": "1.0",
689
+ "type": "verification",
690
+ "handle": handle,
691
+ "identity": {"subject_did": "did:ptr:system", "controller_pubkeys": []},
692
+ "verification": {
693
+ "pcard_hash": pcard.hash,
694
+ "execution_result": execution_result,
695
+ "success": verified, # Match JS 'success' field
696
+ "timestamp": datetime.now(timezone.utc).isoformat(),
697
+ "previous_hash": previous_vcard.hash if previous_vcard else None,
698
+ },
699
+ "gatekeeper": {"capabilities": []},
700
+ "external_refs": [],
701
+ }
702
+ }
703
+
704
+ # Create VCard from content
705
+ return VCard(json.dumps(content, sort_keys=True), hash_function=hash_function)
706
+
707
+ def get_source_pcard_hash(self) -> str | None:
708
+ """Get the hash of the PCard that produced this token."""
709
+ inner = self._get_vcard_inner()
710
+ return inner.get("verification", {}).get("pcard_hash")
711
+
712
+ def get_previous_hash(self) -> str | None:
713
+ """Get the previous VCard hash in the provenance chain."""
714
+ inner = self._get_vcard_inner()
715
+ return inner.get("verification", {}).get("previous_hash")
716
+
717
+ def enables_transition(self, pcard: "PCard") -> bool:
718
+ """Check if this VCard can serve as input to the given PCard.
719
+
720
+ Args:
721
+ pcard: The transition to check.
722
+
723
+ Returns:
724
+ True if this VCard satisfies one of the inputs.
725
+ """
726
+ input_refs = pcard.get_input_vcard_refs()
727
+ self.get_token_handle()
728
+
729
+ for ref in input_refs:
730
+ # Check handle match (if we were stored at that handle)
731
+ # Since VCard model doesn't know where it is stored (Collection knows),
732
+ # this check is limited. We check if we match strict hash requirements.
733
+ if ref.get("expected_hash") == self.hash:
734
+ return True
735
+
736
+ return False
737
+
738
+
739
+ class VCardSimulation:
740
+ """Simulation context for VCard (EOS Compliance)."""
741
+
742
+ def __init__(self, vcard: VCard):
743
+ self.vcard = vcard
744
+ self.simulation_log: list[dict[str, Any]] = []
745
+
746
+ def log_effect(self, effect_type: str, details: dict[str, Any]) -> None:
747
+ """Log a simulated side effect."""
748
+ self.simulation_log.append(
749
+ {
750
+ "timestamp": datetime.now().isoformat(),
751
+ "effect_type": effect_type,
752
+ "details": details,
753
+ "simulated": True,
754
+ }
755
+ )
756
+
757
+ def get_simulation_log(self) -> list[dict[str, Any]]:
758
+ """Get the simulation log."""
759
+ return self.simulation_log
760
+
761
+
762
+ class VCardPre(VCard):
763
+ """VCardPre acts as a Subscriber/Hook mechanism.
764
+
765
+ It subscribes to particular types of events or MCards inserted into
766
+ the database/execution log, executing hooks to alter its internal state.
767
+ """
768
+
769
+ def fire_hook(self, event_type: str, mcard: MCard) -> None:
770
+ """Trigger the hook logic for this subscriber."""
771
+ if event_type in self.get_managed_event_types():
772
+ # In a full implementation, this would perform state transitions
773
+ # or record the event. For now, we update the gatekeeper log.
774
+ self.log_gatekeeper_event(
775
+ GatekeeperEvent(
776
+ direction=GatekeeperDirection.INGRESS,
777
+ timestamp=datetime.now(timezone.utc),
778
+ source_did=None,
779
+ destination_did=self.get_subject_did() or "unknown",
780
+ content_hash=mcard.hash,
781
+ authorized=True,
782
+ capability_used="VCardPre_Hook",
783
+ )
784
+ )
785
+
786
+
787
+ class VCardPost(VCard):
788
+ """VCardPost acts as a Dispatcher mechanism.
789
+
790
+ It contains a list of items (types) that, if it were to be fired,
791
+ it dispatches or creates relevant types of MCards into the execution log.
792
+ """
793
+
794
+ def dispatch_events(self, log_db) -> list[MCard]:
795
+ """Dispatch relevant types of MCards into the execution log."""
796
+ import json
797
+
798
+ from clm.layer0.model.card import MCard
799
+
800
+ dispatched_cards = []
801
+ for event_type in self.get_managed_event_types():
802
+ # Dispatch empty prototype MCards based on the managed types
803
+ # A real implementation would hydrate this from the execution output
804
+ payload = json.dumps({"type": event_type, "dispatched_by": self.hash})
805
+ dispatched = MCard(payload)
806
+ if log_db:
807
+ log_db.add(dispatched)
808
+ dispatched_cards.append(dispatched)
809
+
810
+ # Log the egress
811
+ self.log_gatekeeper_event(
812
+ GatekeeperEvent(
813
+ direction=GatekeeperDirection.EGRESS,
814
+ timestamp=datetime.now(timezone.utc),
815
+ source_did=self.get_subject_did() or "unknown",
816
+ destination_did=None,
817
+ content_hash=dispatched.hash,
818
+ authorized=True,
819
+ capability_used="VCardPost_Dispatch",
820
+ )
821
+ )
822
+
823
+ return dispatched_cards