omnibase_infra 0.2.1__py3-none-any.whl → 0.2.3__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 (161) hide show
  1. omnibase_infra/__init__.py +1 -1
  2. omnibase_infra/adapters/adapter_onex_tool_execution.py +451 -0
  3. omnibase_infra/capabilities/__init__.py +15 -0
  4. omnibase_infra/capabilities/capability_inference_rules.py +211 -0
  5. omnibase_infra/capabilities/contract_capability_extractor.py +221 -0
  6. omnibase_infra/capabilities/intent_type_extractor.py +160 -0
  7. omnibase_infra/cli/commands.py +1 -1
  8. omnibase_infra/configs/widget_mapping.yaml +176 -0
  9. omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +5 -2
  10. omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +5 -2
  11. omnibase_infra/enums/__init__.py +6 -0
  12. omnibase_infra/enums/enum_handler_error_type.py +10 -0
  13. omnibase_infra/enums/enum_handler_source_mode.py +72 -0
  14. omnibase_infra/enums/enum_kafka_acks.py +99 -0
  15. omnibase_infra/errors/error_compute_registry.py +4 -1
  16. omnibase_infra/errors/error_event_bus_registry.py +4 -1
  17. omnibase_infra/errors/error_infra.py +3 -1
  18. omnibase_infra/errors/error_policy_registry.py +4 -1
  19. omnibase_infra/event_bus/event_bus_kafka.py +1 -1
  20. omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +59 -10
  21. omnibase_infra/handlers/__init__.py +8 -1
  22. omnibase_infra/handlers/handler_consul.py +7 -1
  23. omnibase_infra/handlers/handler_db.py +10 -3
  24. omnibase_infra/handlers/handler_graph.py +10 -5
  25. omnibase_infra/handlers/handler_http.py +8 -2
  26. omnibase_infra/handlers/handler_intent.py +387 -0
  27. omnibase_infra/handlers/handler_mcp.py +745 -63
  28. omnibase_infra/handlers/handler_vault.py +11 -5
  29. omnibase_infra/handlers/mixins/mixin_consul_kv.py +4 -3
  30. omnibase_infra/handlers/mixins/mixin_consul_service.py +2 -1
  31. omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +7 -0
  32. omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +308 -4
  33. omnibase_infra/handlers/service_discovery/models/model_service_info.py +10 -0
  34. omnibase_infra/mixins/mixin_async_circuit_breaker.py +3 -2
  35. omnibase_infra/mixins/mixin_node_introspection.py +42 -7
  36. omnibase_infra/mixins/mixin_retry_execution.py +1 -1
  37. omnibase_infra/models/discovery/model_introspection_config.py +11 -0
  38. omnibase_infra/models/handlers/__init__.py +48 -5
  39. omnibase_infra/models/handlers/model_bootstrap_handler_descriptor.py +162 -0
  40. omnibase_infra/models/handlers/model_contract_discovery_result.py +6 -4
  41. omnibase_infra/models/handlers/model_handler_descriptor.py +15 -0
  42. omnibase_infra/models/handlers/model_handler_source_config.py +220 -0
  43. omnibase_infra/models/mcp/__init__.py +15 -0
  44. omnibase_infra/models/mcp/model_mcp_contract_config.py +80 -0
  45. omnibase_infra/models/mcp/model_mcp_server_config.py +67 -0
  46. omnibase_infra/models/mcp/model_mcp_tool_definition.py +73 -0
  47. omnibase_infra/models/mcp/model_mcp_tool_parameter.py +35 -0
  48. omnibase_infra/models/registration/model_node_capabilities.py +11 -0
  49. omnibase_infra/models/registration/model_node_introspection_event.py +9 -0
  50. omnibase_infra/models/runtime/model_handler_contract.py +25 -9
  51. omnibase_infra/models/runtime/model_loaded_handler.py +9 -0
  52. omnibase_infra/nodes/architecture_validator/contract_architecture_validator.yaml +0 -5
  53. omnibase_infra/nodes/architecture_validator/registry/registry_infra_architecture_validator.py +17 -10
  54. omnibase_infra/nodes/effects/contract.yaml +0 -5
  55. omnibase_infra/nodes/node_registration_orchestrator/contract.yaml +7 -0
  56. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_introspected.py +86 -1
  57. omnibase_infra/nodes/node_registration_orchestrator/introspection_event_router.py +3 -3
  58. omnibase_infra/nodes/node_registration_orchestrator/plugin.py +1 -1
  59. omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +9 -8
  60. omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +4 -3
  61. omnibase_infra/nodes/node_registration_orchestrator/wiring.py +14 -13
  62. omnibase_infra/nodes/node_registration_storage_effect/contract.yaml +0 -5
  63. omnibase_infra/nodes/node_registration_storage_effect/node.py +4 -1
  64. omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +47 -26
  65. omnibase_infra/nodes/node_registry_effect/contract.yaml +0 -5
  66. omnibase_infra/nodes/node_registry_effect/handlers/handler_partial_retry.py +2 -1
  67. omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +28 -20
  68. omnibase_infra/plugins/examples/plugin_json_normalizer.py +2 -2
  69. omnibase_infra/plugins/examples/plugin_json_normalizer_error_handling.py +2 -2
  70. omnibase_infra/plugins/plugin_compute_base.py +16 -2
  71. omnibase_infra/protocols/__init__.py +2 -0
  72. omnibase_infra/protocols/protocol_container_aware.py +200 -0
  73. omnibase_infra/protocols/protocol_event_projector.py +1 -1
  74. omnibase_infra/runtime/__init__.py +90 -1
  75. omnibase_infra/runtime/binding_config_resolver.py +102 -37
  76. omnibase_infra/runtime/constants_notification.py +75 -0
  77. omnibase_infra/runtime/contract_handler_discovery.py +6 -1
  78. omnibase_infra/runtime/handler_bootstrap_source.py +507 -0
  79. omnibase_infra/runtime/handler_contract_config_loader.py +603 -0
  80. omnibase_infra/runtime/handler_contract_source.py +267 -186
  81. omnibase_infra/runtime/handler_identity.py +81 -0
  82. omnibase_infra/runtime/handler_plugin_loader.py +19 -2
  83. omnibase_infra/runtime/handler_registry.py +11 -3
  84. omnibase_infra/runtime/handler_source_resolver.py +326 -0
  85. omnibase_infra/runtime/mixin_semver_cache.py +25 -1
  86. omnibase_infra/runtime/mixins/__init__.py +7 -0
  87. omnibase_infra/runtime/mixins/mixin_projector_notification_publishing.py +566 -0
  88. omnibase_infra/runtime/mixins/mixin_projector_sql_operations.py +31 -10
  89. omnibase_infra/runtime/models/__init__.py +24 -0
  90. omnibase_infra/runtime/models/model_health_check_result.py +2 -1
  91. omnibase_infra/runtime/models/model_projector_notification_config.py +171 -0
  92. omnibase_infra/runtime/models/model_transition_notification_outbox_config.py +112 -0
  93. omnibase_infra/runtime/models/model_transition_notification_outbox_metrics.py +140 -0
  94. omnibase_infra/runtime/models/model_transition_notification_publisher_metrics.py +357 -0
  95. omnibase_infra/runtime/projector_plugin_loader.py +1 -1
  96. omnibase_infra/runtime/projector_shell.py +229 -1
  97. omnibase_infra/runtime/protocol_lifecycle_executor.py +6 -6
  98. omnibase_infra/runtime/protocols/__init__.py +10 -0
  99. omnibase_infra/runtime/registry/registry_protocol_binding.py +16 -15
  100. omnibase_infra/runtime/registry_contract_source.py +693 -0
  101. omnibase_infra/runtime/registry_policy.py +9 -326
  102. omnibase_infra/runtime/secret_resolver.py +4 -2
  103. omnibase_infra/runtime/service_kernel.py +11 -3
  104. omnibase_infra/runtime/service_message_dispatch_engine.py +4 -2
  105. omnibase_infra/runtime/service_runtime_host_process.py +589 -106
  106. omnibase_infra/runtime/transition_notification_outbox.py +1190 -0
  107. omnibase_infra/runtime/transition_notification_publisher.py +764 -0
  108. omnibase_infra/runtime/util_container_wiring.py +6 -5
  109. omnibase_infra/runtime/util_wiring.py +17 -4
  110. omnibase_infra/schemas/schema_transition_notification_outbox.sql +245 -0
  111. omnibase_infra/services/__init__.py +21 -0
  112. omnibase_infra/services/corpus_capture.py +7 -1
  113. omnibase_infra/services/mcp/__init__.py +31 -0
  114. omnibase_infra/services/mcp/mcp_server_lifecycle.py +449 -0
  115. omnibase_infra/services/mcp/service_mcp_tool_discovery.py +411 -0
  116. omnibase_infra/services/mcp/service_mcp_tool_registry.py +329 -0
  117. omnibase_infra/services/mcp/service_mcp_tool_sync.py +547 -0
  118. omnibase_infra/services/registry_api/__init__.py +40 -0
  119. omnibase_infra/services/registry_api/main.py +261 -0
  120. omnibase_infra/services/registry_api/models/__init__.py +66 -0
  121. omnibase_infra/services/registry_api/models/model_capability_widget_mapping.py +38 -0
  122. omnibase_infra/services/registry_api/models/model_pagination_info.py +48 -0
  123. omnibase_infra/services/registry_api/models/model_registry_discovery_response.py +73 -0
  124. omnibase_infra/services/registry_api/models/model_registry_health_response.py +49 -0
  125. omnibase_infra/services/registry_api/models/model_registry_instance_view.py +88 -0
  126. omnibase_infra/services/registry_api/models/model_registry_node_view.py +88 -0
  127. omnibase_infra/services/registry_api/models/model_registry_summary.py +60 -0
  128. omnibase_infra/services/registry_api/models/model_response_list_instances.py +43 -0
  129. omnibase_infra/services/registry_api/models/model_response_list_nodes.py +51 -0
  130. omnibase_infra/services/registry_api/models/model_warning.py +49 -0
  131. omnibase_infra/services/registry_api/models/model_widget_defaults.py +28 -0
  132. omnibase_infra/services/registry_api/models/model_widget_mapping.py +51 -0
  133. omnibase_infra/services/registry_api/routes.py +371 -0
  134. omnibase_infra/services/registry_api/service.py +837 -0
  135. omnibase_infra/services/service_capability_query.py +4 -4
  136. omnibase_infra/services/service_health.py +3 -2
  137. omnibase_infra/services/service_timeout_emitter.py +20 -3
  138. omnibase_infra/services/service_timeout_scanner.py +7 -3
  139. omnibase_infra/services/session/__init__.py +56 -0
  140. omnibase_infra/services/session/config_consumer.py +120 -0
  141. omnibase_infra/services/session/config_store.py +139 -0
  142. omnibase_infra/services/session/consumer.py +1007 -0
  143. omnibase_infra/services/session/protocol_session_aggregator.py +117 -0
  144. omnibase_infra/services/session/store.py +997 -0
  145. omnibase_infra/utils/__init__.py +19 -0
  146. omnibase_infra/utils/util_atomic_file.py +261 -0
  147. omnibase_infra/utils/util_db_transaction.py +239 -0
  148. omnibase_infra/utils/util_dsn_validation.py +1 -1
  149. omnibase_infra/utils/util_retry_optimistic.py +281 -0
  150. omnibase_infra/validation/__init__.py +3 -19
  151. omnibase_infra/validation/contracts/security.validation.yaml +114 -0
  152. omnibase_infra/validation/infra_validators.py +35 -24
  153. omnibase_infra/validation/validation_exemptions.yaml +140 -9
  154. omnibase_infra/validation/validator_chain_propagation.py +2 -2
  155. omnibase_infra/validation/validator_runtime_shape.py +1 -1
  156. omnibase_infra/validation/validator_security.py +473 -370
  157. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/METADATA +3 -3
  158. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/RECORD +161 -98
  159. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/WHEEL +0 -0
  160. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/entry_points.txt +0 -0
  161. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/licenses/LICENSE +0 -0
@@ -207,7 +207,9 @@ from typing import TYPE_CHECKING, ClassVar, TypedDict, cast
207
207
  from uuid import UUID, uuid4
208
208
 
209
209
  from omnibase_core.enums import EnumNodeKind
210
+ from omnibase_core.models.events.model_event_envelope import ModelEventEnvelope
210
211
  from omnibase_core.models.primitives.model_semver import ModelSemVer
212
+ from omnibase_infra.capabilities import ContractCapabilityExtractor
211
213
  from omnibase_infra.enums import EnumInfraTransportType, EnumIntrospectionReason
212
214
  from omnibase_infra.errors import ModelInfraErrorContext, ProtocolConfigurationError
213
215
  from omnibase_infra.models.discovery import (
@@ -227,8 +229,11 @@ from omnibase_infra.models.registration.model_node_introspection_event import (
227
229
  )
228
230
 
229
231
  if TYPE_CHECKING:
232
+ from omnibase_core.models.contracts import ModelContractBase
230
233
  from omnibase_core.protocols.event_bus.protocol_event_bus import ProtocolEventBus
231
- from omnibase_infra.event_bus.models import ModelEventMessage
234
+ from omnibase_core.protocols.event_bus.protocol_event_message import (
235
+ ProtocolEventMessage,
236
+ )
232
237
 
233
238
  logger = logging.getLogger(__name__)
234
239
 
@@ -243,6 +248,9 @@ PERF_THRESHOLD_DISCOVER_CAPABILITIES_MS = 30.0
243
248
  PERF_THRESHOLD_GET_INTROSPECTION_DATA_MS = 50.0
244
249
  PERF_THRESHOLD_CACHE_HIT_MS = 1.0
245
250
 
251
+ # Module-level capability extractor instance (stateless, can be shared)
252
+ _CAPABILITY_EXTRACTOR = ContractCapabilityExtractor()
253
+
246
254
 
247
255
  class PerformanceMetricsCacheDict(TypedDict, total=False):
248
256
  """TypedDict for JSON-serialized ModelIntrospectionPerformanceMetrics.
@@ -455,6 +463,7 @@ class MixinNodeIntrospection:
455
463
  _introspection_event_bus: ProtocolEventBus | None
456
464
  _introspection_version: str
457
465
  _introspection_start_time: float | None
466
+ _introspection_contract: ModelContractBase | None
458
467
 
459
468
  # Capability discovery configuration
460
469
  _introspection_operation_keywords: frozenset[str]
@@ -647,6 +656,9 @@ class MixinNodeIntrospection:
647
656
  self._heartbeat_topic = config.heartbeat_topic
648
657
  self._request_introspection_topic = config.request_introspection_topic
649
658
 
659
+ # Contract for capability extraction (may be None for legacy nodes)
660
+ self._introspection_contract = config.contract
661
+
650
662
  # State
651
663
  self._introspection_cache = None
652
664
  self._introspection_cached_at = None
@@ -1335,6 +1347,14 @@ class MixinNodeIntrospection:
1335
1347
  # Fallback to 1.0.0 if version parsing fails
1336
1348
  node_version = ModelSemVer(major=1, minor=0, patch=0)
1337
1349
 
1350
+ # Extract contract capabilities if contract is available
1351
+ # This is automatic and non-skippable when contract is provided
1352
+ contract_capabilities = None
1353
+ if self._introspection_contract is not None:
1354
+ contract_capabilities = _CAPABILITY_EXTRACTOR.extract(
1355
+ self._introspection_contract
1356
+ )
1357
+
1338
1358
  # Create event with performance metrics (metrics is already Pydantic model)
1339
1359
  event = ModelNodeIntrospectionEvent(
1340
1360
  node_id=node_id_uuid,
@@ -1342,6 +1362,7 @@ class MixinNodeIntrospection:
1342
1362
  node_version=node_version,
1343
1363
  declared_capabilities=ModelNodeCapabilities(),
1344
1364
  discovered_capabilities=discovered_capabilities,
1365
+ contract_capabilities=contract_capabilities,
1345
1366
  endpoints=endpoints,
1346
1367
  current_state=current_state,
1347
1368
  reason=EnumIntrospectionReason.HEARTBEAT, # cache_refresh maps to heartbeat
@@ -1353,7 +1374,7 @@ class MixinNodeIntrospection:
1353
1374
  # Update cache - cast the model_dump output to our typed dict since we know
1354
1375
  # the structure matches (model_dump returns dict[str, Any] by default)
1355
1376
  self._introspection_cache = cast(
1356
- IntrospectionCacheDict, event.model_dump(mode="json")
1377
+ "IntrospectionCacheDict", event.model_dump(mode="json")
1357
1378
  )
1358
1379
  self._introspection_cached_at = current_time
1359
1380
 
@@ -1489,8 +1510,13 @@ class MixinNodeIntrospection:
1489
1510
  assert event_bus is not None # Redundant but helps mypy
1490
1511
  topic = self._introspection_topic
1491
1512
  if hasattr(event_bus, "publish_envelope"):
1513
+ # Wrap event in ModelEventEnvelope for protocol compliance
1514
+ envelope: ModelEventEnvelope[object] = ModelEventEnvelope(
1515
+ payload=publish_event,
1516
+ correlation_id=final_correlation_id,
1517
+ )
1492
1518
  await event_bus.publish_envelope(
1493
- envelope=publish_event,
1519
+ envelope=envelope, # type: ignore[arg-type]
1494
1520
  topic=topic,
1495
1521
  )
1496
1522
  else:
@@ -1604,8 +1630,13 @@ class MixinNodeIntrospection:
1604
1630
  assert event_bus is not None # Redundant but helps mypy
1605
1631
  topic = self._heartbeat_topic
1606
1632
  if hasattr(event_bus, "publish_envelope"):
1633
+ # Wrap event in ModelEventEnvelope for protocol compliance
1634
+ envelope: ModelEventEnvelope[object] = ModelEventEnvelope(
1635
+ payload=heartbeat,
1636
+ correlation_id=heartbeat.correlation_id,
1637
+ )
1607
1638
  await event_bus.publish_envelope(
1608
- envelope=heartbeat,
1639
+ envelope=envelope, # type: ignore[arg-type]
1609
1640
  topic=topic,
1610
1641
  )
1611
1642
  else:
@@ -1771,7 +1802,9 @@ class MixinNodeIntrospection:
1771
1802
  )
1772
1803
  self._registry_unsubscribe = None
1773
1804
 
1774
- async def _handle_introspection_request(self, message: ModelEventMessage) -> None:
1805
+ async def _handle_introspection_request(
1806
+ self, message: ProtocolEventMessage
1807
+ ) -> None:
1775
1808
  """Handle incoming introspection request.
1776
1809
 
1777
1810
  Includes error recovery with rate-limited logging to prevent
@@ -1779,7 +1812,7 @@ class MixinNodeIntrospection:
1779
1812
  non-fatal errors to maintain graceful degradation.
1780
1813
 
1781
1814
  Args:
1782
- message: The incoming event message
1815
+ message: The incoming event message (implements ProtocolEventMessage protocol)
1783
1816
  """
1784
1817
  try:
1785
1818
  await self._process_introspection_request(message)
@@ -1788,7 +1821,9 @@ class MixinNodeIntrospection:
1788
1821
  except Exception as e:
1789
1822
  self._handle_request_error(e)
1790
1823
 
1791
- async def _process_introspection_request(self, message: ModelEventMessage) -> None:
1824
+ async def _process_introspection_request(
1825
+ self, message: ProtocolEventMessage
1826
+ ) -> None:
1792
1827
  """Process the introspection request message.
1793
1828
 
1794
1829
  Args:
@@ -196,7 +196,7 @@ class MixinRetryExecution(ABC):
196
196
  This should only be called when _circuit_breaker_initialized is True,
197
197
  which guarantees the circuit breaker methods are available.
198
198
  """
199
- return cast(ProtocolCircuitBreakerAware, self)
199
+ return cast("ProtocolCircuitBreakerAware", self)
200
200
 
201
201
  async def _record_circuit_failure_if_enabled(
202
202
  self, operation: str, correlation_id: UUID
@@ -27,6 +27,7 @@ from uuid import UUID
27
27
  from pydantic import BaseModel, ConfigDict, Field, field_validator
28
28
 
29
29
  from omnibase_core.enums import EnumNodeKind
30
+ from omnibase_core.models.contracts import ModelContractBase
30
31
 
31
32
  if TYPE_CHECKING:
32
33
  from omnibase_core.protocols.event_bus.protocol_event_bus import ProtocolEventBus
@@ -86,6 +87,9 @@ class ModelIntrospectionConfig(BaseModel):
86
87
  request_introspection_topic: Topic for receiving introspection requests.
87
88
  Defaults to "node.request_introspection". ONEX topics (onex.*)
88
89
  require version suffix (.v1, .v2, etc.).
90
+ contract: Optional typed contract model for capability extraction.
91
+ When provided, MixinNodeIntrospection extracts contract_capabilities
92
+ using ContractCapabilityExtractor. None for legacy nodes.
89
93
 
90
94
  Example:
91
95
  ```python
@@ -185,6 +189,13 @@ class ModelIntrospectionConfig(BaseModel):
185
189
  "ONEX topics (onex.*) require version suffix (.v1, .v2, etc.).",
186
190
  )
187
191
 
192
+ contract: ModelContractBase | None = Field(
193
+ default=None,
194
+ description="Typed contract model for capability extraction. "
195
+ "When provided, MixinNodeIntrospection will extract contract_capabilities "
196
+ "using ContractCapabilityExtractor. None for legacy nodes without contracts.",
197
+ )
198
+
188
199
  @field_validator("node_type", mode="before")
189
200
  @classmethod
190
201
  def validate_node_type(cls, v: object) -> EnumNodeKind:
@@ -12,26 +12,69 @@ and error reporting in ONEX handlers.
12
12
  Added ModelHandlerDescriptor and ModelContractDiscoveryResult for
13
13
  OMN-1097 filesystem handler discovery.
14
14
 
15
+ .. versionchanged:: 0.6.4
16
+ Added ModelBootstrapHandlerDescriptor for OMN-1087 bootstrap handler
17
+ validation with required handler_class field.
18
+
19
+ .. versionchanged:: 0.7.0
20
+ Added ModelHandlerSourceConfig for OMN-1095 handler source mode
21
+ configuration with production hardening features.
22
+
15
23
  Note:
16
- ModelContractDiscoveryResult uses a forward reference to
17
- ModelHandlerValidationError to avoid circular imports. The forward
18
- reference is resolved via model_rebuild() in handler_contract_source.py
19
- after both classes are defined. This pattern is tested in
20
- tests/unit/runtime/test_handler_contract_source.py.
24
+ ModelContractDiscoveryResult uses a forward reference to ModelHandlerValidationError
25
+ to avoid circular imports between models.handlers and models.errors packages.
26
+ The forward reference is resolved via model_rebuild() calls in runtime modules
27
+ that import ModelHandlerValidationError (e.g., handler_contract_source.py,
28
+ handler_bootstrap_source.py, registry_contract_source.py). Each module calls
29
+ model_rebuild() after importing both the model and the forward-referenced type.
30
+ This pattern is required because:
31
+ 1. models.errors imports ModelHandlerIdentifier from models.handlers
32
+ 2. models.handlers cannot import from models.errors at module level (circular)
33
+ 3. model_rebuild() is idempotent, so multiple calls are harmless
21
34
  """
22
35
 
36
+ from omnibase_infra.models.handlers.model_bootstrap_handler_descriptor import (
37
+ ModelBootstrapHandlerDescriptor,
38
+ )
23
39
  from omnibase_infra.models.handlers.model_contract_discovery_result import (
24
40
  ModelContractDiscoveryResult,
25
41
  )
26
42
  from omnibase_infra.models.handlers.model_handler_descriptor import (
43
+ LiteralHandlerKind,
27
44
  ModelHandlerDescriptor,
28
45
  )
29
46
  from omnibase_infra.models.handlers.model_handler_identifier import (
30
47
  ModelHandlerIdentifier,
31
48
  )
49
+ from omnibase_infra.models.handlers.model_handler_source_config import (
50
+ ModelHandlerSourceConfig,
51
+ )
32
52
 
33
53
  __all__ = [
54
+ "LiteralHandlerKind",
55
+ "ModelBootstrapHandlerDescriptor",
34
56
  "ModelContractDiscoveryResult",
35
57
  "ModelHandlerDescriptor",
36
58
  "ModelHandlerIdentifier",
59
+ "ModelHandlerSourceConfig",
37
60
  ]
61
+
62
+ # =============================================================================
63
+ # Forward Reference Resolution
64
+ # =============================================================================
65
+ # ModelContractDiscoveryResult uses TYPE_CHECKING to defer import of
66
+ # ModelHandlerValidationError to avoid circular imports:
67
+ # - models.errors imports ModelHandlerIdentifier from models.handlers
68
+ # - models.handlers cannot import ModelHandlerValidationError at module level
69
+ #
70
+ # The forward reference is resolved via model_rebuild() in runtime modules that
71
+ # import ModelHandlerValidationError (e.g., handler_contract_source.py,
72
+ # handler_bootstrap_source.py, registry_contract_source.py, handler_source_resolver.py).
73
+ # Each module calls model_rebuild() at module level after importing both the model
74
+ # and the forward-referenced type. This is safe because model_rebuild() is idempotent.
75
+ #
76
+ # Why NOT here at module level:
77
+ # - Circular import: models.handlers.__init__ -> models.errors.__init__
78
+ # -> model_handler_validation_error.py -> models.handlers (for identifier)
79
+ # - Runtime modules load after model packages, avoiding this cycle
80
+ # =============================================================================
@@ -0,0 +1,162 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Bootstrap Handler Descriptor Model with Required handler_class.
4
+
5
+ This module provides ModelBootstrapHandlerDescriptor, a specialized handler
6
+ descriptor for bootstrap handlers that REQUIRES the handler_class field to be set.
7
+
8
+ Bootstrap handlers are hardcoded handlers that must always specify their
9
+ implementation class for dynamic import. Unlike contract-discovered handlers
10
+ where handler_class may be optional (inferred from convention), bootstrap
11
+ handlers have no contract file to derive the class from.
12
+
13
+ Part of OMN-1087: Implement HandlerBootstrapSource descriptor-based validation.
14
+
15
+ See Also:
16
+ - ModelHandlerDescriptor: Base descriptor with optional handler_class
17
+ - HandlerBootstrapSource: Source that uses this specialized descriptor
18
+ - BootstrapEffectDefinition: TypedDict for bootstrap handler definitions
19
+
20
+ .. versionadded:: 0.6.4
21
+ Created as part of OMN-1087 bootstrap handler validation.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from pydantic import ConfigDict, Field
27
+
28
+ from omnibase_infra.models.handlers.model_handler_descriptor import (
29
+ ModelHandlerDescriptor,
30
+ )
31
+
32
+
33
+ class ModelBootstrapHandlerDescriptor(ModelHandlerDescriptor):
34
+ """Handler descriptor for bootstrap handlers with required handler_class.
35
+
36
+ This specialized descriptor extends ModelHandlerDescriptor to enforce that
37
+ handler_class is always set. Bootstrap handlers are hardcoded and must
38
+ specify their implementation class since there is no contract file to
39
+ derive the class from.
40
+
41
+ The key difference from ModelHandlerDescriptor:
42
+ - handler_class: Required (str) instead of optional (str | None)
43
+
44
+ All other fields maintain the same constraints as the parent class.
45
+
46
+ Attributes:
47
+ handler_id: Unique identifier for the handler (e.g., "proto.consul").
48
+ name: Human-readable name for the handler.
49
+ version: Semantic version (ModelSemVer). Accepts string, dict, or ModelSemVer.
50
+ handler_kind: Handler kind (compute, effect, reducer, orchestrator).
51
+ input_model: Fully qualified input model class path.
52
+ output_model: Fully qualified output model class path.
53
+ description: Optional description of the handler.
54
+ handler_class: REQUIRED fully qualified Python class path for dynamic import.
55
+ contract_path: Path to the source contract file (typically None for bootstrap).
56
+
57
+ Example:
58
+ Create a bootstrap handler descriptor:
59
+
60
+ >>> descriptor = ModelBootstrapHandlerDescriptor(
61
+ ... handler_id="proto.consul",
62
+ ... name="Consul Handler",
63
+ ... version="1.0.0",
64
+ ... handler_kind="effect",
65
+ ... input_model="omnibase_infra.models.types.JsonDict",
66
+ ... output_model="omnibase_core.models.dispatch.ModelHandlerOutput",
67
+ ... handler_class="omnibase_infra.handlers.handler_consul.HandlerConsul",
68
+ ... )
69
+ >>> descriptor.handler_class
70
+ 'omnibase_infra.handlers.handler_consul.HandlerConsul'
71
+
72
+ Missing handler_class raises ValidationError:
73
+
74
+ >>> from pydantic import ValidationError
75
+ >>> try:
76
+ ... ModelBootstrapHandlerDescriptor(
77
+ ... handler_id="proto.consul",
78
+ ... name="Consul Handler",
79
+ ... version="1.0.0",
80
+ ... handler_kind="effect",
81
+ ... input_model="omnibase_infra.models.types.JsonDict",
82
+ ... output_model="omnibase_core.models.dispatch.ModelHandlerOutput",
83
+ ... # handler_class omitted - will fail
84
+ ... )
85
+ ... except ValidationError as e:
86
+ ... print("Validation failed as expected")
87
+ Validation failed as expected
88
+
89
+ Raises:
90
+ ValidationError: If handler_class is not provided or is None.
91
+
92
+ .. versionadded:: 0.6.4
93
+ Created as part of OMN-1087 bootstrap handler validation.
94
+ """
95
+
96
+ model_config = ConfigDict(
97
+ frozen=True,
98
+ extra="forbid",
99
+ strict=True,
100
+ )
101
+
102
+ # Override handler_class to be required (no default, not optional)
103
+ # The Field() definition must include the pattern constraint from parent
104
+ handler_class: str = Field(
105
+ ...,
106
+ min_length=3,
107
+ pattern=r"^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)+$",
108
+ description=(
109
+ "REQUIRED: Fully qualified Python class path for dynamic handler import. "
110
+ "Bootstrap handlers must always specify this field since they have no "
111
+ "contract file to derive the class from. "
112
+ "Example: 'omnibase_infra.handlers.handler_consul.HandlerConsul'"
113
+ ),
114
+ )
115
+
116
+ def to_base_descriptor(self) -> ModelHandlerDescriptor:
117
+ """Convert to base ModelHandlerDescriptor for API compatibility.
118
+
119
+ This method allows bootstrap descriptors to be used where the base
120
+ ModelHandlerDescriptor type is expected, while maintaining the
121
+ validation benefits of the bootstrap-specific model.
122
+
123
+ Implementation Notes:
124
+ Uses ``model_dump()`` without ``exclude_unset=True`` because:
125
+
126
+ 1. **Field parity**: This child class has NO extra fields beyond the
127
+ parent. The only difference is ``handler_class`` type constraint
128
+ (required ``str`` vs optional ``str | None``).
129
+
130
+ 2. **Type compatibility**: A ``str`` value from child is valid where
131
+ parent expects ``str | None``.
132
+
133
+ 3. **Complete copy**: ``model_dump()`` ensures all fields are copied,
134
+ including those set to their default values.
135
+
136
+ Using ``exclude_unset=True`` would risk excluding fields that have
137
+ defaults but were explicitly set to those defaults during construction.
138
+
139
+ If future versions add child-specific fields not in parent, this
140
+ method MUST be updated to use ``exclude={'new_field'}`` or refactored.
141
+
142
+ Returns:
143
+ ModelHandlerDescriptor instance with all fields copied.
144
+
145
+ Example:
146
+ >>> bootstrap_desc = ModelBootstrapHandlerDescriptor(
147
+ ... handler_id="proto.consul",
148
+ ... name="Consul Handler",
149
+ ... version="1.0.0",
150
+ ... handler_kind="effect",
151
+ ... input_model="omnibase_infra.models.types.JsonDict",
152
+ ... output_model="omnibase_core.models.dispatch.ModelHandlerOutput",
153
+ ... handler_class="omnibase_infra.handlers.handler_consul.HandlerConsul",
154
+ ... )
155
+ >>> base_desc = bootstrap_desc.to_base_descriptor()
156
+ >>> isinstance(base_desc, ModelHandlerDescriptor)
157
+ True
158
+ """
159
+ return ModelHandlerDescriptor(**self.model_dump())
160
+
161
+
162
+ __all__ = ["ModelBootstrapHandlerDescriptor"]
@@ -72,9 +72,11 @@ class ModelContractDiscoveryResult(BaseModel):
72
72
  )
73
73
 
74
74
 
75
- # Rebuild model to resolve forward reference after all imports are available.
76
- # model_rebuild() is called in omnibase_infra.runtime.handler_contract_source
77
- # after ModelHandlerValidationError is imported. See the comment block preceding
78
- # model_rebuild() in handler_contract_source.py (lines 52-71) for detailed explanation.
75
+ # Forward Reference Resolution:
76
+ # This model uses TYPE_CHECKING to defer import of ModelHandlerValidationError.
77
+ # model_rebuild() is called in runtime modules that import ModelHandlerValidationError
78
+ # (e.g., handler_contract_source.py, handler_bootstrap_source.py, registry_contract_source.py).
79
+ # Each module calls model_rebuild() at module level after importing both the model
80
+ # and the forward-referenced type. This is safe because model_rebuild() is idempotent.
79
81
 
80
82
  __all__ = ["ModelContractDiscoveryResult"]
@@ -99,6 +99,7 @@ class ModelHandlerDescriptor(BaseModel):
99
99
  input_model: Fully qualified input model class path.
100
100
  output_model: Fully qualified output model class path.
101
101
  description: Optional description of the handler.
102
+ handler_class: Fully qualified Python class path for dynamic handler import.
102
103
  contract_path: Path to the source contract file.
103
104
 
104
105
  Example:
@@ -176,10 +177,24 @@ class ModelHandlerDescriptor(BaseModel):
176
177
  default=None,
177
178
  description="Optional description of the handler",
178
179
  )
180
+ handler_class: str | None = Field(
181
+ default=None,
182
+ min_length=3,
183
+ pattern=r"^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)+$",
184
+ description="Fully qualified Python class path for dynamic handler import (e.g., 'omnibase_infra.handlers.handler_consul.HandlerConsul')",
185
+ )
179
186
  contract_path: str | None = Field(
180
187
  default=None,
181
188
  description="Path to the source contract file",
182
189
  )
190
+ contract_config: dict[str, JsonType] | None = Field(
191
+ default=None,
192
+ description=(
193
+ "Parsed configuration from the handler contract file. "
194
+ "Contains extracted values like security settings, tags, and handler metadata. "
195
+ "Populated during handler discovery when contract_path is set."
196
+ ),
197
+ )
183
198
 
184
199
 
185
200
  __all__ = ["LiteralHandlerKind", "ModelHandlerDescriptor"]