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,500 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Contract registration event router for kernel event processing.
4
+
5
+ This module provides an event router for routing contract registration events
6
+ in the ONEX kernel. Follows the same pattern as IntrospectionEventRouter.
7
+
8
+ The router:
9
+ - Subscribes to contract registration Kafka topics
10
+ - Parses incoming event messages based on topic suffix
11
+ - Routes to ContractRegistryReducer.reduce() for state transitions
12
+ - Emits intents for Effect layer execution (PostgreSQL operations)
13
+
14
+ Topics Handled (realm-agnostic, no environment prefix):
15
+ - onex.evt.platform.contract-registered.v1 -> ModelContractRegisteredEvent
16
+ - onex.evt.platform.contract-deregistered.v1 -> ModelContractDeregisteredEvent
17
+ - onex.evt.platform.node-heartbeat.v1 -> ModelNodeHeartbeatEvent
18
+
19
+ Design:
20
+ This class encapsulates the message routing logic for contract registration
21
+ events. The router uses topic suffix matching to determine the event type
22
+ and routes to the appropriate reducer handler.
23
+
24
+ The reducer returns ModelReducerOutput containing:
25
+ - New state (ModelContractRegistryState)
26
+ - Intents tuple for Effect layer execution
27
+
28
+ Intents are NOT published back to Kafka by the router. They are returned
29
+ for the caller (typically the kernel) to dispatch to the Effect layer.
30
+
31
+ Related:
32
+ - OMN-1869: Contract Registration Event Router
33
+ - OMN-1653: Contract Registry Reducer
34
+ - IntrospectionEventRouter: Reference implementation pattern
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ __all__ = ["ContractRegistrationEventRouter"]
40
+
41
+ import json
42
+ import logging
43
+ import time
44
+ from typing import TYPE_CHECKING
45
+ from uuid import UUID, uuid4
46
+
47
+ from pydantic import ValidationError
48
+
49
+ from omnibase_core.container import ModelONEXContainer
50
+ from omnibase_core.models.events import (
51
+ ModelContractDeregisteredEvent,
52
+ ModelContractRegisteredEvent,
53
+ ModelNodeHeartbeatEvent,
54
+ )
55
+ from omnibase_core.nodes import ModelReducerOutput
56
+ from omnibase_core.types import JsonType
57
+ from omnibase_infra.event_bus.models.model_event_message import ModelEventMessage
58
+ from omnibase_infra.nodes.contract_registry_reducer.models.model_contract_registry_state import (
59
+ ModelContractRegistryState,
60
+ )
61
+ from omnibase_infra.nodes.contract_registry_reducer.reducer import (
62
+ ContractRegistryReducer,
63
+ )
64
+ from omnibase_infra.utils import sanitize_error_message
65
+
66
+ if TYPE_CHECKING:
67
+ from omnibase_infra.protocols import ProtocolEventBusLike
68
+
69
+ logger = logging.getLogger(__name__)
70
+
71
+ # Topic suffix patterns for event type matching
72
+ # Topics are realm-agnostic (no environment prefix)
73
+ TOPIC_SUFFIX_CONTRACT_REGISTERED = "onex.evt.platform.contract-registered.v1"
74
+ TOPIC_SUFFIX_CONTRACT_DEREGISTERED = "onex.evt.platform.contract-deregistered.v1"
75
+ TOPIC_SUFFIX_NODE_HEARTBEAT = "onex.evt.platform.node-heartbeat.v1"
76
+
77
+
78
+ class ContractRegistrationEventRouter:
79
+ """Router for contract registration event messages from event bus.
80
+
81
+ This router handles incoming event messages for the contract registration
82
+ domain. It parses events based on topic suffix and routes them to the
83
+ ContractRegistryReducer for state machine processing.
84
+
85
+ The router propagates correlation IDs from incoming messages for
86
+ distributed tracing. If no correlation ID is present, it generates
87
+ a new one to ensure all operations can be traced.
88
+
89
+ This class follows the container-based dependency injection pattern,
90
+ receiving a ModelONEXContainer for service resolution while also
91
+ accepting explicit dependencies for router-specific configuration.
92
+
93
+ Attributes:
94
+ _container: ONEX service container for dependency resolution.
95
+ _reducer: The ContractRegistryReducer to route events to.
96
+ _event_bus: Event bus implementing ProtocolEventBusLike for publishing.
97
+ _output_topic: The topic to publish intent completion events to.
98
+ _state: Current contract registry state (maintained across messages).
99
+
100
+ Example:
101
+ >>> from omnibase_core.container import ModelONEXContainer
102
+ >>> container = ModelONEXContainer()
103
+ >>> reducer = ContractRegistryReducer()
104
+ >>> router = ContractRegistrationEventRouter(
105
+ ... container=container,
106
+ ... reducer=reducer,
107
+ ... event_bus=event_bus,
108
+ ... output_topic="contract.intents.output",
109
+ ... )
110
+ >>> # Use as callback for event bus subscription
111
+ >>> await event_bus.subscribe(
112
+ ... topic="dev.onex.evt.platform.contract-registered.v1",
113
+ ... group_id="contract-registry",
114
+ ... on_message=router.handle_message,
115
+ ... )
116
+
117
+ See Also:
118
+ - docs/patterns/container_dependency_injection.md for DI patterns.
119
+ - IntrospectionEventRouter for reference implementation.
120
+ """
121
+
122
+ def __init__(
123
+ self,
124
+ container: ModelONEXContainer,
125
+ reducer: ContractRegistryReducer,
126
+ event_bus: ProtocolEventBusLike,
127
+ output_topic: str,
128
+ ) -> None:
129
+ """Initialize ContractRegistrationEventRouter with container-based DI.
130
+
131
+ Follows the ONEX container-based DI pattern where the container is passed
132
+ as the first parameter for service resolution, with additional explicit
133
+ parameters for router-specific configuration.
134
+
135
+ Args:
136
+ container: ONEX service container for dependency resolution. Provides
137
+ access to service_registry for resolving shared services.
138
+ reducer: The ContractRegistryReducer to route events to.
139
+ event_bus: Event bus implementing ProtocolEventBusLike for publishing.
140
+ output_topic: The topic to publish intent completion events to.
141
+
142
+ Raises:
143
+ ValueError: If output_topic is empty.
144
+
145
+ Example:
146
+ >>> from omnibase_core.container import ModelONEXContainer
147
+ >>> container = ModelONEXContainer()
148
+ >>> reducer = ContractRegistryReducer()
149
+ >>> router = ContractRegistrationEventRouter(
150
+ ... container=container,
151
+ ... reducer=reducer,
152
+ ... event_bus=event_bus,
153
+ ... output_topic="contract.intents.output",
154
+ ... )
155
+
156
+ See Also:
157
+ - docs/patterns/container_dependency_injection.md for DI patterns.
158
+ """
159
+ if not output_topic:
160
+ raise ValueError("output_topic cannot be empty")
161
+
162
+ self._container = container
163
+ self._reducer = reducer
164
+ self._event_bus = event_bus
165
+ self._output_topic = output_topic
166
+ # Initialize empty state - maintained across message processing
167
+ self._state = ModelContractRegistryState()
168
+
169
+ logger.debug(
170
+ "ContractRegistrationEventRouter initialized",
171
+ extra={
172
+ "output_topic": output_topic,
173
+ "reducer_type": type(self._reducer).__name__,
174
+ "event_bus_type": type(self._event_bus).__name__,
175
+ },
176
+ )
177
+
178
+ @property
179
+ def container(self) -> ModelONEXContainer:
180
+ """Return the ONEX service container.
181
+
182
+ Returns:
183
+ The ModelONEXContainer instance passed during initialization.
184
+ """
185
+ return self._container
186
+
187
+ @property
188
+ def output_topic(self) -> str:
189
+ """Return the configured output topic for event publishing."""
190
+ return self._output_topic
191
+
192
+ @property
193
+ def reducer(self) -> ContractRegistryReducer:
194
+ """Return the reducer instance."""
195
+ return self._reducer
196
+
197
+ @property
198
+ def event_bus(self) -> ProtocolEventBusLike:
199
+ """Return the event bus instance."""
200
+ return self._event_bus
201
+
202
+ @property
203
+ def state(self) -> ModelContractRegistryState:
204
+ """Return the current contract registry state."""
205
+ return self._state
206
+
207
+ def extract_correlation_id_from_message(self, msg: ModelEventMessage) -> UUID:
208
+ """Extract correlation ID from message headers or payload.
209
+
210
+ Attempts to extract the correlation_id from message headers or payload
211
+ to ensure proper propagation for distributed tracing. Falls back to
212
+ generating a new UUID if no correlation ID is found.
213
+
214
+ This is a public method that may be called by external components
215
+ (e.g., ServiceKernel) to extract correlation IDs for intent execution.
216
+
217
+ Args:
218
+ msg: The incoming event message.
219
+
220
+ Returns:
221
+ UUID: The extracted or generated correlation ID.
222
+ """
223
+ # Try to extract from message headers if available
224
+ if hasattr(msg, "headers") and msg.headers is not None:
225
+ headers = msg.headers
226
+ if (
227
+ hasattr(headers, "correlation_id")
228
+ and headers.correlation_id is not None
229
+ ):
230
+ try:
231
+ correlation_id = headers.correlation_id
232
+ # Handle bytes-like values (duck typing)
233
+ if hasattr(correlation_id, "decode"):
234
+ correlation_id = correlation_id.decode("utf-8")
235
+ return UUID(str(correlation_id))
236
+ except (ValueError, TypeError, UnicodeDecodeError, AttributeError):
237
+ pass # Fall through to try payload extraction
238
+
239
+ # Try to extract from payload
240
+ try:
241
+ if msg.value is not None:
242
+ if hasattr(msg.value, "decode"):
243
+ payload_dict = json.loads(msg.value.decode("utf-8"))
244
+ else:
245
+ try:
246
+ payload_dict = json.loads(msg.value)
247
+ except TypeError:
248
+ payload_dict = msg.value
249
+
250
+ if payload_dict and "correlation_id" in payload_dict:
251
+ return UUID(str(payload_dict["correlation_id"]))
252
+ except (json.JSONDecodeError, ValueError, TypeError, KeyError, AttributeError):
253
+ pass # Fall through to generate new ID
254
+
255
+ # Generate new correlation ID as last resort
256
+ return uuid4()
257
+
258
+ def _determine_event_type(
259
+ self, topic: str
260
+ ) -> (
261
+ type[
262
+ ModelContractRegisteredEvent
263
+ | ModelContractDeregisteredEvent
264
+ | ModelNodeHeartbeatEvent
265
+ ]
266
+ | None
267
+ ):
268
+ """Determine event type based on topic suffix.
269
+
270
+ Args:
271
+ topic: The Kafka topic name.
272
+
273
+ Returns:
274
+ The event model class to use for parsing, or None if topic not recognized.
275
+ """
276
+ if topic.endswith(TOPIC_SUFFIX_CONTRACT_REGISTERED):
277
+ return ModelContractRegisteredEvent
278
+ elif topic.endswith(TOPIC_SUFFIX_CONTRACT_DEREGISTERED):
279
+ return ModelContractDeregisteredEvent
280
+ elif topic.endswith(TOPIC_SUFFIX_NODE_HEARTBEAT):
281
+ return ModelNodeHeartbeatEvent
282
+ return None
283
+
284
+ async def handle_message(
285
+ self, msg: ModelEventMessage
286
+ ) -> ModelReducerOutput[ModelContractRegistryState] | None:
287
+ """Handle incoming contract registration event message.
288
+
289
+ This callback is invoked for each message received on the subscribed topics.
290
+ It parses the raw JSON payload based on topic suffix, routes to the
291
+ ContractRegistryReducer, and updates internal state.
292
+
293
+ The method propagates the correlation_id from the incoming message
294
+ for distributed tracing. If no correlation_id is present in the message,
295
+ a new one is generated.
296
+
297
+ Args:
298
+ msg: The event message containing raw bytes in .value field.
299
+
300
+ Returns:
301
+ ModelReducerOutput containing new state and intents, or None on error.
302
+ The intents should be dispatched to the Effect layer by the caller.
303
+ """
304
+ # Extract correlation_id from message for proper propagation
305
+ callback_correlation_id = self.extract_correlation_id_from_message(msg)
306
+ callback_start_time = time.time()
307
+
308
+ # Extract topic from message
309
+ topic = getattr(msg, "topic", "") or ""
310
+ partition = getattr(msg, "partition", 0) or 0
311
+ offset = getattr(msg, "offset", 0) or 0
312
+
313
+ logger.debug(
314
+ "Contract registration message callback invoked (correlation_id=%s)",
315
+ callback_correlation_id,
316
+ extra={
317
+ "message_offset": offset,
318
+ "message_partition": partition,
319
+ "message_topic": topic,
320
+ },
321
+ )
322
+
323
+ try:
324
+ # Determine event type from topic
325
+ event_class = self._determine_event_type(topic)
326
+ if event_class is None:
327
+ logger.debug(
328
+ "Topic not recognized as contract registration event, skipping "
329
+ "(correlation_id=%s)",
330
+ callback_correlation_id,
331
+ extra={"topic": topic},
332
+ )
333
+ return None
334
+
335
+ # Parse message value
336
+ if msg.value is None:
337
+ logger.debug(
338
+ "Message value is None, skipping (correlation_id=%s)",
339
+ callback_correlation_id,
340
+ )
341
+ return None
342
+
343
+ # Parse message value using duck-typing patterns
344
+ if hasattr(msg.value, "decode"):
345
+ logger.debug(
346
+ "Parsing message value as bytes-like (correlation_id=%s)",
347
+ callback_correlation_id,
348
+ extra={"value_length": len(msg.value)},
349
+ )
350
+ payload_dict = json.loads(msg.value.decode("utf-8"))
351
+ else:
352
+ try:
353
+ logger.debug(
354
+ "Parsing message value as string-like (correlation_id=%s)",
355
+ callback_correlation_id,
356
+ extra={
357
+ "value_length": len(msg.value)
358
+ if hasattr(msg.value, "__len__")
359
+ else None
360
+ },
361
+ )
362
+ payload_dict = json.loads(msg.value)
363
+ except TypeError:
364
+ if hasattr(msg.value, "keys"):
365
+ logger.debug(
366
+ "Message value already dict-like (correlation_id=%s)",
367
+ callback_correlation_id,
368
+ )
369
+ payload_dict = msg.value
370
+ else:
371
+ logger.debug(
372
+ "Unexpected message value type: %s (correlation_id=%s)",
373
+ type(msg.value).__name__,
374
+ callback_correlation_id,
375
+ )
376
+ return None
377
+
378
+ # Parse as the appropriate event model
379
+ logger.debug(
380
+ "Validating payload as %s (correlation_id=%s)",
381
+ event_class.__name__,
382
+ callback_correlation_id,
383
+ )
384
+
385
+ event = event_class.model_validate(payload_dict)
386
+
387
+ logger.info(
388
+ "Event parsed successfully (correlation_id=%s)",
389
+ callback_correlation_id,
390
+ extra={
391
+ "event_type": type(event).__name__,
392
+ "event_id": str(event.event_id),
393
+ "node_name": event.node_name,
394
+ },
395
+ )
396
+
397
+ # Build event metadata for reducer
398
+ event_metadata: dict[str, JsonType] = {
399
+ "topic": topic,
400
+ "partition": partition,
401
+ "offset": offset,
402
+ "correlation_id": str(callback_correlation_id),
403
+ }
404
+
405
+ # Route to reducer
406
+ logger.info(
407
+ "Routing to contract registry reducer (correlation_id=%s)",
408
+ callback_correlation_id,
409
+ extra={
410
+ "event_type": type(event).__name__,
411
+ "node_name": event.node_name,
412
+ },
413
+ )
414
+ reducer_start_time = time.time()
415
+ result = self._reducer.reduce(self._state, event, event_metadata)
416
+ reducer_duration = time.time() - reducer_start_time
417
+
418
+ # Update internal state
419
+ self._state = result.result
420
+
421
+ logger.info(
422
+ "Contract registration event processed successfully: node_name=%s "
423
+ "in %.3fs (correlation_id=%s)",
424
+ event.node_name,
425
+ reducer_duration,
426
+ callback_correlation_id,
427
+ extra={
428
+ "reducer_duration_seconds": reducer_duration,
429
+ "event_type": type(event).__name__,
430
+ "node_name": event.node_name,
431
+ "intents_count": len(result.intents),
432
+ "items_processed": result.items_processed,
433
+ },
434
+ )
435
+
436
+ # Log intent summary for debugging
437
+ if result.intents:
438
+ logger.debug(
439
+ "Reducer emitted %d intents (correlation_id=%s)",
440
+ len(result.intents),
441
+ callback_correlation_id,
442
+ extra={
443
+ "intent_targets": [intent.target for intent in result.intents],
444
+ },
445
+ )
446
+
447
+ return result
448
+
449
+ except ValidationError as validation_error:
450
+ # Not a valid event for this topic - skip
451
+ logger.debug(
452
+ "Message is not a valid contract registration event, skipping "
453
+ "(correlation_id=%s)",
454
+ callback_correlation_id,
455
+ extra={
456
+ "validation_error_count": validation_error.error_count(),
457
+ "topic": topic,
458
+ },
459
+ )
460
+ return None
461
+
462
+ except json.JSONDecodeError as json_error:
463
+ logger.warning(
464
+ "Failed to decode JSON from message: %s (correlation_id=%s)",
465
+ sanitize_error_message(json_error),
466
+ callback_correlation_id,
467
+ extra={
468
+ "error_type": type(json_error).__name__,
469
+ "error_position": getattr(json_error, "pos", None),
470
+ "topic": topic,
471
+ },
472
+ )
473
+ return None
474
+
475
+ except Exception as msg_error:
476
+ # Use warning instead of exception to avoid credential exposure
477
+ # in tracebacks (connection errors may contain DSN with password)
478
+ logger.warning(
479
+ "Failed to process contract registration message: %s (correlation_id=%s)",
480
+ sanitize_error_message(msg_error),
481
+ callback_correlation_id,
482
+ extra={
483
+ "error_type": type(msg_error).__name__,
484
+ "topic": topic,
485
+ },
486
+ )
487
+ return None
488
+
489
+ finally:
490
+ callback_duration = time.time() - callback_start_time
491
+ logger.debug(
492
+ "Contract registration message callback completed in %.3fs "
493
+ "(correlation_id=%s)",
494
+ callback_duration,
495
+ callback_correlation_id,
496
+ extra={
497
+ "callback_duration_seconds": callback_duration,
498
+ "topic": topic,
499
+ },
500
+ )
@@ -56,8 +56,10 @@ from __future__ import annotations
56
56
 
57
57
  from omnibase_infra.runtime.db.models import (
58
58
  ModelDbOperation,
59
+ ModelDbParam,
59
60
  ModelDbRepositoryContract,
60
61
  ModelDbReturn,
62
+ ModelDbSafetyPolicy,
61
63
  ModelRepositoryRuntimeConfig,
62
64
  )
63
65
  from omnibase_infra.runtime.db.postgres_repository_runtime import (
@@ -66,8 +68,10 @@ from omnibase_infra.runtime.db.postgres_repository_runtime import (
66
68
 
67
69
  __all__: list[str] = [
68
70
  "ModelDbOperation",
71
+ "ModelDbParam",
69
72
  "ModelDbRepositoryContract",
70
73
  "ModelDbReturn",
74
+ "ModelDbSafetyPolicy",
71
75
  "ModelRepositoryRuntimeConfig",
72
76
  "PostgresRepositoryRuntime",
73
77
  ]
@@ -2,9 +2,8 @@
2
2
  # Copyright (c) 2025 OmniNode Team
3
3
  """Database Runtime Models Module.
4
4
 
5
- This module exports Pydantic models for database runtime configuration.
6
- Contract models (ModelDbRepositoryContract, ModelDbOperation, ModelDbReturn)
7
- are imported from omnibase_core.models.contracts.
5
+ This module exports Pydantic models for database runtime configuration
6
+ and repository contracts.
8
7
 
9
8
  Exports:
10
9
  ModelRepositoryRuntimeConfig: Configuration for PostgresRepositoryRuntime
@@ -14,19 +13,23 @@ Exports:
14
13
  - Determinism controls (primary_key_column, default_order_by)
15
14
  - Metrics emission configuration
16
15
 
17
- ModelDbRepositoryContract: (re-export from omnibase_core)
18
- ModelDbOperation: (re-export from omnibase_core)
19
- ModelDbReturn: (re-export from omnibase_core)
16
+ ModelDbRepositoryContract: Repository contract definition
17
+ ModelDbOperation: Individual database operation definition
18
+ ModelDbParam: Parameter definition for operations
19
+ ModelDbReturn: Return type specification
20
+ ModelDbSafetyPolicy: Safety constraints for operations
20
21
  """
21
22
 
22
23
  from __future__ import annotations
23
24
 
24
- # Contract models from omnibase_core (canonical source)
25
- from omnibase_core.models.contracts import (
26
- ModelDbOperation,
25
+ # Contract models (local to omnibase_infra since 0.3.2)
26
+ from omnibase_infra.runtime.db.models.model_db_operation import ModelDbOperation
27
+ from omnibase_infra.runtime.db.models.model_db_param import ModelDbParam
28
+ from omnibase_infra.runtime.db.models.model_db_repository_contract import (
27
29
  ModelDbRepositoryContract,
28
- ModelDbReturn,
29
30
  )
31
+ from omnibase_infra.runtime.db.models.model_db_return import ModelDbReturn
32
+ from omnibase_infra.runtime.db.models.model_db_safety_policy import ModelDbSafetyPolicy
30
33
 
31
34
  # Runtime config is local to omnibase_infra
32
35
  from omnibase_infra.runtime.db.models.model_repository_runtime_config import (
@@ -35,7 +38,9 @@ from omnibase_infra.runtime.db.models.model_repository_runtime_config import (
35
38
 
36
39
  __all__: list[str] = [
37
40
  "ModelDbOperation",
41
+ "ModelDbParam",
38
42
  "ModelDbRepositoryContract",
39
43
  "ModelDbReturn",
44
+ "ModelDbSafetyPolicy",
40
45
  "ModelRepositoryRuntimeConfig",
41
46
  ]
@@ -0,0 +1,40 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Database operation model for SQL operations."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+ from omnibase_infra.runtime.db.models.model_db_param import ModelDbParam
10
+ from omnibase_infra.runtime.db.models.model_db_return import ModelDbReturn
11
+ from omnibase_infra.runtime.db.models.model_db_safety_policy import ModelDbSafetyPolicy
12
+
13
+
14
+ class ModelDbOperation(BaseModel):
15
+ """Individual database operation definition.
16
+
17
+ Attributes:
18
+ mode: Operation mode ("read", "write", "delete")
19
+ sql: SQL query template with positional parameters ($1, $2, ...)
20
+ params: Parameter definitions (name -> ModelDbParam)
21
+ returns: Return type specification
22
+ safety_policy: Safety constraints for this operation
23
+ """
24
+
25
+ model_config = ConfigDict(frozen=True, extra="forbid")
26
+
27
+ mode: str = Field(..., description="Operation mode (read/write/delete)")
28
+ sql: str = Field(..., description="SQL query template")
29
+ params: dict[str, ModelDbParam] = Field(
30
+ default_factory=dict, description="Parameter definitions"
31
+ )
32
+ returns: ModelDbReturn = Field(
33
+ default_factory=ModelDbReturn, description="Return type specification"
34
+ )
35
+ safety_policy: ModelDbSafetyPolicy = Field(
36
+ default_factory=ModelDbSafetyPolicy, description="Safety constraints"
37
+ )
38
+
39
+
40
+ __all__ = ["ModelDbOperation"]
@@ -0,0 +1,24 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Database parameter model for SQL operations."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+
10
+ class ModelDbParam(BaseModel):
11
+ """Parameter definition for a database operation.
12
+
13
+ Attributes:
14
+ name: Parameter name (used for documentation/error messages)
15
+ param_type: Parameter type (e.g., "integer", "string", "uuid")
16
+ """
17
+
18
+ model_config = ConfigDict(frozen=True, extra="forbid")
19
+
20
+ name: str = Field(..., description="Parameter name")
21
+ param_type: str = Field(..., description="Parameter type")
22
+
23
+
24
+ __all__ = ["ModelDbParam"]
@@ -0,0 +1,40 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Database repository contract model."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+ from omnibase_infra.runtime.db.models.model_db_operation import ModelDbOperation
10
+
11
+
12
+ class ModelDbRepositoryContract(BaseModel):
13
+ """Complete database repository contract.
14
+
15
+ Defines all operations, tables, and configuration for a database repository.
16
+
17
+ Attributes:
18
+ name: Repository name (used for identification)
19
+ engine: Database engine ("postgres", "mysql", etc.)
20
+ database_ref: Reference to database connection configuration
21
+ tables: List of tables this repository operates on
22
+ models: Mapping of model names to their module paths
23
+ ops: Mapping of operation names to their definitions
24
+ """
25
+
26
+ model_config = ConfigDict(frozen=True, extra="forbid")
27
+
28
+ name: str = Field(..., description="Repository name")
29
+ engine: str = Field(default="postgres", description="Database engine")
30
+ database_ref: str = Field(..., description="Database connection reference")
31
+ tables: list[str] = Field(default_factory=list, description="Tables operated on")
32
+ models: dict[str, str] = Field(
33
+ default_factory=dict, description="Model name -> module path mapping"
34
+ )
35
+ ops: dict[str, ModelDbOperation] = Field(
36
+ default_factory=dict, description="Operation name -> definition mapping"
37
+ )
38
+
39
+
40
+ __all__ = ["ModelDbRepositoryContract"]