omnibase_infra 0.2.5__py3-none-any.whl → 0.2.7__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 (139) hide show
  1. omnibase_infra/constants_topic_patterns.py +26 -0
  2. omnibase_infra/enums/__init__.py +3 -0
  3. omnibase_infra/enums/enum_consumer_group_purpose.py +92 -0
  4. omnibase_infra/enums/enum_handler_source_mode.py +16 -2
  5. omnibase_infra/errors/__init__.py +4 -0
  6. omnibase_infra/errors/error_binding_resolution.py +128 -0
  7. omnibase_infra/event_bus/configs/kafka_event_bus_config.yaml +0 -2
  8. omnibase_infra/event_bus/event_bus_inmemory.py +64 -10
  9. omnibase_infra/event_bus/event_bus_kafka.py +105 -47
  10. omnibase_infra/event_bus/mixin_kafka_broadcast.py +3 -7
  11. omnibase_infra/event_bus/mixin_kafka_dlq.py +12 -6
  12. omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +0 -81
  13. omnibase_infra/event_bus/testing/__init__.py +26 -0
  14. omnibase_infra/event_bus/testing/adapter_protocol_event_publisher_inmemory.py +418 -0
  15. omnibase_infra/event_bus/testing/model_publisher_metrics.py +64 -0
  16. omnibase_infra/handlers/handler_consul.py +2 -0
  17. omnibase_infra/handlers/mixins/__init__.py +5 -0
  18. omnibase_infra/handlers/mixins/mixin_consul_service.py +274 -10
  19. omnibase_infra/handlers/mixins/mixin_consul_topic_index.py +585 -0
  20. omnibase_infra/handlers/models/model_filesystem_config.py +4 -4
  21. omnibase_infra/migrations/001_create_event_ledger.sql +166 -0
  22. omnibase_infra/migrations/001_drop_event_ledger.sql +18 -0
  23. omnibase_infra/mixins/mixin_node_introspection.py +189 -19
  24. omnibase_infra/models/__init__.py +8 -0
  25. omnibase_infra/models/bindings/__init__.py +59 -0
  26. omnibase_infra/models/bindings/constants.py +144 -0
  27. omnibase_infra/models/bindings/model_binding_resolution_result.py +103 -0
  28. omnibase_infra/models/bindings/model_operation_binding.py +44 -0
  29. omnibase_infra/models/bindings/model_operation_bindings_subcontract.py +152 -0
  30. omnibase_infra/models/bindings/model_parsed_binding.py +52 -0
  31. omnibase_infra/models/discovery/model_introspection_config.py +25 -17
  32. omnibase_infra/models/dispatch/__init__.py +8 -0
  33. omnibase_infra/models/dispatch/model_debug_trace_snapshot.py +114 -0
  34. omnibase_infra/models/dispatch/model_materialized_dispatch.py +141 -0
  35. omnibase_infra/models/handlers/model_handler_source_config.py +1 -1
  36. omnibase_infra/models/model_node_identity.py +126 -0
  37. omnibase_infra/models/projection/model_snapshot_topic_config.py +3 -2
  38. omnibase_infra/models/registration/__init__.py +9 -0
  39. omnibase_infra/models/registration/model_event_bus_topic_entry.py +59 -0
  40. omnibase_infra/models/registration/model_node_event_bus_config.py +99 -0
  41. omnibase_infra/models/registration/model_node_introspection_event.py +11 -0
  42. omnibase_infra/models/runtime/__init__.py +9 -0
  43. omnibase_infra/models/validation/model_coverage_metrics.py +2 -2
  44. omnibase_infra/nodes/__init__.py +9 -0
  45. omnibase_infra/nodes/contract_registry_reducer/__init__.py +29 -0
  46. omnibase_infra/nodes/contract_registry_reducer/contract.yaml +255 -0
  47. omnibase_infra/nodes/contract_registry_reducer/models/__init__.py +38 -0
  48. omnibase_infra/nodes/contract_registry_reducer/models/model_contract_registry_state.py +266 -0
  49. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_cleanup_topic_references.py +55 -0
  50. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_deactivate_contract.py +58 -0
  51. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_mark_stale.py +49 -0
  52. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_heartbeat.py +71 -0
  53. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_topic.py +66 -0
  54. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_upsert_contract.py +92 -0
  55. omnibase_infra/nodes/contract_registry_reducer/node.py +121 -0
  56. omnibase_infra/nodes/contract_registry_reducer/reducer.py +784 -0
  57. omnibase_infra/nodes/contract_registry_reducer/registry/__init__.py +9 -0
  58. omnibase_infra/nodes/contract_registry_reducer/registry/registry_infra_contract_registry_reducer.py +101 -0
  59. omnibase_infra/nodes/handlers/consul/contract.yaml +85 -0
  60. omnibase_infra/nodes/handlers/db/contract.yaml +72 -0
  61. omnibase_infra/nodes/handlers/graph/contract.yaml +127 -0
  62. omnibase_infra/nodes/handlers/http/contract.yaml +74 -0
  63. omnibase_infra/nodes/handlers/intent/contract.yaml +66 -0
  64. omnibase_infra/nodes/handlers/mcp/contract.yaml +69 -0
  65. omnibase_infra/nodes/handlers/vault/contract.yaml +91 -0
  66. omnibase_infra/nodes/node_ledger_projection_compute/__init__.py +50 -0
  67. omnibase_infra/nodes/node_ledger_projection_compute/contract.yaml +104 -0
  68. omnibase_infra/nodes/node_ledger_projection_compute/node.py +284 -0
  69. omnibase_infra/nodes/node_ledger_projection_compute/registry/__init__.py +29 -0
  70. omnibase_infra/nodes/node_ledger_projection_compute/registry/registry_infra_ledger_projection.py +118 -0
  71. omnibase_infra/nodes/node_ledger_write_effect/__init__.py +82 -0
  72. omnibase_infra/nodes/node_ledger_write_effect/contract.yaml +200 -0
  73. omnibase_infra/nodes/node_ledger_write_effect/handlers/__init__.py +22 -0
  74. omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_append.py +372 -0
  75. omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_query.py +597 -0
  76. omnibase_infra/nodes/node_ledger_write_effect/models/__init__.py +31 -0
  77. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_append_result.py +54 -0
  78. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_entry.py +92 -0
  79. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query.py +53 -0
  80. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query_result.py +41 -0
  81. omnibase_infra/nodes/node_ledger_write_effect/node.py +89 -0
  82. omnibase_infra/nodes/node_ledger_write_effect/protocols/__init__.py +13 -0
  83. omnibase_infra/nodes/node_ledger_write_effect/protocols/protocol_ledger_persistence.py +127 -0
  84. omnibase_infra/nodes/node_ledger_write_effect/registry/__init__.py +9 -0
  85. omnibase_infra/nodes/node_ledger_write_effect/registry/registry_infra_ledger_write.py +121 -0
  86. omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +7 -5
  87. omnibase_infra/nodes/reducers/models/__init__.py +7 -2
  88. omnibase_infra/nodes/reducers/models/model_payload_consul_register.py +11 -0
  89. omnibase_infra/nodes/reducers/models/model_payload_ledger_append.py +133 -0
  90. omnibase_infra/nodes/reducers/registration_reducer.py +1 -0
  91. omnibase_infra/protocols/__init__.py +3 -0
  92. omnibase_infra/protocols/protocol_dispatch_engine.py +152 -0
  93. omnibase_infra/runtime/__init__.py +60 -0
  94. omnibase_infra/runtime/binding_resolver.py +753 -0
  95. omnibase_infra/runtime/constants_security.py +70 -0
  96. omnibase_infra/runtime/contract_loaders/__init__.py +9 -0
  97. omnibase_infra/runtime/contract_loaders/operation_bindings_loader.py +789 -0
  98. omnibase_infra/runtime/emit_daemon/__init__.py +97 -0
  99. omnibase_infra/runtime/emit_daemon/cli.py +844 -0
  100. omnibase_infra/runtime/emit_daemon/client.py +811 -0
  101. omnibase_infra/runtime/emit_daemon/config.py +535 -0
  102. omnibase_infra/runtime/emit_daemon/daemon.py +812 -0
  103. omnibase_infra/runtime/emit_daemon/event_registry.py +477 -0
  104. omnibase_infra/runtime/emit_daemon/model_daemon_request.py +139 -0
  105. omnibase_infra/runtime/emit_daemon/model_daemon_response.py +191 -0
  106. omnibase_infra/runtime/emit_daemon/queue.py +618 -0
  107. omnibase_infra/runtime/event_bus_subcontract_wiring.py +466 -0
  108. omnibase_infra/runtime/handler_source_resolver.py +43 -2
  109. omnibase_infra/runtime/kafka_contract_source.py +984 -0
  110. omnibase_infra/runtime/models/__init__.py +13 -0
  111. omnibase_infra/runtime/models/model_contract_load_result.py +224 -0
  112. omnibase_infra/runtime/models/model_runtime_contract_config.py +268 -0
  113. omnibase_infra/runtime/models/model_runtime_scheduler_config.py +4 -3
  114. omnibase_infra/runtime/models/model_security_config.py +109 -0
  115. omnibase_infra/runtime/publisher_topic_scoped.py +294 -0
  116. omnibase_infra/runtime/runtime_contract_config_loader.py +406 -0
  117. omnibase_infra/runtime/service_kernel.py +76 -6
  118. omnibase_infra/runtime/service_message_dispatch_engine.py +558 -15
  119. omnibase_infra/runtime/service_runtime_host_process.py +770 -20
  120. omnibase_infra/runtime/transition_notification_publisher.py +3 -2
  121. omnibase_infra/runtime/util_wiring.py +206 -62
  122. omnibase_infra/services/mcp/service_mcp_tool_sync.py +27 -9
  123. omnibase_infra/services/session/config_consumer.py +25 -8
  124. omnibase_infra/services/session/config_store.py +2 -2
  125. omnibase_infra/services/session/consumer.py +1 -1
  126. omnibase_infra/topics/__init__.py +45 -0
  127. omnibase_infra/topics/platform_topic_suffixes.py +140 -0
  128. omnibase_infra/topics/util_topic_composition.py +95 -0
  129. omnibase_infra/types/typed_dict/__init__.py +9 -1
  130. omnibase_infra/types/typed_dict/typed_dict_envelope_build_params.py +115 -0
  131. omnibase_infra/utils/__init__.py +9 -0
  132. omnibase_infra/utils/util_consumer_group.py +232 -0
  133. omnibase_infra/validation/infra_validators.py +18 -1
  134. omnibase_infra/validation/validation_exemptions.yaml +192 -0
  135. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/METADATA +3 -3
  136. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/RECORD +139 -52
  137. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/entry_points.txt +1 -0
  138. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/WHEEL +0 -0
  139. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,50 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 OmniNode Team
3
+ """Node Ledger Projection Compute - Platform event ledger projection node.
4
+
5
+ This package provides the NodeLedgerProjectionCompute, a COMPUTE node that
6
+ subscribes to 7 platform event topics for event ledger persistence.
7
+
8
+ Core Purpose:
9
+ Projects events from the platform event bus into the audit ledger,
10
+ enabling complete traceability and debugging support across all
11
+ node lifecycle events, FSM transitions, and runtime coordination.
12
+
13
+ Subscribed Topics:
14
+ - onex.evt.platform.node-registration.v1
15
+ - onex.evt.platform.node-introspection.v1
16
+ - onex.evt.platform.node-heartbeat.v1
17
+ - onex.cmd.platform.request-introspection.v1
18
+ - onex.evt.platform.fsm-state-transitions.v1
19
+ - onex.intent.platform.runtime-tick.v1
20
+ - onex.snapshot.platform.registration-snapshots.v1
21
+
22
+ Consumer Configuration:
23
+ - consumer_purpose: "audit" (non-processing, read-only)
24
+ - auto_offset_reset: "earliest" (capture all historical events)
25
+
26
+ Related Tickets:
27
+ - OMN-1648: Ledger Projection Compute Node
28
+
29
+ Example:
30
+ >>> from omnibase_core.container import ModelONEXContainer
31
+ >>> from omnibase_infra.nodes.node_ledger_projection_compute import (
32
+ ... NodeLedgerProjectionCompute,
33
+ ... RegistryInfraLedgerProjection,
34
+ ... )
35
+ >>>
36
+ >>> container = ModelONEXContainer()
37
+ >>> node = RegistryInfraLedgerProjection.create_node(container)
38
+ """
39
+
40
+ from omnibase_infra.nodes.node_ledger_projection_compute.node import (
41
+ NodeLedgerProjectionCompute,
42
+ )
43
+ from omnibase_infra.nodes.node_ledger_projection_compute.registry import (
44
+ RegistryInfraLedgerProjection,
45
+ )
46
+
47
+ __all__ = [
48
+ "NodeLedgerProjectionCompute",
49
+ "RegistryInfraLedgerProjection",
50
+ ]
@@ -0,0 +1,104 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 OmniNode Team
3
+ #
4
+ # ONEX Node Contract
5
+ # Node: NodeLedgerProjectionCompute
6
+ #
7
+ # COMPUTE node that subscribes to 7 platform event topics for event ledger
8
+ # persistence. This node projects events from the platform event bus into
9
+ # the audit ledger for complete traceability and debugging support.
10
+ #
11
+ # The node subscribes with consumer_purpose="audit" and auto_offset_reset="earliest"
12
+ # to ensure no events are missed and all historical events are captured.
13
+ #
14
+ # Related Tickets:
15
+ # - OMN-1646: Event Ledger Schema and Models
16
+ # - OMN-1647: Ledger Write Effect Node
17
+ # - OMN-1648: Ledger Projection Compute Node
18
+ # Contract identifiers
19
+ name: "node_ledger_projection_compute"
20
+ contract_name: "node_ledger_projection_compute"
21
+ node_name: "node_ledger_projection_compute"
22
+ contract_version:
23
+ major: 1
24
+ minor: 0
25
+ patch: 0
26
+ node_version: "0.1.0"
27
+ # Node type - COMPUTE for event projection logic
28
+ node_type: "COMPUTE_GENERIC"
29
+ # Description
30
+ description: >
31
+ COMPUTE node that subscribes to 7 platform event topics for event ledger persistence. Projects events from the platform event bus into the audit ledger, enabling complete traceability and debugging support. Uses consumer_purpose="audit" to indicate non-processing read-only consumption and auto_offset_reset="earliest" to capture all historical events.
32
+
33
+ # Event bus configuration
34
+ event_bus:
35
+ version:
36
+ major: 1
37
+ minor: 0
38
+ patch: 0
39
+ # Subscribe to all 7 platform event topics for complete audit coverage
40
+ subscribe_topics:
41
+ # Node lifecycle events
42
+ - "onex.evt.platform.node-registration.v1"
43
+ - "onex.evt.platform.node-introspection.v1"
44
+ - "onex.evt.platform.node-heartbeat.v1"
45
+ # Introspection commands
46
+ - "onex.cmd.platform.request-introspection.v1"
47
+ # FSM state tracking
48
+ - "onex.evt.platform.fsm-state-transitions.v1"
49
+ # Runtime coordination
50
+ - "onex.intent.platform.runtime-tick.v1"
51
+ # State snapshots
52
+ - "onex.snapshot.platform.registration-snapshots.v1"
53
+ # Audit consumer - read-only, non-processing consumption for ledger persistence
54
+ consumer_purpose: "audit"
55
+ # Start from earliest offset to capture all historical events
56
+ auto_offset_reset: "earliest"
57
+ # Consumer group for ledger projection
58
+ consumer_group: "onex-ledger-projection-compute"
59
+ # Strongly typed I/O models
60
+ input_model:
61
+ name: "ModelEventMessage"
62
+ module: "omnibase_infra.event_bus.models.model_event_message"
63
+ description: >
64
+ Kafka event message containing raw bytes payload and headers. The compute node processes events from all 7 subscribed topics.
65
+
66
+ output_model:
67
+ name: "ModelPayloadLedgerAppend"
68
+ module: "omnibase_infra.nodes.reducers.models.model_payload_ledger_append"
69
+ description: >
70
+ Ledger append payload to be passed to NodeLedgerWriteEffect. Contains the event data with Kafka position for idempotent writes.
71
+
72
+ # Capabilities provided by this node
73
+ capabilities:
74
+ - name: "ledger.projection"
75
+ description: "Project platform events into the audit ledger"
76
+ version: "0.1.0"
77
+ - name: "ledger.projection.multi_topic"
78
+ description: "Subscribe to multiple platform topics for comprehensive event capture"
79
+ - name: "ledger.projection.audit"
80
+ description: "Non-processing, read-only event consumption for audit purposes"
81
+ # Dependencies (protocols this node requires)
82
+ dependencies:
83
+ - name: "node_ledger_write_effect"
84
+ type: "node"
85
+ class_name: "NodeLedgerWriteEffect"
86
+ module: "omnibase_infra.nodes.node_ledger_write_effect"
87
+ description: "Effect node for writing events to the PostgreSQL audit ledger"
88
+ - name: "protocol_event_envelope"
89
+ type: "protocol"
90
+ description: "Protocol for parsing and handling event envelopes"
91
+ optional: false
92
+ # Metadata
93
+ metadata:
94
+ author: "OmniNode Team"
95
+ license: "MIT"
96
+ created: "2026-01-29"
97
+ ticket: "OMN-1648"
98
+ tags:
99
+ - compute
100
+ - ledger
101
+ - audit
102
+ - projection
103
+ - multi-topic
104
+ - platform-events
@@ -0,0 +1,284 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 OmniNode Team
3
+ """NodeLedgerProjectionCompute - Extracts metadata from platform events for ledger persistence.
4
+
5
+ This COMPUTE node transforms ModelEventMessage into ModelPayloadLedgerAppend wrapped
6
+ in a ModelIntent for the Effect layer. It follows the ONEX declarative pattern:
7
+ - DECLARATIVE node driven by contract.yaml
8
+ - Subscribes to 7 platform topics via contract configuration
9
+ - Transforms events to ledger append intents
10
+
11
+ Design Rationale - Best-Effort Metadata Extraction:
12
+ The audit ledger serves as the system's source of truth. Events must NEVER
13
+ be dropped due to metadata extraction failures. All metadata fields are
14
+ extracted best-effort - parsing errors result in None/empty values, not
15
+ exceptions. Only a missing event_value (the raw bytes) causes an error.
16
+
17
+ Bytes Encoding:
18
+ Kafka event keys and values are bytes. Since bytes cannot safely cross
19
+ intent boundaries (serialization issues), they are base64-encoded at this
20
+ transform layer. The Effect layer decodes before storage.
21
+
22
+ Subscribed Topics:
23
+ - onex.evt.platform.node-registration.v1
24
+ - onex.evt.platform.node-introspection.v1
25
+ - onex.evt.platform.node-heartbeat.v1
26
+ - onex.cmd.platform.request-introspection.v1
27
+ - onex.evt.platform.fsm-state-transitions.v1
28
+ - onex.intent.platform.runtime-tick.v1
29
+ - onex.snapshot.platform.registration-snapshots.v1
30
+
31
+ Ticket: OMN-1648
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import base64
37
+ import logging
38
+ from typing import TYPE_CHECKING
39
+
40
+ from omnibase_core.errors import OnexError
41
+ from omnibase_core.models.reducer.model_intent import ModelIntent
42
+ from omnibase_core.nodes.node_compute import NodeCompute
43
+ from omnibase_core.types import JsonType
44
+ from omnibase_infra.event_bus.models.model_event_headers import ModelEventHeaders
45
+ from omnibase_infra.event_bus.models.model_event_message import ModelEventMessage
46
+ from omnibase_infra.nodes.reducers.models.model_payload_ledger_append import (
47
+ ModelPayloadLedgerAppend,
48
+ )
49
+
50
+ if TYPE_CHECKING:
51
+ from uuid import UUID
52
+
53
+ from omnibase_core.container import ModelONEXContainer
54
+
55
+ logger = logging.getLogger(__name__)
56
+
57
+
58
+ class NodeLedgerProjectionCompute(NodeCompute):
59
+ """COMPUTE node that extracts metadata from platform events for ledger persistence.
60
+
61
+ Declarative node - subscribes to 7 platform topics via contract.yaml.
62
+ Transforms ModelEventMessage -> ModelPayloadLedgerAppend -> ModelIntent.
63
+
64
+ This node implements the ONEX ledger projection pattern:
65
+ 1. Receives raw Kafka events as ModelEventMessage
66
+ 2. Extracts metadata best-effort (never fails on parse errors)
67
+ 3. Base64-encodes bytes for safe intent serialization
68
+ 4. Emits ModelIntent with "ledger.append" payload for Effect layer
69
+
70
+ CRITICAL INVARIANTS:
71
+ - NEVER drop events due to metadata extraction failure
72
+ - event_value is REQUIRED (raises OnexError if None)
73
+ - correlation_id and other metadata are OPTIONAL
74
+ - Best-effort extraction - parsing errors yield None, not exceptions
75
+
76
+ Attributes:
77
+ container: ONEX dependency injection container.
78
+
79
+ Example:
80
+ ```python
81
+ from omnibase_core.container import ModelONEXContainer
82
+
83
+ container = ModelONEXContainer()
84
+ node = NodeLedgerProjectionCompute(container)
85
+
86
+ # Transform event to ledger intent
87
+ message = ModelEventMessage(
88
+ topic="agent.routing.completed.v1",
89
+ value=b'{"agent": "code-quality"}',
90
+ headers=headers,
91
+ partition=0,
92
+ offset="42",
93
+ )
94
+ intent = node.compute(message)
95
+ # intent.payload.intent_type == "ledger.append"
96
+ ```
97
+ """
98
+
99
+ def __init__(self, container: ModelONEXContainer) -> None:
100
+ """Initialize the ledger projection compute node.
101
+
102
+ Args:
103
+ container: ONEX dependency injection container.
104
+ """
105
+ super().__init__(container)
106
+
107
+ def compute(self, message: ModelEventMessage) -> ModelIntent:
108
+ """Transform event message to ledger append intent.
109
+
110
+ Extracts metadata from the incoming Kafka event and produces a
111
+ ModelIntent with a ModelPayloadLedgerAppend payload for the Effect
112
+ layer to persist to PostgreSQL.
113
+
114
+ Args:
115
+ message: The incoming Kafka event message to transform.
116
+
117
+ Returns:
118
+ ModelIntent with intent_type="extension" containing the ledger
119
+ append payload for the Effect layer.
120
+
121
+ Raises:
122
+ OnexError: If message.value is None (event body is required).
123
+
124
+ INVARIANTS:
125
+ - Never drop events due to metadata extraction failure
126
+ - event_value is REQUIRED (raises OnexError if None)
127
+ - correlation_id is optional
128
+ """
129
+ payload = self._extract_ledger_metadata(message)
130
+ return ModelIntent(
131
+ intent_type="extension",
132
+ target=f"postgres://event_ledger/{payload.topic}/{payload.partition}/{payload.kafka_offset}",
133
+ payload=payload,
134
+ )
135
+
136
+ def _b64(self, b: bytes | None) -> str | None:
137
+ """Base64 encode bytes, returning None for None input.
138
+
139
+ Args:
140
+ b: Bytes to encode, or None.
141
+
142
+ Returns:
143
+ Base64-encoded string, or None if input was None.
144
+ """
145
+ if b is None:
146
+ return None
147
+ return base64.b64encode(b).decode("ascii")
148
+
149
+ def _normalize_headers(
150
+ self, headers: ModelEventHeaders | None
151
+ ) -> dict[str, JsonType]:
152
+ """Convert event headers to JSON-safe dictionary.
153
+
154
+ Uses Pydantic's model_dump with mode="json" to ensure all values
155
+ are JSON-serializable. Returns empty dict for None input.
156
+
157
+ Args:
158
+ headers: Event headers model, or None.
159
+
160
+ Returns:
161
+ JSON-safe dictionary of header values, or empty dict.
162
+ """
163
+ if headers is None:
164
+ return {}
165
+ try:
166
+ return headers.model_dump(mode="json")
167
+ except Exception:
168
+ # Best-effort: try to get correlation_id for logging context
169
+ correlation_id = getattr(headers, "correlation_id", None)
170
+ logger.warning(
171
+ "Failed to serialize event headers, returning empty dict. "
172
+ "correlation_id=%s",
173
+ correlation_id,
174
+ exc_info=True,
175
+ )
176
+ return {}
177
+
178
+ def _parse_offset(
179
+ self, offset: str | None, correlation_id: UUID | None = None
180
+ ) -> int:
181
+ """Parse Kafka offset string to integer.
182
+
183
+ Args:
184
+ offset: Offset string from Kafka, or None.
185
+ correlation_id: Optional correlation ID for logging context.
186
+
187
+ Returns:
188
+ Parsed offset as integer, or 0 if None or unparseable.
189
+ """
190
+ if offset is None:
191
+ return 0
192
+ try:
193
+ return int(offset)
194
+ except (ValueError, TypeError):
195
+ logger.warning(
196
+ "Failed to parse offset '%s' as integer, defaulting to 0. "
197
+ "correlation_id=%s",
198
+ offset,
199
+ correlation_id,
200
+ )
201
+ return 0
202
+
203
+ def _extract_ledger_metadata(
204
+ self, message: ModelEventMessage
205
+ ) -> ModelPayloadLedgerAppend:
206
+ """Extract ledger metadata from event message.
207
+
208
+ Main extraction logic that transforms a ModelEventMessage into a
209
+ ModelPayloadLedgerAppend. Uses best-effort extraction for all
210
+ metadata fields - only event_value being None causes an error.
211
+
212
+ Args:
213
+ message: The event message to extract metadata from.
214
+
215
+ Returns:
216
+ Populated ledger append payload ready for the Effect layer.
217
+
218
+ Raises:
219
+ OnexError: If message.value is None.
220
+
221
+ Field Mapping:
222
+ | Payload Field | Source | Required |
223
+ |------------------|---------------------------------|----------|
224
+ | topic | message.topic | YES |
225
+ | partition | message.partition | YES* |
226
+ | kafka_offset | message.offset | YES* |
227
+ | event_key | base64(message.key) | NO |
228
+ | event_value | base64(message.value) | YES |
229
+ | correlation_id | message.headers.correlation_id | NO |
230
+ | event_type | message.headers.event_type | NO |
231
+ | source | message.headers.source | NO |
232
+ | envelope_id | message.headers.message_id | NO |
233
+ | event_timestamp | message.headers.timestamp | NO |
234
+ | onex_headers | headers.model_dump(mode="json") | NO |
235
+
236
+ * Defaults to 0 if not available (for consumed messages, these
237
+ should always be present, but we handle None defensively).
238
+ """
239
+ # CRITICAL: event_value is required - this is the only case where we raise
240
+ if message.value is None:
241
+ raise OnexError(
242
+ "Cannot create ledger entry: message.value is None. "
243
+ "Event body is required for audit ledger persistence."
244
+ )
245
+
246
+ # Base64 encode the raw bytes
247
+ event_value_b64 = self._b64(message.value)
248
+ # Defensive check - _b64 should never return None for non-None input
249
+ if event_value_b64 is None:
250
+ raise OnexError(
251
+ "Unexpected: base64 encoding of message.value returned None. "
252
+ "This should never happen for non-None bytes input."
253
+ )
254
+
255
+ event_key_b64 = self._b64(message.key)
256
+
257
+ # Extract headers best-effort
258
+ headers = message.headers
259
+ # Extract correlation_id early for logging context in helper methods
260
+ correlation_id = headers.correlation_id if headers else None
261
+ onex_headers = self._normalize_headers(headers)
262
+
263
+ # Build payload with best-effort metadata extraction
264
+ return ModelPayloadLedgerAppend(
265
+ # Required Kafka position fields (defensive defaults for None)
266
+ topic=message.topic,
267
+ partition=message.partition if message.partition is not None else 0,
268
+ kafka_offset=self._parse_offset(
269
+ message.offset, correlation_id=correlation_id
270
+ ),
271
+ # Raw event data as base64
272
+ event_key=event_key_b64,
273
+ event_value=event_value_b64,
274
+ # Extracted metadata (all optional, best-effort)
275
+ onex_headers=onex_headers,
276
+ correlation_id=correlation_id,
277
+ envelope_id=headers.message_id if headers else None,
278
+ event_type=headers.event_type if headers else None,
279
+ source=headers.source if headers else None,
280
+ event_timestamp=headers.timestamp if headers else None,
281
+ )
282
+
283
+
284
+ __all__ = ["NodeLedgerProjectionCompute"]
@@ -0,0 +1,29 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 OmniNode Team
3
+ """Registry module for NodeLedgerProjectionCompute.
4
+
5
+ This module provides the RegistryInfraLedgerProjection class for
6
+ dependency injection registration and factory methods.
7
+
8
+ Usage:
9
+ ```python
10
+ from omnibase_infra.nodes.node_ledger_projection_compute.registry import (
11
+ RegistryInfraLedgerProjection,
12
+ )
13
+
14
+ # Get node class for DI resolution
15
+ node_class = RegistryInfraLedgerProjection.get_node_class()
16
+
17
+ # Create node instance
18
+ node = RegistryInfraLedgerProjection.create_node(container)
19
+ ```
20
+
21
+ Related Tickets:
22
+ - OMN-1648: Ledger Projection Compute Node
23
+ """
24
+
25
+ from omnibase_infra.nodes.node_ledger_projection_compute.registry.registry_infra_ledger_projection import (
26
+ RegistryInfraLedgerProjection,
27
+ )
28
+
29
+ __all__ = ["RegistryInfraLedgerProjection"]
@@ -0,0 +1,118 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 OmniNode Team
3
+ """Registry for NodeLedgerProjectionCompute - DI bindings and exports.
4
+
5
+ This registry provides factory methods and dependency injection bindings for
6
+ the NodeLedgerProjectionCompute. It follows the ONEX registry pattern with
7
+ the naming convention ``RegistryInfra<NodeName>``.
8
+
9
+ Node Purpose:
10
+ NodeLedgerProjectionCompute is a COMPUTE node that transforms platform
11
+ events (ModelEventMessage) into ledger append intents (ModelPayloadLedgerAppend)
12
+ for persistence by the Effect layer.
13
+
14
+ Usage:
15
+ ```python
16
+ from omnibase_infra.nodes.node_ledger_projection_compute.registry import (
17
+ RegistryInfraLedgerProjection,
18
+ )
19
+
20
+ # Get the node class for DI resolution
21
+ node_class = RegistryInfraLedgerProjection.get_node_class()
22
+
23
+ # Create node instance with container
24
+ from omnibase_core.container import ModelONEXContainer
25
+ container = ModelONEXContainer()
26
+ node = RegistryInfraLedgerProjection.create_node(container)
27
+ ```
28
+
29
+ Related Tickets:
30
+ - OMN-1648: Ledger Projection Compute Node
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ from typing import TYPE_CHECKING
36
+
37
+ from omnibase_infra.nodes.node_ledger_projection_compute.node import (
38
+ NodeLedgerProjectionCompute,
39
+ )
40
+
41
+ if TYPE_CHECKING:
42
+ from omnibase_core.container import ModelONEXContainer
43
+
44
+
45
+ class RegistryInfraLedgerProjection:
46
+ """DI registry for ledger projection compute node.
47
+
48
+ Provides factory methods and bindings for the NodeLedgerProjectionCompute.
49
+ This registry follows the ONEX registry pattern for infrastructure nodes.
50
+
51
+ Why a Registry Class?
52
+ ONEX convention requires registry classes (not modules) for:
53
+ - Centralized factory methods for node creation
54
+ - Type-safe DI resolution via get_node_class()
55
+ - Extensibility for subclassing specialized registries
56
+ - Consistent interface across all node registries
57
+
58
+ Example:
59
+ ```python
60
+ from omnibase_core.container import ModelONEXContainer
61
+ from omnibase_infra.nodes.node_ledger_projection_compute.registry import (
62
+ RegistryInfraLedgerProjection,
63
+ )
64
+
65
+ container = ModelONEXContainer()
66
+ node = RegistryInfraLedgerProjection.create_node(container)
67
+ intent = node.compute(event_message)
68
+ ```
69
+ """
70
+
71
+ @staticmethod
72
+ def get_node_class() -> type[NodeLedgerProjectionCompute]:
73
+ """Return the node class for DI resolution.
74
+
75
+ This method enables DI containers to resolve the node class type
76
+ without importing the node module directly, supporting lazy loading
77
+ and circular import prevention.
78
+
79
+ Returns:
80
+ The NodeLedgerProjectionCompute class type.
81
+
82
+ Example:
83
+ ```python
84
+ node_class = RegistryInfraLedgerProjection.get_node_class()
85
+ assert node_class is NodeLedgerProjectionCompute
86
+ ```
87
+ """
88
+ return NodeLedgerProjectionCompute
89
+
90
+ @staticmethod
91
+ def create_node(container: ModelONEXContainer) -> NodeLedgerProjectionCompute:
92
+ """Create a NodeLedgerProjectionCompute instance with the given container.
93
+
94
+ Factory method for creating properly-configured node instances.
95
+ Prefer this method over direct instantiation for consistency
96
+ and future extensibility.
97
+
98
+ Args:
99
+ container: ONEX dependency injection container.
100
+
101
+ Returns:
102
+ Configured NodeLedgerProjectionCompute instance.
103
+
104
+ Example:
105
+ ```python
106
+ from omnibase_core.container import ModelONEXContainer
107
+
108
+ container = ModelONEXContainer()
109
+ node = RegistryInfraLedgerProjection.create_node(container)
110
+ ```
111
+ """
112
+ return NodeLedgerProjectionCompute(container)
113
+
114
+
115
+ __all__ = [
116
+ "NodeLedgerProjectionCompute",
117
+ "RegistryInfraLedgerProjection",
118
+ ]
@@ -0,0 +1,82 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 OmniNode Team
3
+ """Node Ledger Write Effect - Capability-oriented event ledger write node.
4
+
5
+ This package provides the NodeLedgerWriteEffect, a capability-oriented
6
+ effect node for event ledger write operations using PostgreSQL.
7
+
8
+ Core Principle:
9
+ "I'm interested in what you do, not what you are"
10
+
11
+ Named by capability (ledger.write), not by specific backend implementation.
12
+ Uses PostgreSQL for append-only event ledger storage with idempotent writes.
13
+
14
+ Capabilities:
15
+ - ledger.write: Append events to the audit ledger
16
+ - ledger.query: Query events from the audit ledger
17
+
18
+ Event Topics:
19
+ Consumed:
20
+ - {env}.{namespace}.onex.cmd.ledger-append.v1
21
+ - {env}.{namespace}.onex.cmd.ledger-query.v1
22
+ Published:
23
+ - {env}.{namespace}.onex.evt.ledger-appended.v1
24
+ - {env}.{namespace}.onex.evt.ledger-query-result.v1
25
+
26
+ Available Exports:
27
+ - NodeLedgerWriteEffect: The declarative effect node
28
+ - ModelLedgerEntry: Single ledger entry representing one event
29
+ - ModelLedgerAppendResult: Result of a ledger write operation
30
+ - ModelLedgerQuery: Query parameters for ledger searches
31
+ - ModelLedgerQueryResult: Result of a ledger query operation
32
+
33
+ Example:
34
+ >>> from omnibase_core.models.container import ModelONEXContainer
35
+ >>> from omnibase_infra.nodes.node_ledger_write_effect import (
36
+ ... NodeLedgerWriteEffect,
37
+ ... ModelLedgerEntry,
38
+ ... ModelLedgerAppendResult,
39
+ ... )
40
+ >>>
41
+ >>> container = ModelONEXContainer()
42
+ >>> node = NodeLedgerWriteEffect(container)
43
+
44
+ Related Modules:
45
+ - models: Pydantic models for ledger operations
46
+ - registry: Dependency injection registration
47
+ """
48
+
49
+ from omnibase_infra.nodes.node_ledger_write_effect.handlers import (
50
+ HandlerLedgerAppend,
51
+ HandlerLedgerQuery,
52
+ )
53
+ from omnibase_infra.nodes.node_ledger_write_effect.models import (
54
+ ModelLedgerAppendResult,
55
+ ModelLedgerEntry,
56
+ ModelLedgerQuery,
57
+ ModelLedgerQueryResult,
58
+ )
59
+ from omnibase_infra.nodes.node_ledger_write_effect.node import NodeLedgerWriteEffect
60
+ from omnibase_infra.nodes.node_ledger_write_effect.protocols import (
61
+ ProtocolLedgerPersistence,
62
+ )
63
+ from omnibase_infra.nodes.node_ledger_write_effect.registry import (
64
+ RegistryInfraLedgerWrite,
65
+ )
66
+
67
+ __all__ = [
68
+ # Node
69
+ "NodeLedgerWriteEffect",
70
+ # Handlers
71
+ "HandlerLedgerAppend",
72
+ "HandlerLedgerQuery",
73
+ # Protocol
74
+ "ProtocolLedgerPersistence",
75
+ # Registry
76
+ "RegistryInfraLedgerWrite",
77
+ # Models
78
+ "ModelLedgerAppendResult",
79
+ "ModelLedgerEntry",
80
+ "ModelLedgerQuery",
81
+ "ModelLedgerQueryResult",
82
+ ]