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
+ """Shared execution core for PostgreSQL operation handlers.
4
+
5
+ This mixin centralizes the mechanical aspects of PostgreSQL handler execution:
6
+ - Timing via time.perf_counter()
7
+ - Error classification and sanitization
8
+ - ModelBackendResult construction
9
+ - Structured logging with correlation IDs
10
+
11
+ By extracting this boilerplate into a reusable mixin, handlers are reduced from
12
+ ~200 lines to ~30 lines, eliminating drift risk where error handling patterns
13
+ could diverge across handlers.
14
+
15
+ Architecture:
16
+ Handlers inherit from MixinPostgresOpExecutor and call _execute_postgres_op()
17
+ with their operation-specific logic wrapped in a callable. The mixin handles
18
+ all timing, error classification, sanitization, and result construction.
19
+
20
+ Error Classification:
21
+ - TimeoutError, InfraTimeoutError → POSTGRES_TIMEOUT_ERROR (retriable)
22
+ - InfraAuthenticationError → POSTGRES_AUTH_ERROR (non-retriable)
23
+ - InfraConnectionError → POSTGRES_CONNECTION_ERROR (retriable)
24
+ - RepositoryExecutionError → op_error_code (handler-specified)
25
+ - Exception → POSTGRES_UNKNOWN_ERROR (non-retriable)
26
+
27
+ Usage:
28
+ ```python
29
+ class HandlerPostgresHeartbeat(MixinPostgresOpExecutor):
30
+ async def handle(self, payload, correlation_id) -> ModelBackendResult:
31
+ return await self._execute_postgres_op(
32
+ op_error_code=EnumPostgresErrorCode.HEARTBEAT_ERROR,
33
+ correlation_id=correlation_id,
34
+ log_context={"contract_id": payload.contract_id},
35
+ fn=lambda: self._do_heartbeat(payload),
36
+ )
37
+ ```
38
+
39
+ Related:
40
+ - EnumPostgresErrorCode: Error code enumeration with retriability metadata
41
+ - MixinAsyncCircuitBreaker: Circuit breaker mixin (not integrated here)
42
+ - OMN-1857: Extraction ticket for this mixin
43
+
44
+ Note on Circuit Breaker:
45
+ Per OMN-1857 design decision 1A, the executor should manage circuit breaker
46
+ internally. However, this initial implementation focuses on the core execution
47
+ mechanics. Circuit breaker integration will be added as a follow-up once the
48
+ basic pattern is validated.
49
+ """
50
+
51
+ from __future__ import annotations
52
+
53
+ import logging
54
+ import time
55
+ from collections.abc import Awaitable, Callable
56
+ from typing import TYPE_CHECKING, TypeVar
57
+
58
+ from omnibase_infra.enums import EnumPostgresErrorCode
59
+ from omnibase_infra.errors import (
60
+ InfraAuthenticationError,
61
+ InfraConnectionError,
62
+ InfraTimeoutError,
63
+ RepositoryExecutionError,
64
+ )
65
+ from omnibase_infra.models.model_backend_result import ModelBackendResult
66
+ from omnibase_infra.utils import sanitize_backend_error, sanitize_error_message
67
+
68
+ if TYPE_CHECKING:
69
+ from uuid import UUID
70
+
71
+ logger = logging.getLogger(__name__)
72
+
73
+ T = TypeVar("T")
74
+
75
+
76
+ class MixinPostgresOpExecutor:
77
+ """Shared execution core for PostgreSQL operation handlers.
78
+
79
+ Centralizes timing, error handling, sanitization, and result construction
80
+ for PostgreSQL operations. Handlers inherit this mixin and delegate to
81
+ _execute_postgres_op() for consistent mechanical behavior.
82
+
83
+ This mixin does NOT manage circuit breaker state - that responsibility
84
+ remains with the handler or a separate MixinAsyncCircuitBreaker composition.
85
+
86
+ Example:
87
+ ```python
88
+ class HandlerPostgresUpsert(MixinPostgresOpExecutor):
89
+ def __init__(self, pool: asyncpg.Pool) -> None:
90
+ self._pool = pool
91
+
92
+ async def handle(
93
+ self, payload: ModelPayloadUpsertContract, correlation_id: UUID
94
+ ) -> ModelBackendResult:
95
+ return await self._execute_postgres_op(
96
+ op_error_code=EnumPostgresErrorCode.UPSERT_ERROR,
97
+ correlation_id=correlation_id,
98
+ log_context={
99
+ "contract_id": payload.contract_id,
100
+ "node_name": payload.node_name,
101
+ },
102
+ fn=lambda: self._execute_upsert(payload),
103
+ )
104
+ ```
105
+
106
+ See Also:
107
+ - EnumPostgresErrorCode: Error codes with is_retriable property
108
+ - sanitize_error_message: Error sanitization utility
109
+ - ModelBackendResult: Structured result model
110
+ """
111
+
112
+ async def _execute_postgres_op(
113
+ self,
114
+ *,
115
+ op_error_code: EnumPostgresErrorCode,
116
+ correlation_id: UUID,
117
+ log_context: dict[str, object],
118
+ fn: Callable[[], Awaitable[T]],
119
+ ) -> ModelBackendResult:
120
+ """Execute a PostgreSQL operation with timing, error handling, and sanitization.
121
+
122
+ This method wraps the actual database operation (fn) with:
123
+ 1. Timing measurement via time.perf_counter()
124
+ 2. Exception classification into appropriate error codes
125
+ 3. Error message sanitization to prevent credential exposure
126
+ 4. Structured logging with correlation ID and context
127
+ 5. ModelBackendResult construction
128
+
129
+ Args:
130
+ op_error_code: Operation-specific error code for non-infrastructure
131
+ failures (e.g., UPSERT_ERROR, HEARTBEAT_ERROR). Used when the
132
+ operation fails due to business logic or query issues rather
133
+ than connection/auth problems.
134
+ correlation_id: Request correlation ID for distributed tracing.
135
+ log_context: Additional fields for structured logging (e.g.,
136
+ contract_id, node_name). Included in all log messages.
137
+ fn: Async callable that performs the actual database operation.
138
+ Should return any value on success. The return value is not
139
+ used - only success/failure matters.
140
+
141
+ Returns:
142
+ ModelBackendResult with:
143
+ - success: True if fn() completed without exception
144
+ - error: Sanitized error message (empty string on success)
145
+ - error_code: Appropriate EnumPostgresErrorCode
146
+ - duration_ms: Operation duration in milliseconds
147
+ - backend_id: "postgres"
148
+ - correlation_id: Passed through for tracing
149
+
150
+ Error Classification:
151
+ | Exception Type | Error Code | Retriable |
152
+ |-----------------------------|-------------------------|-----------|
153
+ | TimeoutError | TIMEOUT_ERROR | Yes |
154
+ | InfraTimeoutError | TIMEOUT_ERROR | Yes |
155
+ | InfraAuthenticationError | AUTH_ERROR | No |
156
+ | InfraConnectionError | CONNECTION_ERROR | Yes |
157
+ | RepositoryExecutionError | op_error_code | No |
158
+ | Exception | UNKNOWN_ERROR | No |
159
+
160
+ Note:
161
+ This method never raises exceptions. All errors are captured,
162
+ sanitized, logged, and returned in the result model.
163
+ """
164
+ start_time = time.perf_counter()
165
+ log_extra = {
166
+ "correlation_id": str(correlation_id),
167
+ **log_context,
168
+ }
169
+
170
+ try:
171
+ # Execute the operation
172
+ await fn()
173
+
174
+ duration_ms = (time.perf_counter() - start_time) * 1000
175
+
176
+ logger.debug(
177
+ "PostgreSQL operation completed successfully",
178
+ extra={**log_extra, "duration_ms": duration_ms},
179
+ )
180
+
181
+ return ModelBackendResult(
182
+ success=True,
183
+ duration_ms=duration_ms,
184
+ backend_id="postgres",
185
+ correlation_id=correlation_id,
186
+ )
187
+
188
+ except (TimeoutError, InfraTimeoutError) as e:
189
+ # Timeout - retriable error
190
+ duration_ms = (time.perf_counter() - start_time) * 1000
191
+ sanitized_error = sanitize_error_message(e)
192
+ logger.warning(
193
+ "PostgreSQL operation timed out",
194
+ extra={
195
+ **log_extra,
196
+ "duration_ms": duration_ms,
197
+ "error": sanitized_error,
198
+ },
199
+ )
200
+ return ModelBackendResult(
201
+ success=False,
202
+ error=sanitized_error,
203
+ error_code=EnumPostgresErrorCode.TIMEOUT_ERROR,
204
+ duration_ms=duration_ms,
205
+ backend_id="postgres",
206
+ correlation_id=correlation_id,
207
+ )
208
+
209
+ except InfraAuthenticationError as e:
210
+ # Authentication failure - non-retriable
211
+ duration_ms = (time.perf_counter() - start_time) * 1000
212
+ sanitized_error = sanitize_error_message(e)
213
+ logger.exception(
214
+ "PostgreSQL authentication failed",
215
+ extra={
216
+ **log_extra,
217
+ "duration_ms": duration_ms,
218
+ "error": sanitized_error,
219
+ },
220
+ )
221
+ return ModelBackendResult(
222
+ success=False,
223
+ error=sanitized_error,
224
+ error_code=EnumPostgresErrorCode.AUTH_ERROR,
225
+ duration_ms=duration_ms,
226
+ backend_id="postgres",
227
+ correlation_id=correlation_id,
228
+ )
229
+
230
+ except InfraConnectionError as e:
231
+ # Connection failure - retriable
232
+ duration_ms = (time.perf_counter() - start_time) * 1000
233
+ sanitized_error = sanitize_error_message(e)
234
+ logger.warning(
235
+ "PostgreSQL connection failed",
236
+ extra={
237
+ **log_extra,
238
+ "duration_ms": duration_ms,
239
+ "error": sanitized_error,
240
+ },
241
+ )
242
+ return ModelBackendResult(
243
+ success=False,
244
+ error=sanitized_error,
245
+ error_code=EnumPostgresErrorCode.CONNECTION_ERROR,
246
+ duration_ms=duration_ms,
247
+ backend_id="postgres",
248
+ correlation_id=correlation_id,
249
+ )
250
+
251
+ except RepositoryExecutionError as e:
252
+ # Query/operation failure - use handler-provided error code
253
+ duration_ms = (time.perf_counter() - start_time) * 1000
254
+ sanitized_error = sanitize_error_message(e)
255
+ logger.warning(
256
+ "PostgreSQL operation failed",
257
+ extra={
258
+ **log_extra,
259
+ "duration_ms": duration_ms,
260
+ "error": sanitized_error,
261
+ "error_code": op_error_code.value,
262
+ },
263
+ )
264
+ return ModelBackendResult(
265
+ success=False,
266
+ error=sanitized_error,
267
+ error_code=op_error_code,
268
+ duration_ms=duration_ms,
269
+ backend_id="postgres",
270
+ correlation_id=correlation_id,
271
+ )
272
+
273
+ except (
274
+ Exception
275
+ ) as e: # ONEX: catch-all for driver errors, encoding errors, pool errors
276
+ # Unknown error - non-retriable, requires investigation
277
+ duration_ms = (time.perf_counter() - start_time) * 1000
278
+ sanitized_error = sanitize_backend_error("postgres", e)
279
+ logger.exception(
280
+ "PostgreSQL operation failed with unexpected error",
281
+ extra={
282
+ **log_extra,
283
+ "duration_ms": duration_ms,
284
+ "error_type": type(e).__name__,
285
+ "error": sanitized_error,
286
+ },
287
+ )
288
+ return ModelBackendResult(
289
+ success=False,
290
+ error=sanitized_error,
291
+ error_code=EnumPostgresErrorCode.UNKNOWN_ERROR,
292
+ duration_ms=duration_ms,
293
+ backend_id="postgres",
294
+ correlation_id=correlation_id,
295
+ )
296
+
297
+
298
+ __all__ = ["MixinPostgresOpExecutor"]
@@ -30,6 +30,7 @@ from omnibase_infra.models.event_bus import (
30
30
  from omnibase_infra.models.handlers import ModelHandlerIdentifier
31
31
  from omnibase_infra.models.health import ModelHealthCheckResult
32
32
  from omnibase_infra.models.logging import ModelLogContext
33
+ from omnibase_infra.models.model_backend_result import ModelBackendResult
33
34
  from omnibase_infra.models.model_node_identity import ModelNodeIdentity
34
35
  from omnibase_infra.models.model_retry_error_classification import (
35
36
  ModelRetryErrorClassification,
@@ -93,6 +94,8 @@ __all__: list[str] = [
93
94
  "ModelConsumerRetryConfig",
94
95
  "ModelIdempotencyConfig",
95
96
  "ModelOffsetPolicyConfig",
97
+ # Backend result models
98
+ "ModelBackendResult",
96
99
  # Resilience models
97
100
  "ModelCircuitBreakerConfig",
98
101
  # Validation models
@@ -89,6 +89,7 @@ class ModelDependencySpec(BaseModel):
89
89
  model_config = ConfigDict(
90
90
  frozen=True,
91
91
  extra="forbid",
92
+ from_attributes=True, # ORM/pytest-xdist compatibility
92
93
  )
93
94
 
94
95
  # Identity
@@ -27,7 +27,7 @@ class ModelDiscoveredCapabilities(BaseModel):
27
27
  ... )
28
28
  """
29
29
 
30
- model_config = ConfigDict(extra="forbid", frozen=True)
30
+ model_config = ConfigDict(extra="forbid", frozen=True, from_attributes=True)
31
31
 
32
32
  operations: tuple[str, ...] = Field(
33
33
  default=(),
@@ -108,6 +108,7 @@ class ModelIntrospectionConfig(BaseModel):
108
108
  config = ModelIntrospectionConfig(
109
109
  node_id=node_id,
110
110
  node_type=EnumNodeKind.EFFECT, # Use enum directly (preferred)
111
+ node_name="my_effect_node",
111
112
  event_bus=event_bus,
112
113
  version="1.2.0",
113
114
  )
@@ -119,6 +120,7 @@ class ModelIntrospectionConfig(BaseModel):
119
120
  config = ModelIntrospectionConfig(
120
121
  node_id=node_id or uuid4(),
121
122
  node_type=EnumNodeKind.EFFECT, # Use enum directly (preferred)
123
+ node_name="my_custom_effect_node",
122
124
  event_bus=event_bus,
123
125
  operation_keywords=frozenset({"fetch", "upload", "download"}),
124
126
  )
@@ -141,6 +143,27 @@ class ModelIntrospectionConfig(BaseModel):
141
143
  "Accepts EnumNodeKind directly (preferred) or string (deprecated, will be coerced).",
142
144
  )
143
145
 
146
+ node_name: str = Field( # pattern-ok: canonical identifier, not a foreign key reference
147
+ ...,
148
+ min_length=1,
149
+ description="Node name for consumer group identification (e.g., 'claude_hook_effect'). "
150
+ "Cannot be empty.",
151
+ )
152
+
153
+ env: str = Field(
154
+ default="dev",
155
+ min_length=1,
156
+ description="Environment identifier (e.g., 'dev', 'staging', 'prod'). "
157
+ "Used for node identity in event bus subscriptions. Cannot be empty.",
158
+ )
159
+
160
+ service: str = Field(
161
+ default="onex",
162
+ min_length=1,
163
+ description="Service name (e.g., 'omniintelligence', 'omnibridge'). "
164
+ "Used for node identity in event bus subscriptions. Cannot be empty.",
165
+ )
166
+
144
167
  # Event bus for publishing introspection events.
145
168
  # Uses _EventBusType which provides:
146
169
  # - ProtocolEventBus | None during static analysis (TYPE_CHECKING)
@@ -155,7 +178,8 @@ class ModelIntrospectionConfig(BaseModel):
155
178
 
156
179
  version: str = Field(
157
180
  default="1.0.0",
158
- description="Node version string",
181
+ min_length=1,
182
+ description="Node version string. Cannot be empty.",
159
183
  )
160
184
 
161
185
  cache_ttl: float = Field(
@@ -284,6 +308,7 @@ class ModelIntrospectionConfig(BaseModel):
284
308
  model_config = ConfigDict(
285
309
  frozen=True,
286
310
  extra="forbid",
311
+ from_attributes=True, # ORM/pytest-xdist compatibility
287
312
  arbitrary_types_allowed=True, # Allow arbitrary types for event_bus
288
313
  json_schema_extra={
289
314
  "examples": [
@@ -291,6 +316,7 @@ class ModelIntrospectionConfig(BaseModel):
291
316
  {
292
317
  "node_id": "550e8400-e29b-41d4-a716-446655440000",
293
318
  "node_type": "EFFECT",
319
+ "node_name": "example_effect_node",
294
320
  "event_bus": None,
295
321
  "version": "1.0.0",
296
322
  "cache_ttl": 300.0,
@@ -305,6 +331,7 @@ class ModelIntrospectionConfig(BaseModel):
305
331
  {
306
332
  "node_id": "550e8400-e29b-41d4-a716-446655440001",
307
333
  "node_type": "COMPUTE",
334
+ "node_name": "example_compute_node",
308
335
  "event_bus": None,
309
336
  "version": "2.1.0",
310
337
  "cache_ttl": 120.0,
@@ -131,6 +131,7 @@ class ModelIntrospectionPerformanceMetrics(BaseModel):
131
131
  model_config = ConfigDict(
132
132
  frozen=True,
133
133
  extra="forbid",
134
+ from_attributes=True, # ORM/pytest-xdist compatibility
134
135
  json_schema_extra={
135
136
  "examples": [
136
137
  {
@@ -96,6 +96,7 @@ class ModelIntrospectionTaskConfig(BaseModel):
96
96
  model_config = ConfigDict(
97
97
  frozen=True,
98
98
  extra="forbid",
99
+ from_attributes=True, # ORM/pytest-xdist compatibility
99
100
  json_schema_extra={
100
101
  "examples": [
101
102
  {
@@ -61,7 +61,9 @@ class ModelBackendResult(BaseModel):
61
61
  Attributes:
62
62
  success: Whether the backend operation completed successfully.
63
63
  error: Sanitized error message if success is False.
64
- error_code: Optional error code for programmatic handling.
64
+ error_code: Error code for programmatic handling. PostgreSQL handlers
65
+ use EnumPostgresErrorCode enum values which serialize to strings
66
+ (e.g., "POSTGRES_CONNECTION_ERROR"). Other backends use string codes.
65
67
  duration_ms: Time taken for the operation in milliseconds.
66
68
  backend_id: Optional identifier for the backend instance.
67
69
 
@@ -96,18 +98,30 @@ class ModelBackendResult(BaseModel):
96
98
  >>> result.success
97
99
  True
98
100
 
99
- Example (failure case):
101
+ Example (failure case with PostgreSQL enum):
102
+ >>> from omnibase_infra.enums import EnumPostgresErrorCode
100
103
  >>> result = ModelBackendResult(
101
104
  ... success=False,
102
105
  ... error="Connection refused to database host",
103
- ... error_code="DATABASE_CONNECTION_ERROR",
106
+ ... error_code=EnumPostgresErrorCode.CONNECTION_ERROR,
104
107
  ... duration_ms=5000.0,
105
108
  ... backend_id="postgres",
106
109
  ... )
107
110
  >>> result.success
108
111
  False
109
- >>> result.error
110
- 'Connection refused to database host'
112
+ >>> result.error_code # Enum serializes to string
113
+ 'POSTGRES_CONNECTION_ERROR'
114
+
115
+ Example (failure case with Consul string code):
116
+ >>> result = ModelBackendResult(
117
+ ... success=False,
118
+ ... error="Service registration failed",
119
+ ... error_code="CONSUL_CONNECTION_ERROR",
120
+ ... duration_ms=1500.0,
121
+ ... backend_id="consul",
122
+ ... )
123
+ >>> result.error_code
124
+ 'CONSUL_CONNECTION_ERROR'
111
125
  """
112
126
 
113
127
  model_config = ConfigDict(frozen=True, extra="forbid", from_attributes=True)
@@ -122,7 +136,9 @@ class ModelBackendResult(BaseModel):
122
136
  )
123
137
  error_code: str | None = Field(
124
138
  default=None,
125
- description="Error code for programmatic handling (e.g., DATABASE_CONNECTION_ERROR)",
139
+ description="Error code for programmatic handling. PostgreSQL handlers use "
140
+ "EnumPostgresErrorCode enum values (which serialize to strings like "
141
+ "'POSTGRES_CONNECTION_ERROR'). Other backends use string codes directly.",
126
142
  )
127
143
  duration_ms: float = Field(
128
144
  default=0.0,
@@ -8,12 +8,15 @@ by orchestrators to query current entity state.
8
8
 
9
9
  Exports:
10
10
  ModelCapabilityFields: Container for capability fields in projection persistence
11
+ ModelContractProjection: Contract projection for Registry API queries
11
12
  ModelRegistrationProjection: Registration projection for orchestrator state queries
12
13
  ModelRegistrationSnapshot: Compacted snapshot for read optimization
13
14
  ModelSequenceInfo: Sequence information for projection ordering and idempotency
14
15
  ModelSnapshotTopicConfig: Kafka topic configuration for snapshot publishing
16
+ ModelTopicProjection: Topic projection for Registry API queries
15
17
 
16
18
  Related Tickets:
19
+ - OMN-1845: Create ProjectionReaderContract for contract/topic queries
17
20
  - OMN-1134: Registry Projection Extensions for Capabilities
18
21
  - OMN-947 (F2): Snapshot Publishing
19
22
  - OMN-944 (F1): Implement Registration Projection Schema
@@ -23,6 +26,9 @@ Related Tickets:
23
26
  from omnibase_infra.models.projection.model_capability_fields import (
24
27
  ModelCapabilityFields,
25
28
  )
29
+ from omnibase_infra.models.projection.model_contract_projection import (
30
+ ModelContractProjection,
31
+ )
26
32
  from omnibase_infra.models.projection.model_registration_projection import (
27
33
  ModelRegistrationProjection,
28
34
  )
@@ -33,11 +39,16 @@ from omnibase_infra.models.projection.model_sequence_info import ModelSequenceIn
33
39
  from omnibase_infra.models.projection.model_snapshot_topic_config import (
34
40
  ModelSnapshotTopicConfig,
35
41
  )
42
+ from omnibase_infra.models.projection.model_topic_projection import (
43
+ ModelTopicProjection,
44
+ )
36
45
 
37
46
  __all__ = [
38
47
  "ModelCapabilityFields",
48
+ "ModelContractProjection",
39
49
  "ModelRegistrationProjection",
40
50
  "ModelRegistrationSnapshot",
41
51
  "ModelSequenceInfo",
42
52
  "ModelSnapshotTopicConfig",
53
+ "ModelTopicProjection",
43
54
  ]