omnibase_infra 0.3.1__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omnibase_infra/__init__.py +1 -1
- omnibase_infra/enums/__init__.py +3 -0
- omnibase_infra/enums/enum_consumer_group_purpose.py +9 -0
- omnibase_infra/enums/enum_postgres_error_code.py +188 -0
- omnibase_infra/errors/__init__.py +4 -0
- omnibase_infra/errors/error_infra.py +60 -0
- omnibase_infra/handlers/__init__.py +3 -0
- omnibase_infra/handlers/handler_slack_webhook.py +426 -0
- omnibase_infra/handlers/models/__init__.py +14 -0
- omnibase_infra/handlers/models/enum_alert_severity.py +36 -0
- omnibase_infra/handlers/models/model_slack_alert.py +24 -0
- omnibase_infra/handlers/models/model_slack_alert_payload.py +77 -0
- omnibase_infra/handlers/models/model_slack_alert_result.py +73 -0
- omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +29 -20
- omnibase_infra/mixins/__init__.py +14 -0
- omnibase_infra/mixins/mixin_node_introspection.py +42 -20
- omnibase_infra/mixins/mixin_postgres_error_response.py +314 -0
- omnibase_infra/mixins/mixin_postgres_op_executor.py +298 -0
- omnibase_infra/models/__init__.py +3 -0
- omnibase_infra/models/discovery/model_dependency_spec.py +1 -0
- omnibase_infra/models/discovery/model_discovered_capabilities.py +1 -1
- omnibase_infra/models/discovery/model_introspection_config.py +28 -1
- omnibase_infra/models/discovery/model_introspection_performance_metrics.py +1 -0
- omnibase_infra/models/discovery/model_introspection_task_config.py +1 -0
- omnibase_infra/{nodes/effects/models → models}/model_backend_result.py +22 -6
- omnibase_infra/models/projection/__init__.py +11 -0
- omnibase_infra/models/projection/model_contract_projection.py +170 -0
- omnibase_infra/models/projection/model_topic_projection.py +148 -0
- omnibase_infra/models/runtime/__init__.py +4 -0
- omnibase_infra/models/runtime/model_resolved_dependencies.py +116 -0
- omnibase_infra/nodes/contract_registry_reducer/__init__.py +5 -0
- omnibase_infra/nodes/contract_registry_reducer/contract.yaml +6 -5
- omnibase_infra/nodes/contract_registry_reducer/contract_registration_event_router.py +689 -0
- omnibase_infra/nodes/contract_registry_reducer/reducer.py +9 -26
- omnibase_infra/nodes/effects/__init__.py +1 -1
- omnibase_infra/nodes/effects/models/__init__.py +6 -4
- omnibase_infra/nodes/effects/models/model_registry_response.py +1 -1
- omnibase_infra/nodes/effects/protocol_consul_client.py +1 -1
- omnibase_infra/nodes/effects/protocol_postgres_adapter.py +1 -1
- omnibase_infra/nodes/effects/registry_effect.py +1 -1
- omnibase_infra/nodes/node_contract_persistence_effect/__init__.py +101 -0
- omnibase_infra/nodes/node_contract_persistence_effect/contract.yaml +490 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/__init__.py +74 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_cleanup_topics.py +217 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_contract_upsert.py +242 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_deactivate.py +194 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_heartbeat.py +243 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_mark_stale.py +208 -0
- omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_topic_update.py +298 -0
- omnibase_infra/nodes/node_contract_persistence_effect/models/__init__.py +15 -0
- omnibase_infra/nodes/node_contract_persistence_effect/models/model_persistence_result.py +52 -0
- omnibase_infra/nodes/node_contract_persistence_effect/node.py +131 -0
- omnibase_infra/nodes/node_contract_persistence_effect/registry/__init__.py +27 -0
- omnibase_infra/nodes/node_contract_persistence_effect/registry/registry_infra_contract_persistence_effect.py +251 -0
- omnibase_infra/nodes/node_registration_orchestrator/models/model_postgres_intent_payload.py +8 -12
- omnibase_infra/nodes/node_registry_effect/models/__init__.py +2 -2
- omnibase_infra/nodes/node_slack_alerter_effect/__init__.py +33 -0
- omnibase_infra/nodes/node_slack_alerter_effect/contract.yaml +291 -0
- omnibase_infra/nodes/node_slack_alerter_effect/node.py +106 -0
- omnibase_infra/projectors/__init__.py +6 -0
- omnibase_infra/projectors/projection_reader_contract.py +1301 -0
- omnibase_infra/runtime/__init__.py +12 -0
- omnibase_infra/runtime/baseline_subscriptions.py +13 -6
- omnibase_infra/runtime/contract_dependency_resolver.py +455 -0
- omnibase_infra/runtime/contract_registration_event_router.py +500 -0
- omnibase_infra/runtime/db/__init__.py +4 -0
- omnibase_infra/runtime/db/models/__init__.py +15 -10
- omnibase_infra/runtime/db/models/model_db_operation.py +40 -0
- omnibase_infra/runtime/db/models/model_db_param.py +24 -0
- omnibase_infra/runtime/db/models/model_db_repository_contract.py +40 -0
- omnibase_infra/runtime/db/models/model_db_return.py +26 -0
- omnibase_infra/runtime/db/models/model_db_safety_policy.py +32 -0
- omnibase_infra/runtime/emit_daemon/event_registry.py +34 -22
- omnibase_infra/runtime/event_bus_subcontract_wiring.py +63 -23
- omnibase_infra/runtime/intent_execution_router.py +430 -0
- omnibase_infra/runtime/models/__init__.py +6 -0
- omnibase_infra/runtime/models/model_contract_registry_config.py +41 -0
- omnibase_infra/runtime/models/model_intent_execution_summary.py +79 -0
- omnibase_infra/runtime/models/model_runtime_config.py +8 -0
- omnibase_infra/runtime/protocols/__init__.py +16 -0
- omnibase_infra/runtime/protocols/protocol_intent_executor.py +107 -0
- omnibase_infra/runtime/publisher_topic_scoped.py +16 -11
- omnibase_infra/runtime/registry_policy.py +29 -15
- omnibase_infra/runtime/request_response_wiring.py +793 -0
- omnibase_infra/runtime/service_kernel.py +295 -8
- omnibase_infra/runtime/service_runtime_host_process.py +149 -5
- omnibase_infra/runtime/util_version.py +5 -1
- omnibase_infra/schemas/schema_latency_baseline.sql +135 -0
- omnibase_infra/services/contract_publisher/config.py +4 -4
- omnibase_infra/services/contract_publisher/service.py +8 -5
- omnibase_infra/services/observability/injection_effectiveness/__init__.py +67 -0
- omnibase_infra/services/observability/injection_effectiveness/config.py +295 -0
- omnibase_infra/services/observability/injection_effectiveness/consumer.py +1461 -0
- omnibase_infra/services/observability/injection_effectiveness/models/__init__.py +32 -0
- omnibase_infra/services/observability/injection_effectiveness/models/model_agent_match.py +79 -0
- omnibase_infra/services/observability/injection_effectiveness/models/model_context_utilization.py +118 -0
- omnibase_infra/services/observability/injection_effectiveness/models/model_latency_breakdown.py +107 -0
- omnibase_infra/services/observability/injection_effectiveness/models/model_pattern_utilization.py +46 -0
- omnibase_infra/services/observability/injection_effectiveness/writer_postgres.py +596 -0
- omnibase_infra/services/registry_api/models/__init__.py +25 -0
- omnibase_infra/services/registry_api/models/model_contract_ref.py +44 -0
- omnibase_infra/services/registry_api/models/model_contract_view.py +81 -0
- omnibase_infra/services/registry_api/models/model_response_contracts.py +50 -0
- omnibase_infra/services/registry_api/models/model_response_topics.py +50 -0
- omnibase_infra/services/registry_api/models/model_topic_summary.py +57 -0
- omnibase_infra/services/registry_api/models/model_topic_view.py +63 -0
- omnibase_infra/services/registry_api/routes.py +205 -6
- omnibase_infra/services/registry_api/service.py +528 -1
- omnibase_infra/utils/__init__.py +7 -0
- omnibase_infra/utils/util_db_error_context.py +292 -0
- omnibase_infra/validation/infra_validators.py +3 -1
- omnibase_infra/validation/validation_exemptions.yaml +65 -0
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/METADATA +3 -3
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/RECORD +117 -58
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/WHEEL +0 -0
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/entry_points.txt +0 -0
- {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
#
|
|
4
|
+
# ONEX Node Contract
|
|
5
|
+
# Node: NodeContractPersistenceEffect
|
|
6
|
+
#
|
|
7
|
+
# This contract defines the interface for the Contract Persistence Effect node,
|
|
8
|
+
# which handles PostgreSQL persistence for contract registry intents emitted by
|
|
9
|
+
# ContractRegistryReducer (OMN-1653).
|
|
10
|
+
#
|
|
11
|
+
# Related Tickets:
|
|
12
|
+
# - OMN-1845: NodeContractPersistenceEffect implementation
|
|
13
|
+
# - OMN-1653: ContractRegistryReducer (source of intents)
|
|
14
|
+
# Contract identifiers
|
|
15
|
+
name: "node_contract_persistence_effect"
|
|
16
|
+
contract_name: "node_contract_persistence_effect"
|
|
17
|
+
node_name: "node_contract_persistence_effect"
|
|
18
|
+
contract_version:
|
|
19
|
+
major: 1
|
|
20
|
+
minor: 0
|
|
21
|
+
patch: 0
|
|
22
|
+
node_version:
|
|
23
|
+
major: 1
|
|
24
|
+
minor: 0
|
|
25
|
+
patch: 0
|
|
26
|
+
# Node type
|
|
27
|
+
node_type: "EFFECT_GENERIC"
|
|
28
|
+
# Description
|
|
29
|
+
description: >
|
|
30
|
+
Effect node for contract registry persistence. Routes intents from ContractRegistryReducer to PostgreSQL handlers for contract and topic management. Supports upsert, update, deactivate, and cleanup operations with circuit breaker protection and retry policies.
|
|
31
|
+
|
|
32
|
+
# Strongly typed I/O models
|
|
33
|
+
# Note: Input is ModelIntent with typed payloads from ContractRegistryReducer
|
|
34
|
+
input_model:
|
|
35
|
+
name: "ModelIntent"
|
|
36
|
+
module: "omnibase_core.models.reducer.model_intent"
|
|
37
|
+
description: "Intent model from ContractRegistryReducer with typed payload."
|
|
38
|
+
output_model:
|
|
39
|
+
name: "ModelPersistenceResult"
|
|
40
|
+
module: "omnibase_infra.nodes.node_contract_persistence_effect.models"
|
|
41
|
+
description: "Output model containing PostgreSQL operation result."
|
|
42
|
+
# =============================================================================
|
|
43
|
+
# HANDLER ROUTING (Declarative Handler Dispatch)
|
|
44
|
+
# =============================================================================
|
|
45
|
+
# This section defines how intents are routed to PostgreSQL handlers.
|
|
46
|
+
# The routing strategy determines handler selection based on payload.intent_type
|
|
47
|
+
# (per ONEX handler routing standards).
|
|
48
|
+
#
|
|
49
|
+
# Design Rationale:
|
|
50
|
+
# - Single backend (PostgreSQL) with multiple operation types
|
|
51
|
+
# - Each handler is responsible for a specific PostgreSQL operation
|
|
52
|
+
# - Handlers are isolated for clear error handling and retry logic
|
|
53
|
+
#
|
|
54
|
+
# Execution Flow:
|
|
55
|
+
# 1. Receive ModelIntent with typed payload from ContractRegistryReducer
|
|
56
|
+
# 2. Extract payload.intent_type for routing
|
|
57
|
+
# 3. Route to matching handler based on intent_type
|
|
58
|
+
# 4. Execute PostgreSQL operation via handler
|
|
59
|
+
# 5. Return ModelPersistenceResult with operation status
|
|
60
|
+
#
|
|
61
|
+
# ┌─────────────────────────────────────────────────────────────────────────┐
|
|
62
|
+
# │ HANDLER ROUTING ARCHITECTURE │
|
|
63
|
+
# ├─────────────────────────────────────────────────────────────────────────┤
|
|
64
|
+
# │ │
|
|
65
|
+
# │ ┌────────────────────────┐ │
|
|
66
|
+
# │ │ ModelIntent │ │
|
|
67
|
+
# │ │ payload.intent_type │ │
|
|
68
|
+
# │ └───────────┬────────────┘ │
|
|
69
|
+
# │ │ │
|
|
70
|
+
# │ ▼ │
|
|
71
|
+
# │ ┌───────────────────────┐ │
|
|
72
|
+
# │ │ Handler Router │ routing_strategy: "payload_type_match" │
|
|
73
|
+
# │ │ (Contract-Driven) │ Matches payload.intent_type to handlers │
|
|
74
|
+
# │ └───────────┬───────────┘ │
|
|
75
|
+
# │ │ │
|
|
76
|
+
# │ ┌────────┼────────┬────────┬────────┬────────┐ │
|
|
77
|
+
# │ │ │ │ │ │ │ │
|
|
78
|
+
# │ ▼ ▼ ▼ ▼ ▼ ▼ │
|
|
79
|
+
# │ ┌────────┐┌────────┐┌────────┐┌────────┐┌────────┐┌────────┐ │
|
|
80
|
+
# │ │Upsert ││Topic ││Mark ││Heartbeat││Deact. ││Cleanup │ │
|
|
81
|
+
# │ │Contract││Update ││Stale ││Update ││Contract││Topics │ │
|
|
82
|
+
# │ │Handler ││Handler ││Handler ││Handler ││Handler ││Handler │ │
|
|
83
|
+
# │ └────┬───┘└────┬───┘└────┬───┘└────┬───┘└────┬───┘└────┬───┘ │
|
|
84
|
+
# │ │ │ │ │ │ │ │
|
|
85
|
+
# │ └─────────┴─────────┴────┬────┴─────────┴─────────┘ │
|
|
86
|
+
# │ │ │
|
|
87
|
+
# │ ▼ │
|
|
88
|
+
# │ ┌─────────────────────────────────────────────────────────────┐ │
|
|
89
|
+
# │ │ PostgreSQL Database │ │
|
|
90
|
+
# │ │ ┌────────────────┐ ┌────────────────┐ │ │
|
|
91
|
+
# │ │ │ contracts │ │ topics │ │ │
|
|
92
|
+
# │ │ └────────────────┘ └────────────────┘ │ │
|
|
93
|
+
# │ └─────────────────────────────────────────────────────────────┘ │
|
|
94
|
+
# │ │
|
|
95
|
+
# └────────────────────────────────────────────────────────────────────────┘
|
|
96
|
+
#
|
|
97
|
+
# Handler Responsibility:
|
|
98
|
+
# Each handler manages its own retry logic using the retry_policy defined
|
|
99
|
+
# in error_handling. The effect layer dispatches once per handler and
|
|
100
|
+
# delegates retry behavior to the handlers themselves.
|
|
101
|
+
# =============================================================================
|
|
102
|
+
handler_routing:
|
|
103
|
+
routing_strategy: "payload_type_match"
|
|
104
|
+
handlers:
|
|
105
|
+
# Upsert Contract - Insert or update contract record
|
|
106
|
+
# Triggered by contract-registered events from ContractRegistryReducer.
|
|
107
|
+
# Uses upsert semantics (ON CONFLICT DO UPDATE) for idempotency.
|
|
108
|
+
- payload_type: "postgres.upsert_contract"
|
|
109
|
+
handler:
|
|
110
|
+
name: "HandlerPostgresContractUpsert"
|
|
111
|
+
module: "omnibase_infra.nodes.node_contract_persistence_effect.handlers.handler_postgres_contract_upsert"
|
|
112
|
+
backend: "postgres"
|
|
113
|
+
description: "Insert or update contract record in contracts table"
|
|
114
|
+
input_model:
|
|
115
|
+
name: "ModelPayloadUpsertContract"
|
|
116
|
+
module: "omnibase_infra.nodes.contract_registry_reducer.models.model_payload_upsert_contract"
|
|
117
|
+
output_fields:
|
|
118
|
+
- success
|
|
119
|
+
- contract_id
|
|
120
|
+
- operation
|
|
121
|
+
- rows_affected
|
|
122
|
+
# Update Topic - Update topic routing table
|
|
123
|
+
# Triggered by contract-registered events with publish/subscribe declarations.
|
|
124
|
+
# Maintains topic-to-contract mapping for event routing.
|
|
125
|
+
- payload_type: "postgres.update_topic"
|
|
126
|
+
handler:
|
|
127
|
+
name: "HandlerPostgresTopicUpdate"
|
|
128
|
+
module: "omnibase_infra.nodes.node_contract_persistence_effect.handlers.handler_postgres_topic_update"
|
|
129
|
+
backend: "postgres"
|
|
130
|
+
description: "Update topic routing table with producer/consumer info"
|
|
131
|
+
input_model:
|
|
132
|
+
name: "ModelPayloadUpdateTopic"
|
|
133
|
+
module: "omnibase_infra.nodes.contract_registry_reducer.models.model_payload_update_topic"
|
|
134
|
+
output_fields:
|
|
135
|
+
- success
|
|
136
|
+
- topic_suffix
|
|
137
|
+
- direction
|
|
138
|
+
- rows_affected
|
|
139
|
+
# Mark Stale - Batch deactivate contracts that stopped heartbeating
|
|
140
|
+
# Triggered by runtime-tick events to detect unresponsive contracts.
|
|
141
|
+
# Deactivates (is_active=FALSE) contracts not seen within threshold.
|
|
142
|
+
- payload_type: "postgres.mark_stale"
|
|
143
|
+
handler:
|
|
144
|
+
name: "HandlerPostgresMarkStale"
|
|
145
|
+
module: "omnibase_infra.nodes.node_contract_persistence_effect.handlers.handler_postgres_mark_stale"
|
|
146
|
+
backend: "postgres"
|
|
147
|
+
description: "Batch mark contracts as stale based on last_seen_at threshold"
|
|
148
|
+
input_model:
|
|
149
|
+
name: "ModelPayloadMarkStale"
|
|
150
|
+
module: "omnibase_infra.nodes.contract_registry_reducer.models.model_payload_mark_stale"
|
|
151
|
+
output_fields:
|
|
152
|
+
- success
|
|
153
|
+
- contracts_marked
|
|
154
|
+
- stale_cutoff
|
|
155
|
+
# Update Heartbeat - Update last_seen_at timestamp
|
|
156
|
+
# Triggered by node-heartbeat events to track contract liveness.
|
|
157
|
+
# Lightweight update operation for high-frequency heartbeats.
|
|
158
|
+
- payload_type: "postgres.update_heartbeat"
|
|
159
|
+
handler:
|
|
160
|
+
name: "HandlerPostgresHeartbeat"
|
|
161
|
+
module: "omnibase_infra.nodes.node_contract_persistence_effect.handlers.handler_postgres_heartbeat"
|
|
162
|
+
backend: "postgres"
|
|
163
|
+
description: "Update contract heartbeat timestamp"
|
|
164
|
+
input_model:
|
|
165
|
+
name: "ModelPayloadUpdateHeartbeat"
|
|
166
|
+
module: "omnibase_infra.nodes.contract_registry_reducer.models.model_payload_update_heartbeat"
|
|
167
|
+
output_fields:
|
|
168
|
+
- success
|
|
169
|
+
- contract_id
|
|
170
|
+
- last_seen_at
|
|
171
|
+
# Deactivate Contract - Soft delete contract
|
|
172
|
+
# Triggered by contract-deregistered events for graceful shutdown.
|
|
173
|
+
# Marks contract as inactive without deleting the record.
|
|
174
|
+
- payload_type: "postgres.deactivate_contract"
|
|
175
|
+
handler:
|
|
176
|
+
name: "HandlerPostgresDeactivate"
|
|
177
|
+
module: "omnibase_infra.nodes.node_contract_persistence_effect.handlers.handler_postgres_deactivate"
|
|
178
|
+
backend: "postgres"
|
|
179
|
+
description: "Mark contract as inactive (soft delete)"
|
|
180
|
+
input_model:
|
|
181
|
+
name: "ModelPayloadDeactivateContract"
|
|
182
|
+
module: "omnibase_infra.nodes.contract_registry_reducer.models.model_payload_deactivate_contract"
|
|
183
|
+
output_fields:
|
|
184
|
+
- success
|
|
185
|
+
- contract_id
|
|
186
|
+
- deactivated_at
|
|
187
|
+
# Cleanup Topic References - Remove contract from topic arrays
|
|
188
|
+
# Triggered by contract-deregistered events to clean topic mappings.
|
|
189
|
+
# Removes contract_id from topics.contract_ids JSONB arrays.
|
|
190
|
+
- payload_type: "postgres.cleanup_topic_references"
|
|
191
|
+
handler:
|
|
192
|
+
name: "HandlerPostgresCleanupTopics"
|
|
193
|
+
module: "omnibase_infra.nodes.node_contract_persistence_effect.handlers.handler_postgres_cleanup_topics"
|
|
194
|
+
backend: "postgres"
|
|
195
|
+
description: "Remove contract from topic references"
|
|
196
|
+
input_model:
|
|
197
|
+
name: "ModelPayloadCleanupTopicReferences"
|
|
198
|
+
module: "omnibase_infra.nodes.contract_registry_reducer.models.model_payload_cleanup_topic_references"
|
|
199
|
+
output_fields:
|
|
200
|
+
- success
|
|
201
|
+
- contract_id
|
|
202
|
+
- topics_updated
|
|
203
|
+
# Execution configuration
|
|
204
|
+
execution_mode: "sequential"
|
|
205
|
+
partial_failure_handling: false
|
|
206
|
+
aggregation_strategy: "first_match"
|
|
207
|
+
# =============================================================================
|
|
208
|
+
# ERROR HANDLING (Circuit Breaker + Retry + Sanitization)
|
|
209
|
+
# =============================================================================
|
|
210
|
+
# Error handling configuration following ONEX infrastructure patterns.
|
|
211
|
+
# Uses circuit breakers for PostgreSQL protection and retry policies for
|
|
212
|
+
# transient failure recovery.
|
|
213
|
+
#
|
|
214
|
+
# Error Flow:
|
|
215
|
+
# 1. Operation fails -> Check if error is retriable
|
|
216
|
+
# 2. Retry with exponential backoff if retriable
|
|
217
|
+
# 3. After max retries, check circuit breaker state
|
|
218
|
+
# 4. Open circuit if failure threshold exceeded
|
|
219
|
+
# 5. Sanitize error before logging/returning
|
|
220
|
+
# =============================================================================
|
|
221
|
+
error_handling:
|
|
222
|
+
# Circuit breaker configuration
|
|
223
|
+
# Prevents cascading failures by stopping requests to failing PostgreSQL.
|
|
224
|
+
circuit_breaker:
|
|
225
|
+
enabled: true
|
|
226
|
+
failure_threshold: 5
|
|
227
|
+
reset_timeout_ms: 60000
|
|
228
|
+
half_open_max_requests: 3
|
|
229
|
+
per_backend: false
|
|
230
|
+
backends:
|
|
231
|
+
- name: "postgres"
|
|
232
|
+
failure_threshold: 5
|
|
233
|
+
reset_timeout_ms: 60000
|
|
234
|
+
# Retry policy with exponential backoff
|
|
235
|
+
# Handles transient PostgreSQL failures.
|
|
236
|
+
#
|
|
237
|
+
# ==========================================================================
|
|
238
|
+
# IMPORTANT: Effect Layer vs Handler Layer Retry Behavior
|
|
239
|
+
# ==========================================================================
|
|
240
|
+
#
|
|
241
|
+
# Effect Layer (NodeContractPersistenceEffect):
|
|
242
|
+
# - Dispatches to handlers EXACTLY ONCE per intent
|
|
243
|
+
# - Does NOT implement retry loops
|
|
244
|
+
# - Returns immediately after handler execution
|
|
245
|
+
# - Effective retry count at this layer: ZERO (0)
|
|
246
|
+
#
|
|
247
|
+
# Handler Layer (HandlerPostgres*):
|
|
248
|
+
# - OWNS retry logic using this retry_policy configuration
|
|
249
|
+
# - Implements exponential backoff for retriable errors
|
|
250
|
+
# - Returns final result after all retries exhausted
|
|
251
|
+
# - Effective retry count: 0 to max_retries
|
|
252
|
+
#
|
|
253
|
+
# ==========================================================================
|
|
254
|
+
retry_policy:
|
|
255
|
+
max_retries: 3
|
|
256
|
+
initial_delay_ms: 100
|
|
257
|
+
max_delay_ms: 5000
|
|
258
|
+
exponential_base: 2
|
|
259
|
+
retry_on:
|
|
260
|
+
- "InfraConnectionError"
|
|
261
|
+
- "InfraTimeoutError"
|
|
262
|
+
- "InfraUnavailableError"
|
|
263
|
+
- "RepositoryExecutionError"
|
|
264
|
+
# Error sanitization
|
|
265
|
+
# Removes sensitive information from error messages before logging.
|
|
266
|
+
error_sanitization:
|
|
267
|
+
enabled: true
|
|
268
|
+
utility: "omnibase_infra.utils.util_error_sanitization"
|
|
269
|
+
sanitize_patterns:
|
|
270
|
+
- "password"
|
|
271
|
+
- "secret"
|
|
272
|
+
- "token"
|
|
273
|
+
- "api_key"
|
|
274
|
+
- "connection_string"
|
|
275
|
+
# Error type definitions
|
|
276
|
+
error_types:
|
|
277
|
+
- name: "InfraConnectionError"
|
|
278
|
+
description: "Failed to connect to PostgreSQL"
|
|
279
|
+
recoverable: true
|
|
280
|
+
retry_strategy: "exponential_backoff"
|
|
281
|
+
- name: "InfraTimeoutError"
|
|
282
|
+
description: "PostgreSQL operation timed out"
|
|
283
|
+
recoverable: true
|
|
284
|
+
retry_strategy: "exponential_backoff"
|
|
285
|
+
- name: "InfraAuthenticationError"
|
|
286
|
+
description: "Authentication failed with PostgreSQL"
|
|
287
|
+
recoverable: false
|
|
288
|
+
retry_strategy: "none"
|
|
289
|
+
- name: "InfraUnavailableError"
|
|
290
|
+
description: "PostgreSQL is unavailable (circuit open)"
|
|
291
|
+
recoverable: true
|
|
292
|
+
retry_strategy: "exponential_backoff"
|
|
293
|
+
- name: "RepositoryExecutionError"
|
|
294
|
+
description: "PostgreSQL query execution failed"
|
|
295
|
+
recoverable: true
|
|
296
|
+
retry_strategy: "exponential_backoff"
|
|
297
|
+
# Error codes used in result models
|
|
298
|
+
error_codes:
|
|
299
|
+
# Connection errors
|
|
300
|
+
- code: "POSTGRES_CONNECTION_ERROR"
|
|
301
|
+
backend: "postgres"
|
|
302
|
+
operation: "all"
|
|
303
|
+
description: "Connection to PostgreSQL server failed"
|
|
304
|
+
retriable: true
|
|
305
|
+
- code: "POSTGRES_TIMEOUT_ERROR"
|
|
306
|
+
backend: "postgres"
|
|
307
|
+
operation: "all"
|
|
308
|
+
description: "PostgreSQL operation exceeded timeout"
|
|
309
|
+
retriable: true
|
|
310
|
+
- code: "POSTGRES_AUTH_ERROR"
|
|
311
|
+
backend: "postgres"
|
|
312
|
+
operation: "all"
|
|
313
|
+
description: "Authentication with PostgreSQL server failed"
|
|
314
|
+
retriable: false
|
|
315
|
+
# Operation-specific errors
|
|
316
|
+
- code: "POSTGRES_UPSERT_ERROR"
|
|
317
|
+
backend: "postgres"
|
|
318
|
+
operation: "upsert_contract"
|
|
319
|
+
description: "PostgreSQL upsert operation failed"
|
|
320
|
+
retriable: false
|
|
321
|
+
- code: "POSTGRES_TOPIC_UPDATE_ERROR"
|
|
322
|
+
backend: "postgres"
|
|
323
|
+
operation: "update_topic"
|
|
324
|
+
description: "PostgreSQL topic update failed"
|
|
325
|
+
retriable: false
|
|
326
|
+
- code: "POSTGRES_MARK_STALE_ERROR"
|
|
327
|
+
backend: "postgres"
|
|
328
|
+
operation: "mark_stale"
|
|
329
|
+
description: "PostgreSQL mark stale operation failed"
|
|
330
|
+
retriable: false
|
|
331
|
+
- code: "POSTGRES_HEARTBEAT_ERROR"
|
|
332
|
+
backend: "postgres"
|
|
333
|
+
operation: "update_heartbeat"
|
|
334
|
+
description: "PostgreSQL heartbeat update failed"
|
|
335
|
+
retriable: false
|
|
336
|
+
- code: "POSTGRES_DEACTIVATE_ERROR"
|
|
337
|
+
backend: "postgres"
|
|
338
|
+
operation: "deactivate_contract"
|
|
339
|
+
description: "PostgreSQL deactivation failed"
|
|
340
|
+
retriable: false
|
|
341
|
+
- code: "POSTGRES_CLEANUP_ERROR"
|
|
342
|
+
backend: "postgres"
|
|
343
|
+
operation: "cleanup_topic_references"
|
|
344
|
+
description: "PostgreSQL topic cleanup failed"
|
|
345
|
+
retriable: false
|
|
346
|
+
- code: "POSTGRES_UNKNOWN_ERROR"
|
|
347
|
+
backend: "postgres"
|
|
348
|
+
operation: "all"
|
|
349
|
+
description: "Unknown error during PostgreSQL operation"
|
|
350
|
+
retriable: false
|
|
351
|
+
# IO operations (EFFECT node specific)
|
|
352
|
+
io_operations:
|
|
353
|
+
- operation: "upsert_contract"
|
|
354
|
+
description: "Insert or update a contract record"
|
|
355
|
+
input_fields:
|
|
356
|
+
- contract_id
|
|
357
|
+
- node_name
|
|
358
|
+
- version_major
|
|
359
|
+
- version_minor
|
|
360
|
+
- version_patch
|
|
361
|
+
- contract_hash
|
|
362
|
+
- contract_yaml
|
|
363
|
+
- source_node_id
|
|
364
|
+
- is_active
|
|
365
|
+
- registered_at
|
|
366
|
+
- last_seen_at
|
|
367
|
+
output_fields:
|
|
368
|
+
- success
|
|
369
|
+
- contract_id
|
|
370
|
+
- operation
|
|
371
|
+
- rows_affected
|
|
372
|
+
- error
|
|
373
|
+
- error_code
|
|
374
|
+
- operation: "update_topic"
|
|
375
|
+
description: "Update topic routing information"
|
|
376
|
+
input_fields:
|
|
377
|
+
- topic_suffix
|
|
378
|
+
- direction
|
|
379
|
+
- contract_id
|
|
380
|
+
- node_name
|
|
381
|
+
- event_type
|
|
382
|
+
- last_seen_at
|
|
383
|
+
output_fields:
|
|
384
|
+
- success
|
|
385
|
+
- topic_suffix
|
|
386
|
+
- direction
|
|
387
|
+
- rows_affected
|
|
388
|
+
- error
|
|
389
|
+
- error_code
|
|
390
|
+
- operation: "mark_stale"
|
|
391
|
+
description: "Batch mark contracts as stale"
|
|
392
|
+
input_fields:
|
|
393
|
+
- stale_cutoff
|
|
394
|
+
- checked_at
|
|
395
|
+
output_fields:
|
|
396
|
+
- success
|
|
397
|
+
- contracts_marked
|
|
398
|
+
- stale_cutoff
|
|
399
|
+
- error
|
|
400
|
+
- error_code
|
|
401
|
+
- operation: "update_heartbeat"
|
|
402
|
+
description: "Update contract heartbeat timestamp"
|
|
403
|
+
input_fields:
|
|
404
|
+
- contract_id
|
|
405
|
+
- node_name
|
|
406
|
+
- source_node_id
|
|
407
|
+
- last_seen_at
|
|
408
|
+
- uptime_seconds
|
|
409
|
+
- sequence_number
|
|
410
|
+
output_fields:
|
|
411
|
+
- success
|
|
412
|
+
- contract_id
|
|
413
|
+
- last_seen_at
|
|
414
|
+
- error
|
|
415
|
+
- error_code
|
|
416
|
+
- operation: "deactivate_contract"
|
|
417
|
+
description: "Mark contract as inactive"
|
|
418
|
+
input_fields:
|
|
419
|
+
- contract_id
|
|
420
|
+
- node_name
|
|
421
|
+
- reason
|
|
422
|
+
- deactivated_at
|
|
423
|
+
output_fields:
|
|
424
|
+
- success
|
|
425
|
+
- contract_id
|
|
426
|
+
- deactivated_at
|
|
427
|
+
- error
|
|
428
|
+
- error_code
|
|
429
|
+
- operation: "cleanup_topic_references"
|
|
430
|
+
description: "Remove contract from topic references"
|
|
431
|
+
input_fields:
|
|
432
|
+
- contract_id
|
|
433
|
+
- node_name
|
|
434
|
+
- cleaned_at
|
|
435
|
+
output_fields:
|
|
436
|
+
- success
|
|
437
|
+
- contract_id
|
|
438
|
+
- topics_updated
|
|
439
|
+
- error
|
|
440
|
+
- error_code
|
|
441
|
+
# Dependencies (protocols this node requires)
|
|
442
|
+
dependencies:
|
|
443
|
+
- name: "protocol_postgres_adapter"
|
|
444
|
+
type: "protocol"
|
|
445
|
+
class_name: "ProtocolPostgresAdapter"
|
|
446
|
+
module: "omnibase_infra.adapters.protocol_postgres_adapter"
|
|
447
|
+
description: "PostgreSQL database operations"
|
|
448
|
+
- name: "protocol_circuit_breaker_aware"
|
|
449
|
+
type: "protocol"
|
|
450
|
+
class_name: "ProtocolCircuitBreakerAware"
|
|
451
|
+
module: "omnibase_infra.mixins.protocol_circuit_breaker_aware"
|
|
452
|
+
description: "Circuit breaker awareness for backend protection"
|
|
453
|
+
# Capabilities provided by this node
|
|
454
|
+
capabilities:
|
|
455
|
+
- name: "contract_persistence"
|
|
456
|
+
description: "Persist contract records to PostgreSQL"
|
|
457
|
+
- name: "topic_routing"
|
|
458
|
+
description: "Manage topic-to-contract routing mappings"
|
|
459
|
+
- name: "staleness_detection"
|
|
460
|
+
description: "Batch mark stale contracts based on heartbeat"
|
|
461
|
+
- name: "heartbeat_tracking"
|
|
462
|
+
description: "Track contract liveness via heartbeat updates"
|
|
463
|
+
- name: "soft_delete"
|
|
464
|
+
description: "Deactivate contracts without data loss"
|
|
465
|
+
- name: "circuit_breaker_protection"
|
|
466
|
+
description: "Protect PostgreSQL with circuit breaker pattern"
|
|
467
|
+
# Health check configuration
|
|
468
|
+
health_check:
|
|
469
|
+
enabled: true
|
|
470
|
+
endpoint: "/health"
|
|
471
|
+
interval_seconds: 30
|
|
472
|
+
backends:
|
|
473
|
+
- name: "postgres"
|
|
474
|
+
check_type: "connection"
|
|
475
|
+
timeout_ms: 5000
|
|
476
|
+
# Metadata
|
|
477
|
+
metadata:
|
|
478
|
+
author: "OmniNode Team"
|
|
479
|
+
license: "MIT"
|
|
480
|
+
created: "2025-02-02"
|
|
481
|
+
updated: "2025-02-02"
|
|
482
|
+
tags:
|
|
483
|
+
- effect
|
|
484
|
+
- contract-registry
|
|
485
|
+
- postgresql
|
|
486
|
+
- persistence
|
|
487
|
+
- circuit-breaker
|
|
488
|
+
related_tickets:
|
|
489
|
+
- "OMN-1845"
|
|
490
|
+
- "OMN-1653"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Handlers for NodeContractPersistenceEffect operations.
|
|
4
|
+
|
|
5
|
+
This package contains the handlers for the NodeContractPersistenceEffect node,
|
|
6
|
+
following the declarative node pattern where PostgreSQL operations are
|
|
7
|
+
encapsulated in dedicated handler classes.
|
|
8
|
+
|
|
9
|
+
Available Handlers:
|
|
10
|
+
HandlerPostgresContractUpsert: Upsert contract record handler.
|
|
11
|
+
HandlerPostgresTopicUpdate: Update topic routing handler.
|
|
12
|
+
HandlerPostgresMarkStale: Batch mark stale contracts handler.
|
|
13
|
+
HandlerPostgresHeartbeat: Update heartbeat timestamp handler.
|
|
14
|
+
HandlerPostgresDeactivate: Deactivate contract handler.
|
|
15
|
+
HandlerPostgresCleanupTopics: Cleanup topic references handler.
|
|
16
|
+
|
|
17
|
+
Architecture:
|
|
18
|
+
These handlers are used by NodeContractPersistenceEffect to execute
|
|
19
|
+
PostgreSQL operations based on intents from ContractRegistryReducer.
|
|
20
|
+
Each handler is responsible for:
|
|
21
|
+
- Operation timing and observability
|
|
22
|
+
- Error sanitization for security
|
|
23
|
+
- Structured result construction
|
|
24
|
+
- Retry logic per retry_policy configuration
|
|
25
|
+
|
|
26
|
+
Shared Patterns:
|
|
27
|
+
All handlers share a common error handling pattern:
|
|
28
|
+
- TimeoutError/InfraTimeoutError: Returns *_TIMEOUT_ERROR code
|
|
29
|
+
- InfraAuthenticationError: Returns *_AUTH_ERROR code (non-retriable)
|
|
30
|
+
- InfraConnectionError: Returns *_CONNECTION_ERROR code (retriable)
|
|
31
|
+
- RepositoryExecutionError: Returns operation-specific error code
|
|
32
|
+
- Exception: Returns *_UNKNOWN_ERROR code
|
|
33
|
+
|
|
34
|
+
Each handler sanitizes errors via sanitize_error_message() to prevent
|
|
35
|
+
credential exposure in logs and error responses.
|
|
36
|
+
|
|
37
|
+
Related:
|
|
38
|
+
- NodeContractPersistenceEffect: Parent effect node coordinating handlers
|
|
39
|
+
- ContractRegistryReducer: Source of intents
|
|
40
|
+
- OMN-1845: Implementation ticket
|
|
41
|
+
- OMN-1653: ContractRegistryReducer ticket
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
from __future__ import annotations
|
|
45
|
+
|
|
46
|
+
from omnibase_infra.nodes.node_contract_persistence_effect.handlers.handler_postgres_cleanup_topics import (
|
|
47
|
+
HandlerPostgresCleanupTopics,
|
|
48
|
+
)
|
|
49
|
+
from omnibase_infra.nodes.node_contract_persistence_effect.handlers.handler_postgres_contract_upsert import (
|
|
50
|
+
HandlerPostgresContractUpsert,
|
|
51
|
+
)
|
|
52
|
+
from omnibase_infra.nodes.node_contract_persistence_effect.handlers.handler_postgres_deactivate import (
|
|
53
|
+
HandlerPostgresDeactivate,
|
|
54
|
+
)
|
|
55
|
+
from omnibase_infra.nodes.node_contract_persistence_effect.handlers.handler_postgres_heartbeat import (
|
|
56
|
+
HandlerPostgresHeartbeat,
|
|
57
|
+
)
|
|
58
|
+
from omnibase_infra.nodes.node_contract_persistence_effect.handlers.handler_postgres_mark_stale import (
|
|
59
|
+
HandlerPostgresMarkStale,
|
|
60
|
+
)
|
|
61
|
+
from omnibase_infra.nodes.node_contract_persistence_effect.handlers.handler_postgres_topic_update import (
|
|
62
|
+
HandlerPostgresTopicUpdate,
|
|
63
|
+
normalize_topic_for_storage,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
__all__: list[str] = [
|
|
67
|
+
"HandlerPostgresCleanupTopics",
|
|
68
|
+
"HandlerPostgresContractUpsert",
|
|
69
|
+
"HandlerPostgresDeactivate",
|
|
70
|
+
"HandlerPostgresHeartbeat",
|
|
71
|
+
"HandlerPostgresMarkStale",
|
|
72
|
+
"HandlerPostgresTopicUpdate",
|
|
73
|
+
"normalize_topic_for_storage",
|
|
74
|
+
]
|