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.
- omnibase_infra/constants_topic_patterns.py +26 -0
- omnibase_infra/enums/__init__.py +3 -0
- omnibase_infra/enums/enum_consumer_group_purpose.py +92 -0
- omnibase_infra/enums/enum_handler_source_mode.py +16 -2
- omnibase_infra/errors/__init__.py +4 -0
- omnibase_infra/errors/error_binding_resolution.py +128 -0
- omnibase_infra/event_bus/configs/kafka_event_bus_config.yaml +0 -2
- omnibase_infra/event_bus/event_bus_inmemory.py +64 -10
- omnibase_infra/event_bus/event_bus_kafka.py +105 -47
- omnibase_infra/event_bus/mixin_kafka_broadcast.py +3 -7
- omnibase_infra/event_bus/mixin_kafka_dlq.py +12 -6
- omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +0 -81
- omnibase_infra/event_bus/testing/__init__.py +26 -0
- omnibase_infra/event_bus/testing/adapter_protocol_event_publisher_inmemory.py +418 -0
- omnibase_infra/event_bus/testing/model_publisher_metrics.py +64 -0
- omnibase_infra/handlers/handler_consul.py +2 -0
- omnibase_infra/handlers/mixins/__init__.py +5 -0
- omnibase_infra/handlers/mixins/mixin_consul_service.py +274 -10
- omnibase_infra/handlers/mixins/mixin_consul_topic_index.py +585 -0
- omnibase_infra/handlers/models/model_filesystem_config.py +4 -4
- omnibase_infra/migrations/001_create_event_ledger.sql +166 -0
- omnibase_infra/migrations/001_drop_event_ledger.sql +18 -0
- omnibase_infra/mixins/mixin_node_introspection.py +189 -19
- omnibase_infra/models/__init__.py +8 -0
- omnibase_infra/models/bindings/__init__.py +59 -0
- omnibase_infra/models/bindings/constants.py +144 -0
- omnibase_infra/models/bindings/model_binding_resolution_result.py +103 -0
- omnibase_infra/models/bindings/model_operation_binding.py +44 -0
- omnibase_infra/models/bindings/model_operation_bindings_subcontract.py +152 -0
- omnibase_infra/models/bindings/model_parsed_binding.py +52 -0
- omnibase_infra/models/discovery/model_introspection_config.py +25 -17
- omnibase_infra/models/dispatch/__init__.py +8 -0
- omnibase_infra/models/dispatch/model_debug_trace_snapshot.py +114 -0
- omnibase_infra/models/dispatch/model_materialized_dispatch.py +141 -0
- omnibase_infra/models/handlers/model_handler_source_config.py +1 -1
- omnibase_infra/models/model_node_identity.py +126 -0
- omnibase_infra/models/projection/model_snapshot_topic_config.py +3 -2
- omnibase_infra/models/registration/__init__.py +9 -0
- omnibase_infra/models/registration/model_event_bus_topic_entry.py +59 -0
- omnibase_infra/models/registration/model_node_event_bus_config.py +99 -0
- omnibase_infra/models/registration/model_node_introspection_event.py +11 -0
- omnibase_infra/models/runtime/__init__.py +9 -0
- omnibase_infra/models/validation/model_coverage_metrics.py +2 -2
- omnibase_infra/nodes/__init__.py +9 -0
- omnibase_infra/nodes/contract_registry_reducer/__init__.py +29 -0
- omnibase_infra/nodes/contract_registry_reducer/contract.yaml +255 -0
- omnibase_infra/nodes/contract_registry_reducer/models/__init__.py +38 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_contract_registry_state.py +266 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_cleanup_topic_references.py +55 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_deactivate_contract.py +58 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_mark_stale.py +49 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_heartbeat.py +71 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_topic.py +66 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_upsert_contract.py +92 -0
- omnibase_infra/nodes/contract_registry_reducer/node.py +121 -0
- omnibase_infra/nodes/contract_registry_reducer/reducer.py +784 -0
- omnibase_infra/nodes/contract_registry_reducer/registry/__init__.py +9 -0
- omnibase_infra/nodes/contract_registry_reducer/registry/registry_infra_contract_registry_reducer.py +101 -0
- omnibase_infra/nodes/handlers/consul/contract.yaml +85 -0
- omnibase_infra/nodes/handlers/db/contract.yaml +72 -0
- omnibase_infra/nodes/handlers/graph/contract.yaml +127 -0
- omnibase_infra/nodes/handlers/http/contract.yaml +74 -0
- omnibase_infra/nodes/handlers/intent/contract.yaml +66 -0
- omnibase_infra/nodes/handlers/mcp/contract.yaml +69 -0
- omnibase_infra/nodes/handlers/vault/contract.yaml +91 -0
- omnibase_infra/nodes/node_ledger_projection_compute/__init__.py +50 -0
- omnibase_infra/nodes/node_ledger_projection_compute/contract.yaml +104 -0
- omnibase_infra/nodes/node_ledger_projection_compute/node.py +284 -0
- omnibase_infra/nodes/node_ledger_projection_compute/registry/__init__.py +29 -0
- omnibase_infra/nodes/node_ledger_projection_compute/registry/registry_infra_ledger_projection.py +118 -0
- omnibase_infra/nodes/node_ledger_write_effect/__init__.py +82 -0
- omnibase_infra/nodes/node_ledger_write_effect/contract.yaml +200 -0
- omnibase_infra/nodes/node_ledger_write_effect/handlers/__init__.py +22 -0
- omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_append.py +372 -0
- omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_query.py +597 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/__init__.py +31 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_append_result.py +54 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_entry.py +92 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query.py +53 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query_result.py +41 -0
- omnibase_infra/nodes/node_ledger_write_effect/node.py +89 -0
- omnibase_infra/nodes/node_ledger_write_effect/protocols/__init__.py +13 -0
- omnibase_infra/nodes/node_ledger_write_effect/protocols/protocol_ledger_persistence.py +127 -0
- omnibase_infra/nodes/node_ledger_write_effect/registry/__init__.py +9 -0
- omnibase_infra/nodes/node_ledger_write_effect/registry/registry_infra_ledger_write.py +121 -0
- omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +7 -5
- omnibase_infra/nodes/reducers/models/__init__.py +7 -2
- omnibase_infra/nodes/reducers/models/model_payload_consul_register.py +11 -0
- omnibase_infra/nodes/reducers/models/model_payload_ledger_append.py +133 -0
- omnibase_infra/nodes/reducers/registration_reducer.py +1 -0
- omnibase_infra/protocols/__init__.py +3 -0
- omnibase_infra/protocols/protocol_dispatch_engine.py +152 -0
- omnibase_infra/runtime/__init__.py +60 -0
- omnibase_infra/runtime/binding_resolver.py +753 -0
- omnibase_infra/runtime/constants_security.py +70 -0
- omnibase_infra/runtime/contract_loaders/__init__.py +9 -0
- omnibase_infra/runtime/contract_loaders/operation_bindings_loader.py +789 -0
- omnibase_infra/runtime/emit_daemon/__init__.py +97 -0
- omnibase_infra/runtime/emit_daemon/cli.py +844 -0
- omnibase_infra/runtime/emit_daemon/client.py +811 -0
- omnibase_infra/runtime/emit_daemon/config.py +535 -0
- omnibase_infra/runtime/emit_daemon/daemon.py +812 -0
- omnibase_infra/runtime/emit_daemon/event_registry.py +477 -0
- omnibase_infra/runtime/emit_daemon/model_daemon_request.py +139 -0
- omnibase_infra/runtime/emit_daemon/model_daemon_response.py +191 -0
- omnibase_infra/runtime/emit_daemon/queue.py +618 -0
- omnibase_infra/runtime/event_bus_subcontract_wiring.py +466 -0
- omnibase_infra/runtime/handler_source_resolver.py +43 -2
- omnibase_infra/runtime/kafka_contract_source.py +984 -0
- omnibase_infra/runtime/models/__init__.py +13 -0
- omnibase_infra/runtime/models/model_contract_load_result.py +224 -0
- omnibase_infra/runtime/models/model_runtime_contract_config.py +268 -0
- omnibase_infra/runtime/models/model_runtime_scheduler_config.py +4 -3
- omnibase_infra/runtime/models/model_security_config.py +109 -0
- omnibase_infra/runtime/publisher_topic_scoped.py +294 -0
- omnibase_infra/runtime/runtime_contract_config_loader.py +406 -0
- omnibase_infra/runtime/service_kernel.py +76 -6
- omnibase_infra/runtime/service_message_dispatch_engine.py +558 -15
- omnibase_infra/runtime/service_runtime_host_process.py +770 -20
- omnibase_infra/runtime/transition_notification_publisher.py +3 -2
- omnibase_infra/runtime/util_wiring.py +206 -62
- omnibase_infra/services/mcp/service_mcp_tool_sync.py +27 -9
- omnibase_infra/services/session/config_consumer.py +25 -8
- omnibase_infra/services/session/config_store.py +2 -2
- omnibase_infra/services/session/consumer.py +1 -1
- omnibase_infra/topics/__init__.py +45 -0
- omnibase_infra/topics/platform_topic_suffixes.py +140 -0
- omnibase_infra/topics/util_topic_composition.py +95 -0
- omnibase_infra/types/typed_dict/__init__.py +9 -1
- omnibase_infra/types/typed_dict/typed_dict_envelope_build_params.py +115 -0
- omnibase_infra/utils/__init__.py +9 -0
- omnibase_infra/utils/util_consumer_group.py +232 -0
- omnibase_infra/validation/infra_validators.py +18 -1
- omnibase_infra/validation/validation_exemptions.yaml +192 -0
- {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/METADATA +3 -3
- {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/RECORD +139 -52
- {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/entry_points.txt +1 -0
- {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/WHEEL +0 -0
- {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"]
|
omnibase_infra/nodes/node_ledger_projection_compute/registry/registry_infra_ledger_projection.py
ADDED
|
@@ -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
|
+
]
|