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.
Files changed (117) hide show
  1. omnibase_infra/__init__.py +1 -1
  2. omnibase_infra/enums/__init__.py +3 -0
  3. omnibase_infra/enums/enum_consumer_group_purpose.py +9 -0
  4. omnibase_infra/enums/enum_postgres_error_code.py +188 -0
  5. omnibase_infra/errors/__init__.py +4 -0
  6. omnibase_infra/errors/error_infra.py +60 -0
  7. omnibase_infra/handlers/__init__.py +3 -0
  8. omnibase_infra/handlers/handler_slack_webhook.py +426 -0
  9. omnibase_infra/handlers/models/__init__.py +14 -0
  10. omnibase_infra/handlers/models/enum_alert_severity.py +36 -0
  11. omnibase_infra/handlers/models/model_slack_alert.py +24 -0
  12. omnibase_infra/handlers/models/model_slack_alert_payload.py +77 -0
  13. omnibase_infra/handlers/models/model_slack_alert_result.py +73 -0
  14. omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +29 -20
  15. omnibase_infra/mixins/__init__.py +14 -0
  16. omnibase_infra/mixins/mixin_node_introspection.py +42 -20
  17. omnibase_infra/mixins/mixin_postgres_error_response.py +314 -0
  18. omnibase_infra/mixins/mixin_postgres_op_executor.py +298 -0
  19. omnibase_infra/models/__init__.py +3 -0
  20. omnibase_infra/models/discovery/model_dependency_spec.py +1 -0
  21. omnibase_infra/models/discovery/model_discovered_capabilities.py +1 -1
  22. omnibase_infra/models/discovery/model_introspection_config.py +28 -1
  23. omnibase_infra/models/discovery/model_introspection_performance_metrics.py +1 -0
  24. omnibase_infra/models/discovery/model_introspection_task_config.py +1 -0
  25. omnibase_infra/{nodes/effects/models → models}/model_backend_result.py +22 -6
  26. omnibase_infra/models/projection/__init__.py +11 -0
  27. omnibase_infra/models/projection/model_contract_projection.py +170 -0
  28. omnibase_infra/models/projection/model_topic_projection.py +148 -0
  29. omnibase_infra/models/runtime/__init__.py +4 -0
  30. omnibase_infra/models/runtime/model_resolved_dependencies.py +116 -0
  31. omnibase_infra/nodes/contract_registry_reducer/__init__.py +5 -0
  32. omnibase_infra/nodes/contract_registry_reducer/contract.yaml +6 -5
  33. omnibase_infra/nodes/contract_registry_reducer/contract_registration_event_router.py +689 -0
  34. omnibase_infra/nodes/contract_registry_reducer/reducer.py +9 -26
  35. omnibase_infra/nodes/effects/__init__.py +1 -1
  36. omnibase_infra/nodes/effects/models/__init__.py +6 -4
  37. omnibase_infra/nodes/effects/models/model_registry_response.py +1 -1
  38. omnibase_infra/nodes/effects/protocol_consul_client.py +1 -1
  39. omnibase_infra/nodes/effects/protocol_postgres_adapter.py +1 -1
  40. omnibase_infra/nodes/effects/registry_effect.py +1 -1
  41. omnibase_infra/nodes/node_contract_persistence_effect/__init__.py +101 -0
  42. omnibase_infra/nodes/node_contract_persistence_effect/contract.yaml +490 -0
  43. omnibase_infra/nodes/node_contract_persistence_effect/handlers/__init__.py +74 -0
  44. omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_cleanup_topics.py +217 -0
  45. omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_contract_upsert.py +242 -0
  46. omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_deactivate.py +194 -0
  47. omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_heartbeat.py +243 -0
  48. omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_mark_stale.py +208 -0
  49. omnibase_infra/nodes/node_contract_persistence_effect/handlers/handler_postgres_topic_update.py +298 -0
  50. omnibase_infra/nodes/node_contract_persistence_effect/models/__init__.py +15 -0
  51. omnibase_infra/nodes/node_contract_persistence_effect/models/model_persistence_result.py +52 -0
  52. omnibase_infra/nodes/node_contract_persistence_effect/node.py +131 -0
  53. omnibase_infra/nodes/node_contract_persistence_effect/registry/__init__.py +27 -0
  54. omnibase_infra/nodes/node_contract_persistence_effect/registry/registry_infra_contract_persistence_effect.py +251 -0
  55. omnibase_infra/nodes/node_registration_orchestrator/models/model_postgres_intent_payload.py +8 -12
  56. omnibase_infra/nodes/node_registry_effect/models/__init__.py +2 -2
  57. omnibase_infra/nodes/node_slack_alerter_effect/__init__.py +33 -0
  58. omnibase_infra/nodes/node_slack_alerter_effect/contract.yaml +291 -0
  59. omnibase_infra/nodes/node_slack_alerter_effect/node.py +106 -0
  60. omnibase_infra/projectors/__init__.py +6 -0
  61. omnibase_infra/projectors/projection_reader_contract.py +1301 -0
  62. omnibase_infra/runtime/__init__.py +12 -0
  63. omnibase_infra/runtime/baseline_subscriptions.py +13 -6
  64. omnibase_infra/runtime/contract_dependency_resolver.py +455 -0
  65. omnibase_infra/runtime/contract_registration_event_router.py +500 -0
  66. omnibase_infra/runtime/db/__init__.py +4 -0
  67. omnibase_infra/runtime/db/models/__init__.py +15 -10
  68. omnibase_infra/runtime/db/models/model_db_operation.py +40 -0
  69. omnibase_infra/runtime/db/models/model_db_param.py +24 -0
  70. omnibase_infra/runtime/db/models/model_db_repository_contract.py +40 -0
  71. omnibase_infra/runtime/db/models/model_db_return.py +26 -0
  72. omnibase_infra/runtime/db/models/model_db_safety_policy.py +32 -0
  73. omnibase_infra/runtime/emit_daemon/event_registry.py +34 -22
  74. omnibase_infra/runtime/event_bus_subcontract_wiring.py +63 -23
  75. omnibase_infra/runtime/intent_execution_router.py +430 -0
  76. omnibase_infra/runtime/models/__init__.py +6 -0
  77. omnibase_infra/runtime/models/model_contract_registry_config.py +41 -0
  78. omnibase_infra/runtime/models/model_intent_execution_summary.py +79 -0
  79. omnibase_infra/runtime/models/model_runtime_config.py +8 -0
  80. omnibase_infra/runtime/protocols/__init__.py +16 -0
  81. omnibase_infra/runtime/protocols/protocol_intent_executor.py +107 -0
  82. omnibase_infra/runtime/publisher_topic_scoped.py +16 -11
  83. omnibase_infra/runtime/registry_policy.py +29 -15
  84. omnibase_infra/runtime/request_response_wiring.py +793 -0
  85. omnibase_infra/runtime/service_kernel.py +295 -8
  86. omnibase_infra/runtime/service_runtime_host_process.py +149 -5
  87. omnibase_infra/runtime/util_version.py +5 -1
  88. omnibase_infra/schemas/schema_latency_baseline.sql +135 -0
  89. omnibase_infra/services/contract_publisher/config.py +4 -4
  90. omnibase_infra/services/contract_publisher/service.py +8 -5
  91. omnibase_infra/services/observability/injection_effectiveness/__init__.py +67 -0
  92. omnibase_infra/services/observability/injection_effectiveness/config.py +295 -0
  93. omnibase_infra/services/observability/injection_effectiveness/consumer.py +1461 -0
  94. omnibase_infra/services/observability/injection_effectiveness/models/__init__.py +32 -0
  95. omnibase_infra/services/observability/injection_effectiveness/models/model_agent_match.py +79 -0
  96. omnibase_infra/services/observability/injection_effectiveness/models/model_context_utilization.py +118 -0
  97. omnibase_infra/services/observability/injection_effectiveness/models/model_latency_breakdown.py +107 -0
  98. omnibase_infra/services/observability/injection_effectiveness/models/model_pattern_utilization.py +46 -0
  99. omnibase_infra/services/observability/injection_effectiveness/writer_postgres.py +596 -0
  100. omnibase_infra/services/registry_api/models/__init__.py +25 -0
  101. omnibase_infra/services/registry_api/models/model_contract_ref.py +44 -0
  102. omnibase_infra/services/registry_api/models/model_contract_view.py +81 -0
  103. omnibase_infra/services/registry_api/models/model_response_contracts.py +50 -0
  104. omnibase_infra/services/registry_api/models/model_response_topics.py +50 -0
  105. omnibase_infra/services/registry_api/models/model_topic_summary.py +57 -0
  106. omnibase_infra/services/registry_api/models/model_topic_view.py +63 -0
  107. omnibase_infra/services/registry_api/routes.py +205 -6
  108. omnibase_infra/services/registry_api/service.py +528 -1
  109. omnibase_infra/utils/__init__.py +7 -0
  110. omnibase_infra/utils/util_db_error_context.py +292 -0
  111. omnibase_infra/validation/infra_validators.py +3 -1
  112. omnibase_infra/validation/validation_exemptions.yaml +65 -0
  113. {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/METADATA +3 -3
  114. {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/RECORD +117 -58
  115. {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/WHEEL +0 -0
  116. {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/entry_points.txt +0 -0
  117. {omnibase_infra-0.3.1.dist-info → omnibase_infra-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,298 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Handler for PostgreSQL topic routing table updates.
4
+
5
+ This handler encapsulates PostgreSQL-specific persistence logic for the
6
+ NodeContractPersistenceEffect node, following the declarative node pattern where
7
+ handlers are extracted for testability and separation of concerns.
8
+
9
+ Architecture:
10
+ HandlerPostgresTopicUpdate is responsible for:
11
+ - Normalizing topic suffixes (stripping environment prefixes)
12
+ - Executing upsert operations against the PostgreSQL topics table
13
+ - Managing JSONB array of contract_ids for topic-to-contract mapping
14
+ - Returning structured ModelBackendResult
15
+
16
+ Timing, error classification, and sanitization are delegated to
17
+ MixinPostgresOpExecutor to eliminate boilerplate drift across handlers.
18
+
19
+ Topic Normalization:
20
+ Topics in contracts may include environment placeholders (e.g., "{env}.topic.name")
21
+ or actual environment prefixes (e.g., "dev.topic.name"). Before storage, these
22
+ prefixes are stripped to store only the topic suffix. This enables:
23
+ - Environment-agnostic topic routing queries
24
+ - Consistent topic identity across environments
25
+ - Simplified topic discovery and management
26
+
27
+ Coroutine Safety:
28
+ This handler is stateless and coroutine-safe for concurrent calls
29
+ with different payload instances. Thread-safety depends on the
30
+ underlying asyncpg connection pool implementation.
31
+
32
+ SQL Security:
33
+ All SQL queries use parameterized queries with positional placeholders
34
+ ($1, $2, etc.) to prevent SQL injection attacks. The asyncpg library
35
+ handles proper escaping and type conversion for all parameters.
36
+
37
+ Related:
38
+ - NodeContractPersistenceEffect: Parent effect node that coordinates handlers
39
+ - ModelPayloadUpdateTopic: Input payload model
40
+ - ModelBackendResult: Structured result model for backend operations
41
+ - MixinPostgresOpExecutor: Shared execution core for timing/error handling
42
+ - OMN-1845: Implementation ticket
43
+ - OMN-1857: Executor extraction ticket
44
+ """
45
+
46
+ from __future__ import annotations
47
+
48
+ import logging
49
+ from typing import TYPE_CHECKING
50
+ from uuid import UUID
51
+
52
+ from omnibase_infra.enums import (
53
+ EnumHandlerType,
54
+ EnumHandlerTypeCategory,
55
+ EnumPostgresErrorCode,
56
+ )
57
+ from omnibase_infra.errors import ModelInfraErrorContext, RepositoryExecutionError
58
+ from omnibase_infra.mixins.mixin_postgres_op_executor import MixinPostgresOpExecutor
59
+ from omnibase_infra.models.model_backend_result import ModelBackendResult
60
+
61
+ if TYPE_CHECKING:
62
+ import asyncpg
63
+
64
+ from omnibase_infra.nodes.contract_registry_reducer.models import (
65
+ ModelPayloadUpdateTopic,
66
+ )
67
+
68
+ logger = logging.getLogger(__name__)
69
+
70
+ # Known environment prefixes to strip from topic suffixes before storage.
71
+ # The placeholder prefix is checked first, then actual environment prefixes.
72
+ _ENVIRONMENT_PREFIXES: tuple[str, ...] = (
73
+ "{env}.", # Placeholder prefix (most common)
74
+ "dev.",
75
+ "prod.",
76
+ "staging.",
77
+ "local.",
78
+ "test.",
79
+ )
80
+
81
+ # SQL statement for topic upsert with JSONB array merge.
82
+ # Uses ON CONFLICT to handle existing topic+direction combinations.
83
+ # The JSONB containment operator (?) checks if contract_id already exists in array.
84
+ # If not present, appends to array; otherwise keeps existing array unchanged.
85
+ SQL_UPSERT_TOPIC = """
86
+ INSERT INTO topics (topic_suffix, direction, contract_ids, first_seen_at, last_seen_at, is_active)
87
+ VALUES ($1, $2, jsonb_build_array($3), $4, $5, TRUE)
88
+ ON CONFLICT (topic_suffix, direction) DO UPDATE SET
89
+ contract_ids = CASE
90
+ WHEN NOT topics.contract_ids ? $3
91
+ THEN topics.contract_ids || to_jsonb($3::text)
92
+ ELSE topics.contract_ids
93
+ END,
94
+ last_seen_at = EXCLUDED.last_seen_at,
95
+ is_active = TRUE,
96
+ updated_at = NOW()
97
+ RETURNING topic_suffix, direction, contract_ids;
98
+ """
99
+
100
+
101
+ def normalize_topic_for_storage(topic: str) -> str:
102
+ """Strip environment placeholder/prefix from topic before storage.
103
+
104
+ Topics in contracts may include environment placeholders like "{env}." or
105
+ actual environment prefixes like "dev.", "prod.", etc. This function
106
+ normalizes topics by stripping these prefixes to store only the topic suffix.
107
+
108
+ The normalization enables environment-agnostic topic routing queries and
109
+ consistent topic identity across different deployment environments.
110
+
111
+ Args:
112
+ topic: The topic string to normalize, potentially with an environment
113
+ prefix (e.g., "{env}.onex.evt.platform.contract-registered.v1" or
114
+ "dev.onex.evt.platform.contract-registered.v1").
115
+
116
+ Returns:
117
+ The normalized topic suffix without environment prefix
118
+ (e.g., "onex.evt.platform.contract-registered.v1").
119
+
120
+ Examples:
121
+ >>> normalize_topic_for_storage("{env}.onex.evt.platform.contract-registered.v1")
122
+ 'onex.evt.platform.contract-registered.v1'
123
+
124
+ >>> normalize_topic_for_storage("dev.onex.evt.platform.contract-registered.v1")
125
+ 'onex.evt.platform.contract-registered.v1'
126
+
127
+ >>> normalize_topic_for_storage("onex.evt.platform.contract-registered.v1")
128
+ 'onex.evt.platform.contract-registered.v1'
129
+ """
130
+ for prefix in _ENVIRONMENT_PREFIXES:
131
+ if topic.startswith(prefix):
132
+ return topic[len(prefix) :]
133
+ return topic
134
+
135
+
136
+ class HandlerPostgresTopicUpdate(MixinPostgresOpExecutor):
137
+ """Handler for PostgreSQL topic routing table updates.
138
+
139
+ Encapsulates all PostgreSQL-specific persistence logic for topic
140
+ routing updates.
141
+
142
+ Timing, error classification, and sanitization are handled by the
143
+ MixinPostgresOpExecutor base class, reducing boilerplate and ensuring
144
+ consistent error handling across all PostgreSQL handlers.
145
+
146
+ Topic Contract Mapping:
147
+ The topics table stores a JSONB array of contract_ids that reference
148
+ each topic. This handler uses JSONB operations to safely add contracts
149
+ to the array without duplicates:
150
+ - If contract_id not in array: append to array
151
+ - If contract_id already in array: keep array unchanged
152
+
153
+ Attributes:
154
+ _pool: asyncpg connection pool for database operations.
155
+
156
+ Example:
157
+ >>> import asyncpg
158
+ >>> pool = await asyncpg.create_pool(dsn="...")
159
+ >>> handler = HandlerPostgresTopicUpdate(pool)
160
+ >>> result = await handler.handle(payload, correlation_id)
161
+ >>> result.success
162
+ True
163
+
164
+ See Also:
165
+ - NodeContractPersistenceEffect: Parent node that uses this handler
166
+ - ModelPayloadUpdateTopic: Input payload model
167
+ - normalize_topic_for_storage: Topic normalization function
168
+ - MixinPostgresOpExecutor: Shared execution mechanics
169
+ """
170
+
171
+ def __init__(self, pool: asyncpg.Pool) -> None:
172
+ """Initialize handler with asyncpg connection pool.
173
+
174
+ Args:
175
+ pool: asyncpg connection pool for database operations.
176
+ The pool should be pre-configured and ready for use.
177
+ """
178
+ self._pool = pool
179
+
180
+ @property
181
+ def handler_type(self) -> EnumHandlerType:
182
+ """Architectural role of this handler."""
183
+ return EnumHandlerType.INFRA_HANDLER
184
+
185
+ @property
186
+ def handler_category(self) -> EnumHandlerTypeCategory:
187
+ """Behavioral classification of this handler."""
188
+ return EnumHandlerTypeCategory.EFFECT
189
+
190
+ async def handle(
191
+ self,
192
+ payload: ModelPayloadUpdateTopic,
193
+ correlation_id: UUID,
194
+ ) -> ModelBackendResult:
195
+ """Execute PostgreSQL topic routing table update.
196
+
197
+ Performs the upsert operation against the topics table with:
198
+ - Topic suffix normalization (strip environment prefix)
199
+ - JSONB array merge for contract_ids
200
+ - Parameterized SQL for injection prevention
201
+
202
+ Args:
203
+ payload: Update topic payload containing topic_suffix, direction,
204
+ contract_id, and last_seen_at timestamp.
205
+ correlation_id: Request correlation ID for distributed tracing.
206
+
207
+ Returns:
208
+ ModelBackendResult with:
209
+ - success: True if upsert completed successfully
210
+ - error: Sanitized error message if failed
211
+ - error_code: Error code for programmatic handling
212
+ - duration_ms: Operation duration in milliseconds
213
+ - backend_id: Set to "postgres"
214
+ - correlation_id: Passed through for tracing
215
+
216
+ Note:
217
+ Topic suffixes are normalized before storage - environment
218
+ prefixes like "{env}." or "dev." are stripped.
219
+ """
220
+ # Normalize topic suffix by stripping environment prefix
221
+ normalized_topic = normalize_topic_for_storage(payload.topic_suffix)
222
+
223
+ return await self._execute_postgres_op(
224
+ op_error_code=EnumPostgresErrorCode.TOPIC_UPDATE_ERROR,
225
+ correlation_id=correlation_id,
226
+ log_context={
227
+ "topic_suffix": normalized_topic,
228
+ "original_topic": payload.topic_suffix,
229
+ "direction": payload.direction,
230
+ "contract_id": payload.contract_id,
231
+ },
232
+ fn=lambda: self._execute_topic_update(
233
+ payload, normalized_topic, correlation_id
234
+ ),
235
+ )
236
+
237
+ async def _execute_topic_update(
238
+ self,
239
+ payload: ModelPayloadUpdateTopic,
240
+ normalized_topic: str,
241
+ correlation_id: UUID,
242
+ ) -> None:
243
+ """Execute the topic upsert query.
244
+
245
+ Args:
246
+ payload: Topic update payload.
247
+ normalized_topic: Environment-stripped topic suffix.
248
+ correlation_id: Correlation ID for logging.
249
+
250
+ Raises:
251
+ RepositoryExecutionError: If no result returned from upsert.
252
+ Any exception from asyncpg (handled by MixinPostgresOpExecutor).
253
+ """
254
+ async with self._pool.acquire() as conn:
255
+ result = await conn.fetchrow(
256
+ SQL_UPSERT_TOPIC,
257
+ normalized_topic,
258
+ payload.direction,
259
+ payload.contract_id,
260
+ payload.last_seen_at, # first_seen_at (on insert)
261
+ payload.last_seen_at, # last_seen_at
262
+ )
263
+
264
+ if result is None:
265
+ # RETURNING clause should always return a row on success
266
+ # If None, something unexpected happened
267
+ logger.warning(
268
+ "Topic update returned no result",
269
+ extra={
270
+ "topic_suffix": normalized_topic,
271
+ "direction": payload.direction,
272
+ "correlation_id": str(correlation_id),
273
+ },
274
+ )
275
+ context = ModelInfraErrorContext.with_correlation(
276
+ correlation_id=correlation_id,
277
+ transport_type="db",
278
+ operation="topic_update",
279
+ )
280
+ raise RepositoryExecutionError(
281
+ "postgres operation failed: no result returned",
282
+ context=context,
283
+ )
284
+
285
+ # Log for observability
286
+ logger.info(
287
+ "Topic update completed",
288
+ extra={
289
+ "topic_suffix": normalized_topic,
290
+ "original_topic": payload.topic_suffix,
291
+ "direction": payload.direction,
292
+ "contract_id": payload.contract_id,
293
+ "correlation_id": str(correlation_id),
294
+ },
295
+ )
296
+
297
+
298
+ __all__: list[str] = ["HandlerPostgresTopicUpdate", "normalize_topic_for_storage"]
@@ -0,0 +1,15 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Models for NodeContractPersistenceEffect.
4
+
5
+ Related:
6
+ - OMN-1845: NodeContractPersistenceEffect implementation
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from omnibase_infra.nodes.node_contract_persistence_effect.models.model_persistence_result import (
12
+ ModelPersistenceResult,
13
+ )
14
+
15
+ __all__ = ["ModelPersistenceResult"]
@@ -0,0 +1,52 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Persistence result model for NodeContractPersistenceEffect.
4
+
5
+ Related:
6
+ - OMN-1845: NodeContractPersistenceEffect implementation
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from datetime import UTC, datetime
12
+ from uuid import UUID
13
+
14
+ from pydantic import BaseModel, ConfigDict, Field
15
+
16
+ from omnibase_infra.enums import EnumPostgresErrorCode
17
+
18
+
19
+ class ModelPersistenceResult(BaseModel):
20
+ """Result of a contract persistence operation.
21
+
22
+ Attributes:
23
+ success: Whether the operation succeeded.
24
+ error: Error message if operation failed (sanitized).
25
+ error_code: Typed error code for programmatic handling. Uses
26
+ EnumPostgresErrorCode for strong typing and validation.
27
+ duration_ms: Operation duration in milliseconds.
28
+ correlation_id: Correlation ID for distributed tracing.
29
+ rows_affected: Number of database rows affected.
30
+ timestamp: When the operation completed.
31
+ """
32
+
33
+ model_config = ConfigDict(frozen=True, extra="forbid", from_attributes=True)
34
+
35
+ success: bool = Field(..., description="Whether the operation succeeded.")
36
+ error: str | None = Field(default=None, description="Sanitized error message.")
37
+ error_code: EnumPostgresErrorCode | None = Field(
38
+ default=None,
39
+ description="Typed error code for programmatic handling.",
40
+ )
41
+ duration_ms: float = Field(default=0.0, description="Operation duration in ms.")
42
+ correlation_id: UUID | None = Field(
43
+ default=None, description="Correlation ID for tracing."
44
+ )
45
+ rows_affected: int = Field(default=0, description="Database rows affected.")
46
+ timestamp: datetime = Field(
47
+ default_factory=lambda: datetime.now(UTC),
48
+ description="Operation completion time.",
49
+ )
50
+
51
+
52
+ __all__ = ["ModelPersistenceResult"]
@@ -0,0 +1,131 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Node Contract Persistence Effect - Declarative effect node for contract registry persistence.
4
+
5
+ This node follows the ONEX declarative pattern:
6
+ - DECLARATIVE effect driven by contract.yaml
7
+ - Zero custom routing logic - all behavior from handler_routing
8
+ - Lightweight shell that delegates to handlers via container resolution
9
+ - Used for ONEX-compliant runtime execution via RuntimeHostProcess
10
+ - Pattern: "Contract-driven, handlers wired externally"
11
+
12
+ Extends NodeEffect from omnibase_core for infrastructure I/O operations.
13
+ All handler routing is 100% driven by contract.yaml, not Python code.
14
+
15
+ Handler Routing Pattern:
16
+ 1. Receive intent from ContractRegistryReducer (ModelIntent with typed payload)
17
+ 2. Route to appropriate handler based on payload.intent_type (handler_routing)
18
+ 3. Execute PostgreSQL I/O via handler
19
+ 4. Return structured response (output_model in contract)
20
+
21
+ Design Decisions:
22
+ - 100% Contract-Driven: All routing logic in YAML, not Python
23
+ - Zero Custom Routing: Base class handles handler dispatch via contract
24
+ - Declarative Handlers: handler_routing section defines dispatch rules
25
+ - Container DI: Backend adapters resolved via container, not setter methods
26
+
27
+ Supported Intent Types (from ContractRegistryReducer):
28
+ - postgres.upsert_contract: Insert/update contract record
29
+ - postgres.update_topic: Update topic routing table
30
+ - postgres.mark_stale: Batch mark contracts as stale
31
+ - postgres.update_heartbeat: Update last_seen_at timestamp
32
+ - postgres.deactivate_contract: Mark contract as inactive (soft delete)
33
+ - postgres.cleanup_topic_references: Remove contract from topic arrays
34
+
35
+ Node Responsibilities:
36
+ - Route intents to appropriate PostgreSQL handlers
37
+ - Delegate all execution to handlers via base class
38
+ - NO custom logic - pure declarative shell
39
+
40
+ Related Modules:
41
+ - contract.yaml: Handler routing and I/O model definitions
42
+ - handlers/: PostgreSQL operation handlers
43
+ - contract_registry_reducer/: Source of intents
44
+ - models/model_payload_*.py: Intent payload types
45
+
46
+ Related Tickets:
47
+ - OMN-1845: NodeContractPersistenceEffect implementation
48
+ - OMN-1653: ContractRegistryReducer (source of intents)
49
+ """
50
+
51
+ from __future__ import annotations
52
+
53
+ from typing import TYPE_CHECKING
54
+
55
+ from omnibase_core.nodes.node_effect import NodeEffect
56
+
57
+ if TYPE_CHECKING:
58
+ from omnibase_core.models.container.model_onex_container import ModelONEXContainer
59
+ from omnibase_infra.models.runtime.model_resolved_dependencies import (
60
+ ModelResolvedDependencies,
61
+ )
62
+
63
+
64
+ # ONEX_EXCLUDE: declarative_node - OMN-1732 DEC-003 requires constructor injection
65
+ # for protocol dependencies. The _resolved_dependencies instance variable stores
66
+ # pre-resolved protocols from ContractDependencyResolver.
67
+ class NodeContractPersistenceEffect(NodeEffect):
68
+ """Declarative effect node for contract registry persistence.
69
+
70
+ This effect node is a lightweight shell that routes intents from
71
+ ContractRegistryReducer to PostgreSQL handlers. All routing and
72
+ execution logic is driven by contract.yaml - this class contains
73
+ NO custom routing code.
74
+
75
+ Supported Intent Types:
76
+ - postgres.upsert_contract: Upsert contract record
77
+ - postgres.update_topic: Update topic routing table
78
+ - postgres.mark_stale: Batch mark stale contracts
79
+ - postgres.update_heartbeat: Update heartbeat timestamp
80
+ - postgres.deactivate_contract: Soft delete contract
81
+ - postgres.cleanup_topic_references: Remove contract from topics
82
+
83
+ Args:
84
+ container: ONEX dependency injection container.
85
+ dependencies: Optional pre-resolved protocol dependencies. If provided,
86
+ the node will use these instead of resolving from container.
87
+ Part of OMN-1732 runtime dependency injection.
88
+
89
+ Dependency Injection:
90
+ Backend adapters (PostgreSQL) are resolved via container.
91
+ Handlers receive their dependencies directly via constructor injection.
92
+ This node contains NO instance variables for backend clients.
93
+
94
+ Example:
95
+ ```python
96
+ from omnibase_core.models.container import ModelONEXContainer
97
+ from omnibase_infra.nodes.node_contract_persistence_effect import (
98
+ NodeContractPersistenceEffect,
99
+ )
100
+
101
+ # Create effect node via container
102
+ container = ModelONEXContainer()
103
+ effect = NodeContractPersistenceEffect(container)
104
+
105
+ # Handlers receive dependencies directly via constructor
106
+ postgres_adapter = container.resolve(ProtocolPostgresAdapter)
107
+ upsert_handler = HandlerPostgresContractUpsert(postgres_adapter)
108
+
109
+ # Execute handler with intent payload
110
+ result = await upsert_handler.handle(intent_payload)
111
+ ```
112
+ """
113
+
114
+ def __init__(
115
+ self,
116
+ container: ModelONEXContainer,
117
+ dependencies: ModelResolvedDependencies | None = None,
118
+ ) -> None:
119
+ """Initialize effect node with container dependency injection.
120
+
121
+ Args:
122
+ container: ONEX dependency injection container.
123
+ dependencies: Optional pre-resolved protocol dependencies from
124
+ ContractDependencyResolver. If provided, the node uses these
125
+ instead of resolving from container. Part of OMN-1732.
126
+ """
127
+ super().__init__(container)
128
+ self._resolved_dependencies = dependencies
129
+
130
+
131
+ __all__ = ["NodeContractPersistenceEffect"]
@@ -0,0 +1,27 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Registry package for NodeContractPersistenceEffect.
4
+
5
+ This package provides infrastructure registry components for the
6
+ NodeContractPersistenceEffect node, following ONEX naming conventions.
7
+
8
+ Exports:
9
+ RegistryInfraContractPersistenceEffect: Factory and metadata registry for
10
+ creating NodeContractPersistenceEffect instances with dependency injection.
11
+
12
+ Usage:
13
+ >>> from omnibase_infra.nodes.node_contract_persistence_effect.registry import (
14
+ ... RegistryInfraContractPersistenceEffect,
15
+ ... )
16
+ >>> effect = RegistryInfraContractPersistenceEffect.create(container)
17
+
18
+ .. versionadded:: 0.5.0
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from omnibase_infra.nodes.node_contract_persistence_effect.registry.registry_infra_contract_persistence_effect import (
24
+ RegistryInfraContractPersistenceEffect,
25
+ )
26
+
27
+ __all__ = ["RegistryInfraContractPersistenceEffect"]