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,92 @@
|
|
|
1
|
+
"""Ledger entry model for event ledger storage.
|
|
2
|
+
|
|
3
|
+
This module defines the data structure for a single event ledger entry,
|
|
4
|
+
representing one row in the event_ledger table.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from uuid import UUID
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
11
|
+
|
|
12
|
+
from omnibase_core.types import JsonType
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ModelLedgerEntry(BaseModel):
|
|
16
|
+
"""Represents a single event ledger entry (one row in event_ledger table).
|
|
17
|
+
|
|
18
|
+
This model captures the complete state of an event as it was received
|
|
19
|
+
from Kafka, including the raw payload, Kafka position metadata, and
|
|
20
|
+
extracted envelope fields for queryability.
|
|
21
|
+
|
|
22
|
+
All extracted metadata fields are nullable because:
|
|
23
|
+
1. Events may not conform to the ONEX envelope schema
|
|
24
|
+
2. Malformed events should still be ledgered for debugging
|
|
25
|
+
3. Legacy events may lack certain fields
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
29
|
+
|
|
30
|
+
ledger_entry_id: UUID = Field(
|
|
31
|
+
...,
|
|
32
|
+
description="Unique identifier for this ledger entry",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Kafka position - uniquely identifies the event in the stream
|
|
36
|
+
topic: str = Field(
|
|
37
|
+
...,
|
|
38
|
+
min_length=1,
|
|
39
|
+
description="Kafka topic from which the event was consumed",
|
|
40
|
+
)
|
|
41
|
+
partition: int = Field(
|
|
42
|
+
...,
|
|
43
|
+
ge=0,
|
|
44
|
+
description="Kafka partition number",
|
|
45
|
+
)
|
|
46
|
+
kafka_offset: int = Field(
|
|
47
|
+
...,
|
|
48
|
+
ge=0,
|
|
49
|
+
description="Kafka offset within the partition",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Raw event data (base64 encoded for transport)
|
|
53
|
+
event_key: str | None = Field(
|
|
54
|
+
default=None,
|
|
55
|
+
description="Base64-encoded Kafka message key, if present",
|
|
56
|
+
)
|
|
57
|
+
event_value: str = Field(
|
|
58
|
+
...,
|
|
59
|
+
description="Base64-encoded Kafka message value (the event payload)",
|
|
60
|
+
)
|
|
61
|
+
onex_headers: dict[str, JsonType] = Field(
|
|
62
|
+
default_factory=dict,
|
|
63
|
+
description="ONEX-specific headers extracted from Kafka headers",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Extracted metadata (ALL NULLABLE for schema flexibility)
|
|
67
|
+
envelope_id: UUID | None = Field(
|
|
68
|
+
default=None,
|
|
69
|
+
description="Extracted envelope ID from the event, if present",
|
|
70
|
+
)
|
|
71
|
+
correlation_id: UUID | None = Field(
|
|
72
|
+
default=None,
|
|
73
|
+
description="Extracted correlation ID for request tracing",
|
|
74
|
+
)
|
|
75
|
+
event_type: str | None = Field(
|
|
76
|
+
default=None,
|
|
77
|
+
description="Extracted event type identifier",
|
|
78
|
+
)
|
|
79
|
+
source: str | None = Field(
|
|
80
|
+
default=None,
|
|
81
|
+
description="Extracted source system or service identifier",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Timestamps
|
|
85
|
+
event_timestamp: datetime | None = Field(
|
|
86
|
+
default=None,
|
|
87
|
+
description="Original event timestamp from the envelope, if present",
|
|
88
|
+
)
|
|
89
|
+
ledger_written_at: datetime = Field(
|
|
90
|
+
...,
|
|
91
|
+
description="Timestamp when this entry was written to the ledger",
|
|
92
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Ledger query model for searching ledger entries.
|
|
2
|
+
|
|
3
|
+
This module defines the query parameters for ledger search operations.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ModelLedgerQuery(BaseModel):
|
|
13
|
+
"""Query parameters for ledger searches.
|
|
14
|
+
|
|
15
|
+
All filter fields are optional - omitting a field means no filtering
|
|
16
|
+
on that dimension. Multiple filters are combined with AND logic.
|
|
17
|
+
|
|
18
|
+
The limit and offset fields enable pagination through large result sets.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
22
|
+
|
|
23
|
+
correlation_id: UUID | None = Field(
|
|
24
|
+
default=None,
|
|
25
|
+
description="Filter by correlation ID for request tracing",
|
|
26
|
+
)
|
|
27
|
+
event_type: str | None = Field(
|
|
28
|
+
default=None,
|
|
29
|
+
description="Filter by event type identifier",
|
|
30
|
+
)
|
|
31
|
+
topic: str | None = Field(
|
|
32
|
+
default=None,
|
|
33
|
+
description="Filter by Kafka topic",
|
|
34
|
+
)
|
|
35
|
+
start_time: datetime | None = Field(
|
|
36
|
+
default=None,
|
|
37
|
+
description="Filter events at or after this timestamp",
|
|
38
|
+
)
|
|
39
|
+
end_time: datetime | None = Field(
|
|
40
|
+
default=None,
|
|
41
|
+
description="Filter events before this timestamp",
|
|
42
|
+
)
|
|
43
|
+
limit: int = Field(
|
|
44
|
+
default=100,
|
|
45
|
+
ge=1,
|
|
46
|
+
le=10000,
|
|
47
|
+
description="Maximum number of entries to return",
|
|
48
|
+
)
|
|
49
|
+
offset: int = Field(
|
|
50
|
+
default=0,
|
|
51
|
+
ge=0,
|
|
52
|
+
description="Number of entries to skip for pagination",
|
|
53
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Ledger query result model for search responses.
|
|
2
|
+
|
|
3
|
+
This module defines the result structure returned by ledger search operations.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
7
|
+
|
|
8
|
+
from omnibase_infra.nodes.node_ledger_write_effect.models.model_ledger_entry import (
|
|
9
|
+
ModelLedgerEntry,
|
|
10
|
+
)
|
|
11
|
+
from omnibase_infra.nodes.node_ledger_write_effect.models.model_ledger_query import (
|
|
12
|
+
ModelLedgerQuery,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ModelLedgerQueryResult(BaseModel):
|
|
17
|
+
"""Result of a ledger query operation.
|
|
18
|
+
|
|
19
|
+
Contains the matching entries along with pagination metadata
|
|
20
|
+
and the original query for reference.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
24
|
+
|
|
25
|
+
entries: list[ModelLedgerEntry] = Field(
|
|
26
|
+
...,
|
|
27
|
+
description="List of matching ledger entries",
|
|
28
|
+
)
|
|
29
|
+
total_count: int = Field(
|
|
30
|
+
...,
|
|
31
|
+
ge=0,
|
|
32
|
+
description="Total number of entries matching the query (before pagination)",
|
|
33
|
+
)
|
|
34
|
+
has_more: bool = Field(
|
|
35
|
+
...,
|
|
36
|
+
description="True if more entries exist beyond the current page",
|
|
37
|
+
)
|
|
38
|
+
query: ModelLedgerQuery = Field(
|
|
39
|
+
...,
|
|
40
|
+
description="The query parameters used to generate this result",
|
|
41
|
+
)
|
|
@@ -0,0 +1,89 @@
|
|
|
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 effect node provides event ledger write capabilities using PostgreSQL.
|
|
6
|
+
Named by capability ("ledger.write"), not by vendor (e.g., PostgreSQL).
|
|
7
|
+
|
|
8
|
+
Core Principle:
|
|
9
|
+
"I'm interested in what you do, not what you are"
|
|
10
|
+
|
|
11
|
+
This node follows the ONEX declarative pattern:
|
|
12
|
+
- DECLARATIVE effect driven by contract.yaml
|
|
13
|
+
- Zero custom storage logic - all behavior from handler
|
|
14
|
+
- Lightweight shell that delegates to handler implementation
|
|
15
|
+
- Pattern: "Contract-driven, handlers wired externally"
|
|
16
|
+
|
|
17
|
+
Extends NodeEffect from omnibase_core for external I/O operations.
|
|
18
|
+
All storage logic is 100% driven by handler implementations, not Python code.
|
|
19
|
+
|
|
20
|
+
Capabilities:
|
|
21
|
+
- ledger.write: Append events to the audit ledger
|
|
22
|
+
- ledger.query: Query events from the audit ledger
|
|
23
|
+
|
|
24
|
+
Event Topics:
|
|
25
|
+
Consumed:
|
|
26
|
+
- {env}.{namespace}.onex.cmd.ledger-append.v1
|
|
27
|
+
- {env}.{namespace}.onex.cmd.ledger-query.v1
|
|
28
|
+
Published:
|
|
29
|
+
- {env}.{namespace}.onex.evt.ledger-appended.v1
|
|
30
|
+
- {env}.{namespace}.onex.evt.ledger-query-result.v1
|
|
31
|
+
|
|
32
|
+
Design Decisions:
|
|
33
|
+
- 100% Contract-Driven: All capabilities in YAML, not Python
|
|
34
|
+
- Zero Custom Methods: Base class handles everything
|
|
35
|
+
- Declarative Execution: Handler wired externally
|
|
36
|
+
- Capability-Oriented: Named by what it does, not what it uses
|
|
37
|
+
|
|
38
|
+
Related:
|
|
39
|
+
- contract.yaml: Capability definitions and IO operations
|
|
40
|
+
- models/: Input, output models (ModelPayloadLedgerAppend, ModelLedgerAppendResult)
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
from typing import TYPE_CHECKING
|
|
46
|
+
|
|
47
|
+
from omnibase_core.nodes.node_effect import NodeEffect
|
|
48
|
+
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
from omnibase_core.models.container.model_onex_container import ModelONEXContainer
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class NodeLedgerWriteEffect(NodeEffect):
|
|
54
|
+
"""Effect node for event ledger write operations.
|
|
55
|
+
|
|
56
|
+
Capability: ledger.write
|
|
57
|
+
|
|
58
|
+
Provides a capability-oriented interface for event ledger operations.
|
|
59
|
+
Uses PostgreSQL for storing events in an append-only audit ledger with
|
|
60
|
+
idempotent write support via (topic, partition, kafka_offset) constraint.
|
|
61
|
+
|
|
62
|
+
This node is declarative - all behavior is defined in contract.yaml and
|
|
63
|
+
implemented through the handler. No custom storage logic exists in this class.
|
|
64
|
+
|
|
65
|
+
The audit ledger serves as the system's source of truth for all events.
|
|
66
|
+
Events are never dropped - even malformed events are captured with raw
|
|
67
|
+
data intact for later analysis or re-processing.
|
|
68
|
+
|
|
69
|
+
Attributes:
|
|
70
|
+
container: ONEX dependency injection container
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
>>> from omnibase_core.models.container import ModelONEXContainer
|
|
74
|
+
>>> container = ModelONEXContainer()
|
|
75
|
+
>>> node = NodeLedgerWriteEffect(container)
|
|
76
|
+
>>> # Handler must be wired externally via registry
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(self, container: ModelONEXContainer) -> None:
|
|
80
|
+
"""Initialize the ledger write effect node.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
container: ONEX dependency injection container for resolving
|
|
84
|
+
dependencies defined in contract.yaml.
|
|
85
|
+
"""
|
|
86
|
+
super().__init__(container)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
__all__ = ["NodeLedgerWriteEffect"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 OmniNode Team
|
|
3
|
+
"""Protocols for event ledger persistence operations.
|
|
4
|
+
|
|
5
|
+
This package defines the protocol interfaces for ledger persistence,
|
|
6
|
+
enabling duck typing and testability for handlers.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from omnibase_infra.nodes.node_ledger_write_effect.protocols.protocol_ledger_persistence import (
|
|
10
|
+
ProtocolLedgerPersistence,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = ["ProtocolLedgerPersistence"]
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 OmniNode Team
|
|
3
|
+
"""Protocol definition for event ledger persistence operations.
|
|
4
|
+
|
|
5
|
+
This module defines the ProtocolLedgerPersistence interface for ledger
|
|
6
|
+
storage and retrieval operations. Handlers implementing this protocol
|
|
7
|
+
can be used interchangeably for testing and production.
|
|
8
|
+
|
|
9
|
+
Design Decisions:
|
|
10
|
+
- runtime_checkable: Enables isinstance() checks for duck typing
|
|
11
|
+
- Async methods: All operations are async for non-blocking I/O
|
|
12
|
+
- Typed models: Uses Pydantic models for type safety
|
|
13
|
+
- Nullable metadata: Query filters are optional (None = no filter)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from typing import TYPE_CHECKING, Protocol, runtime_checkable
|
|
20
|
+
from uuid import UUID
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from omnibase_infra.nodes.node_ledger_write_effect.models import (
|
|
24
|
+
ModelLedgerAppendResult,
|
|
25
|
+
ModelLedgerEntry,
|
|
26
|
+
)
|
|
27
|
+
from omnibase_infra.nodes.reducers.models import ModelPayloadLedgerAppend
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@runtime_checkable
|
|
31
|
+
class ProtocolLedgerPersistence(Protocol):
|
|
32
|
+
"""Protocol for event ledger persistence operations.
|
|
33
|
+
|
|
34
|
+
This protocol defines the interface for appending events to the audit
|
|
35
|
+
ledger and querying events by various criteria. Implementations must
|
|
36
|
+
provide idempotent append operations via the (topic, partition, kafka_offset)
|
|
37
|
+
unique constraint.
|
|
38
|
+
|
|
39
|
+
Implementations:
|
|
40
|
+
- HandlerLedgerAppend: Production handler composing with HandlerDb
|
|
41
|
+
- MockLedgerPersistence: Test double for unit testing
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
>>> async def process_events(
|
|
45
|
+
... persistence: ProtocolLedgerPersistence,
|
|
46
|
+
... payload: ModelPayloadLedgerAppend,
|
|
47
|
+
... ) -> ModelLedgerAppendResult:
|
|
48
|
+
... return await persistence.append(payload)
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
async def append(
|
|
52
|
+
self,
|
|
53
|
+
payload: ModelPayloadLedgerAppend,
|
|
54
|
+
) -> ModelLedgerAppendResult:
|
|
55
|
+
"""Append an event to the audit ledger with idempotent write support.
|
|
56
|
+
|
|
57
|
+
Uses INSERT ... ON CONFLICT DO NOTHING with the (topic, partition, kafka_offset)
|
|
58
|
+
unique constraint. Duplicate events are detected without raising errors.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
payload: Event payload containing Kafka position and event data.
|
|
62
|
+
The payload includes base64-encoded event_key and event_value
|
|
63
|
+
which are decoded to BYTEA for storage.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
ModelLedgerAppendResult with:
|
|
67
|
+
- success: True if operation completed without error
|
|
68
|
+
- ledger_entry_id: UUID of created entry, None if duplicate
|
|
69
|
+
- duplicate: True if ON CONFLICT was triggered
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
InfraConnectionError: If database connection fails
|
|
73
|
+
InfraTimeoutError: If operation times out
|
|
74
|
+
"""
|
|
75
|
+
...
|
|
76
|
+
|
|
77
|
+
async def query_by_correlation_id(
|
|
78
|
+
self,
|
|
79
|
+
correlation_id: UUID,
|
|
80
|
+
limit: int = 100,
|
|
81
|
+
offset: int = 0,
|
|
82
|
+
) -> list[ModelLedgerEntry]:
|
|
83
|
+
"""Query ledger entries by correlation ID for distributed tracing.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
correlation_id: The correlation ID to search for.
|
|
87
|
+
limit: Maximum number of entries to return (default: 100).
|
|
88
|
+
offset: Number of entries to skip for pagination (default: 0).
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
List of ModelLedgerEntry matching the correlation ID,
|
|
92
|
+
ordered by event_timestamp descending.
|
|
93
|
+
"""
|
|
94
|
+
...
|
|
95
|
+
|
|
96
|
+
async def query_by_time_range(
|
|
97
|
+
self,
|
|
98
|
+
start: datetime,
|
|
99
|
+
end: datetime,
|
|
100
|
+
correlation_id: UUID | None = None,
|
|
101
|
+
event_type: str | None = None,
|
|
102
|
+
topic: str | None = None,
|
|
103
|
+
limit: int = 100,
|
|
104
|
+
offset: int = 0,
|
|
105
|
+
) -> list[ModelLedgerEntry]:
|
|
106
|
+
"""Query ledger entries within a time range.
|
|
107
|
+
|
|
108
|
+
Uses COALESCE(event_timestamp, ledger_written_at) for consistent
|
|
109
|
+
ordering even when event_timestamp is NULL.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
start: Start of time range (inclusive).
|
|
113
|
+
end: End of time range (exclusive).
|
|
114
|
+
correlation_id: Correlation ID for distributed tracing (auto-generated if None).
|
|
115
|
+
event_type: Optional filter by event type.
|
|
116
|
+
topic: Optional filter by Kafka topic.
|
|
117
|
+
limit: Maximum number of entries to return (default: 100, max: 10000).
|
|
118
|
+
offset: Number of entries to skip for pagination (default: 0).
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
List of ModelLedgerEntry within the time range,
|
|
122
|
+
ordered by timestamp descending.
|
|
123
|
+
"""
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
__all__ = ["ProtocolLedgerPersistence"]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 OmniNode Team
|
|
3
|
+
"""Registry for event ledger write effect node."""
|
|
4
|
+
|
|
5
|
+
from omnibase_infra.nodes.node_ledger_write_effect.registry.registry_infra_ledger_write import (
|
|
6
|
+
RegistryInfraLedgerWrite,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = ["RegistryInfraLedgerWrite"]
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 OmniNode Team
|
|
3
|
+
"""Registry for event ledger write effect node components.
|
|
4
|
+
|
|
5
|
+
This registry exports all public symbols for the node_ledger_write_effect node,
|
|
6
|
+
providing a single import point for consumers of this node.
|
|
7
|
+
|
|
8
|
+
Exported Components:
|
|
9
|
+
- NodeLedgerWriteEffect: The declarative effect node
|
|
10
|
+
- HandlerLedgerAppend: Handler for idempotent append operations
|
|
11
|
+
- HandlerLedgerQuery: Handler for query operations
|
|
12
|
+
- ProtocolLedgerPersistence: Protocol for ledger persistence
|
|
13
|
+
- Models: Data models for ledger operations
|
|
14
|
+
|
|
15
|
+
Example:
|
|
16
|
+
>>> from omnibase_infra.nodes.node_ledger_write_effect.registry import (
|
|
17
|
+
... RegistryInfraLedgerWrite,
|
|
18
|
+
... )
|
|
19
|
+
>>> # Access all components via registry
|
|
20
|
+
>>> node_cls = RegistryInfraLedgerWrite.node
|
|
21
|
+
>>> append_handler = RegistryInfraLedgerWrite.handler_append
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
27
|
+
|
|
28
|
+
# Handlers
|
|
29
|
+
from omnibase_infra.nodes.node_ledger_write_effect.handlers import (
|
|
30
|
+
HandlerLedgerAppend,
|
|
31
|
+
HandlerLedgerQuery,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Models
|
|
35
|
+
from omnibase_infra.nodes.node_ledger_write_effect.models import (
|
|
36
|
+
ModelLedgerAppendResult,
|
|
37
|
+
ModelLedgerEntry,
|
|
38
|
+
ModelLedgerQuery,
|
|
39
|
+
ModelLedgerQueryResult,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Node
|
|
43
|
+
from omnibase_infra.nodes.node_ledger_write_effect.node import NodeLedgerWriteEffect
|
|
44
|
+
|
|
45
|
+
# Protocol
|
|
46
|
+
from omnibase_infra.nodes.node_ledger_write_effect.protocols import (
|
|
47
|
+
ProtocolLedgerPersistence,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Intent payload model (from reducers)
|
|
51
|
+
from omnibase_infra.nodes.reducers.models import ModelPayloadLedgerAppend
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class RegistryInfraLedgerWrite:
|
|
55
|
+
"""Registry providing access to all ledger write effect node components.
|
|
56
|
+
|
|
57
|
+
This class provides a centralized access point for all components of the
|
|
58
|
+
node_ledger_write_effect node. Use this registry for dependency injection
|
|
59
|
+
and container registration.
|
|
60
|
+
|
|
61
|
+
Class Attributes:
|
|
62
|
+
node: The NodeLedgerWriteEffect class
|
|
63
|
+
handler_append: The HandlerLedgerAppend class
|
|
64
|
+
handler_query: The HandlerLedgerQuery class
|
|
65
|
+
protocol: The ProtocolLedgerPersistence protocol
|
|
66
|
+
models: Tuple of all model classes
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
>>> from omnibase_infra.nodes.node_ledger_write_effect.registry import (
|
|
70
|
+
... RegistryInfraLedgerWrite,
|
|
71
|
+
... )
|
|
72
|
+
>>> # Create node instance
|
|
73
|
+
>>> node = RegistryInfraLedgerWrite.node(container)
|
|
74
|
+
>>> # Create handlers
|
|
75
|
+
>>> append_handler = RegistryInfraLedgerWrite.handler_append(container, db_handler)
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
# Node
|
|
79
|
+
node = NodeLedgerWriteEffect
|
|
80
|
+
|
|
81
|
+
# Handlers
|
|
82
|
+
handler_append = HandlerLedgerAppend
|
|
83
|
+
handler_query = HandlerLedgerQuery
|
|
84
|
+
|
|
85
|
+
# Protocol
|
|
86
|
+
protocol = ProtocolLedgerPersistence
|
|
87
|
+
|
|
88
|
+
# Models (as tuple for iteration)
|
|
89
|
+
models = (
|
|
90
|
+
ModelLedgerAppendResult,
|
|
91
|
+
ModelLedgerEntry,
|
|
92
|
+
ModelLedgerQuery,
|
|
93
|
+
ModelLedgerQueryResult,
|
|
94
|
+
ModelPayloadLedgerAppend,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Individual model references
|
|
98
|
+
model_append_result = ModelLedgerAppendResult
|
|
99
|
+
model_entry = ModelLedgerEntry
|
|
100
|
+
model_query = ModelLedgerQuery
|
|
101
|
+
model_query_result = ModelLedgerQueryResult
|
|
102
|
+
model_payload_append = ModelPayloadLedgerAppend
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# Re-export all components at module level for convenience
|
|
106
|
+
__all__ = [
|
|
107
|
+
"RegistryInfraLedgerWrite",
|
|
108
|
+
# Node
|
|
109
|
+
"NodeLedgerWriteEffect",
|
|
110
|
+
# Handlers
|
|
111
|
+
"HandlerLedgerAppend",
|
|
112
|
+
"HandlerLedgerQuery",
|
|
113
|
+
# Protocol
|
|
114
|
+
"ProtocolLedgerPersistence",
|
|
115
|
+
# Models
|
|
116
|
+
"ModelLedgerAppendResult",
|
|
117
|
+
"ModelLedgerEntry",
|
|
118
|
+
"ModelLedgerQuery",
|
|
119
|
+
"ModelLedgerQueryResult",
|
|
120
|
+
"ModelPayloadLedgerAppend",
|
|
121
|
+
]
|
|
@@ -94,20 +94,22 @@ from omnibase_core.services.service_handler_registry import ServiceHandlerRegist
|
|
|
94
94
|
from omnibase_infra.enums import EnumInfraTransportType
|
|
95
95
|
from omnibase_infra.errors import ModelInfraErrorContext, ProtocolConfigurationError
|
|
96
96
|
from omnibase_infra.protocols import ProtocolContainerAware
|
|
97
|
+
from omnibase_infra.runtime.constants_security import (
|
|
98
|
+
TRUSTED_HANDLER_NAMESPACE_PREFIXES,
|
|
99
|
+
)
|
|
97
100
|
from omnibase_infra.runtime.contract_loaders import (
|
|
98
101
|
load_handler_class_info_from_contract,
|
|
99
102
|
)
|
|
100
103
|
|
|
101
104
|
logger = logging.getLogger(__name__)
|
|
102
105
|
|
|
103
|
-
# Security:
|
|
106
|
+
# Security: Use centralized namespace allowlist for dynamic handler imports
|
|
104
107
|
# Per CLAUDE.md Handler Plugin Loader security patterns, only trusted namespaces
|
|
105
108
|
# are allowed for dynamic imports to prevent arbitrary code execution.
|
|
106
109
|
# Error code: NAMESPACE_NOT_ALLOWED (HANDLER_LOADER_013)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
)
|
|
110
|
+
# NOTE: Aliased for backwards compatibility - prefer importing directly from
|
|
111
|
+
# constants_security for new code.
|
|
112
|
+
ALLOWED_NAMESPACES: tuple[str, ...] = TRUSTED_HANDLER_NAMESPACE_PREFIXES
|
|
111
113
|
|
|
112
114
|
|
|
113
115
|
def _validate_handler_protocol(handler: object) -> tuple[bool, list[str]]:
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
|
2
2
|
# Copyright (c) 2025 OmniNode Team
|
|
3
|
-
"""Models for
|
|
3
|
+
"""Models for Reducers.
|
|
4
4
|
|
|
5
|
-
This module exports models used by
|
|
5
|
+
This module exports models used by infrastructure reducers (pure function pattern).
|
|
6
6
|
|
|
7
7
|
Available Models:
|
|
8
8
|
- ModelRegistrationState: Immutable state for pure reducer pattern
|
|
9
9
|
- ModelRegistrationConfirmation: Confirmation event from Effect layer (Phase 2)
|
|
10
10
|
- ModelPayloadConsulRegister: Payload for Consul registration intents
|
|
11
|
+
- ModelPayloadLedgerAppend: Payload for audit ledger append intents
|
|
11
12
|
- ModelPayloadPostgresUpsertRegistration: Payload for PostgreSQL upsert intents
|
|
12
13
|
"""
|
|
13
14
|
|
|
14
15
|
from omnibase_infra.nodes.reducers.models.model_payload_consul_register import (
|
|
15
16
|
ModelPayloadConsulRegister,
|
|
16
17
|
)
|
|
18
|
+
from omnibase_infra.nodes.reducers.models.model_payload_ledger_append import (
|
|
19
|
+
ModelPayloadLedgerAppend,
|
|
20
|
+
)
|
|
17
21
|
from omnibase_infra.nodes.reducers.models.model_payload_postgres_upsert_registration import (
|
|
18
22
|
ModelPayloadPostgresUpsertRegistration,
|
|
19
23
|
)
|
|
@@ -26,6 +30,7 @@ from omnibase_infra.nodes.reducers.models.model_registration_state import (
|
|
|
26
30
|
|
|
27
31
|
__all__ = [
|
|
28
32
|
"ModelPayloadConsulRegister",
|
|
33
|
+
"ModelPayloadLedgerAppend",
|
|
29
34
|
"ModelPayloadPostgresUpsertRegistration",
|
|
30
35
|
"ModelRegistrationConfirmation",
|
|
31
36
|
"ModelRegistrationState",
|
|
@@ -18,6 +18,10 @@ from uuid import UUID
|
|
|
18
18
|
|
|
19
19
|
from pydantic import BaseModel, ConfigDict, Field
|
|
20
20
|
|
|
21
|
+
from omnibase_infra.models.registration.model_node_event_bus_config import (
|
|
22
|
+
ModelNodeEventBusConfig,
|
|
23
|
+
)
|
|
24
|
+
|
|
21
25
|
# NOTE: ModelIntentPayloadBase was removed in omnibase_core 0.6.2
|
|
22
26
|
# Using pydantic.BaseModel directly as the base class
|
|
23
27
|
|
|
@@ -34,6 +38,8 @@ class ModelPayloadConsulRegister(BaseModel):
|
|
|
34
38
|
service_name: Service name for Consul service catalog.
|
|
35
39
|
tags: Service tags for filtering and categorization.
|
|
36
40
|
health_check: Optional health check configuration.
|
|
41
|
+
event_bus_config: Resolved event bus topics for registry storage.
|
|
42
|
+
If None, node is not included in dynamic topic routing lookups.
|
|
37
43
|
"""
|
|
38
44
|
|
|
39
45
|
model_config = ConfigDict(frozen=True, extra="forbid", from_attributes=True)
|
|
@@ -70,6 +76,11 @@ class ModelPayloadConsulRegister(BaseModel):
|
|
|
70
76
|
description="Optional health check configuration (HTTP, Interval, Timeout).",
|
|
71
77
|
)
|
|
72
78
|
|
|
79
|
+
event_bus_config: ModelNodeEventBusConfig | None = Field(
|
|
80
|
+
default=None,
|
|
81
|
+
description="Resolved event bus topics for registry storage.",
|
|
82
|
+
)
|
|
83
|
+
|
|
73
84
|
|
|
74
85
|
__all__ = [
|
|
75
86
|
"ModelPayloadConsulRegister",
|