omnibase_infra 0.2.2__py3-none-any.whl → 0.2.4__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 (79) hide show
  1. omnibase_infra/__init__.py +1 -1
  2. omnibase_infra/adapters/adapter_onex_tool_execution.py +6 -1
  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/contracts/handlers/filesystem/handler_contract.yaml +1 -1
  8. omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +1 -1
  9. omnibase_infra/enums/__init__.py +6 -0
  10. omnibase_infra/enums/enum_handler_error_type.py +10 -0
  11. omnibase_infra/enums/enum_handler_source_mode.py +72 -0
  12. omnibase_infra/enums/enum_kafka_acks.py +99 -0
  13. omnibase_infra/event_bus/event_bus_kafka.py +1 -1
  14. omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +59 -10
  15. omnibase_infra/handlers/__init__.py +8 -1
  16. omnibase_infra/handlers/handler_consul.py +7 -1
  17. omnibase_infra/handlers/handler_db.py +8 -2
  18. omnibase_infra/handlers/handler_graph.py +860 -4
  19. omnibase_infra/handlers/handler_http.py +8 -2
  20. omnibase_infra/handlers/handler_intent.py +387 -0
  21. omnibase_infra/handlers/handler_mcp.py +10 -1
  22. omnibase_infra/handlers/handler_vault.py +11 -5
  23. omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +7 -0
  24. omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +7 -0
  25. omnibase_infra/mixins/mixin_node_introspection.py +18 -0
  26. omnibase_infra/models/discovery/model_introspection_config.py +11 -0
  27. omnibase_infra/models/handlers/__init__.py +38 -5
  28. omnibase_infra/models/handlers/model_bootstrap_handler_descriptor.py +4 -4
  29. omnibase_infra/models/handlers/model_contract_discovery_result.py +6 -4
  30. omnibase_infra/models/handlers/model_handler_source_config.py +220 -0
  31. omnibase_infra/models/registration/model_node_introspection_event.py +9 -0
  32. omnibase_infra/models/runtime/model_handler_contract.py +25 -9
  33. omnibase_infra/models/runtime/model_loaded_handler.py +9 -0
  34. omnibase_infra/nodes/node_registration_orchestrator/plugin.py +1 -1
  35. omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +7 -7
  36. omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +4 -3
  37. omnibase_infra/nodes/node_registration_storage_effect/node.py +4 -1
  38. omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +1 -1
  39. omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +4 -1
  40. omnibase_infra/protocols/__init__.py +2 -0
  41. omnibase_infra/protocols/protocol_container_aware.py +200 -0
  42. omnibase_infra/runtime/__init__.py +39 -0
  43. omnibase_infra/runtime/handler_bootstrap_source.py +26 -33
  44. omnibase_infra/runtime/handler_contract_config_loader.py +1 -1
  45. omnibase_infra/runtime/handler_contract_source.py +10 -51
  46. omnibase_infra/runtime/handler_identity.py +81 -0
  47. omnibase_infra/runtime/handler_plugin_loader.py +15 -0
  48. omnibase_infra/runtime/handler_registry.py +11 -3
  49. omnibase_infra/runtime/handler_source_resolver.py +326 -0
  50. omnibase_infra/runtime/protocol_lifecycle_executor.py +6 -6
  51. omnibase_infra/runtime/registry/registry_protocol_binding.py +13 -13
  52. omnibase_infra/runtime/registry_contract_source.py +693 -0
  53. omnibase_infra/runtime/service_kernel.py +1 -1
  54. omnibase_infra/runtime/service_runtime_host_process.py +463 -190
  55. omnibase_infra/runtime/util_wiring.py +12 -3
  56. omnibase_infra/services/__init__.py +21 -0
  57. omnibase_infra/services/corpus_capture.py +7 -1
  58. omnibase_infra/services/mcp/mcp_server_lifecycle.py +9 -3
  59. omnibase_infra/services/registry_api/main.py +31 -13
  60. omnibase_infra/services/registry_api/service.py +10 -19
  61. omnibase_infra/services/service_timeout_emitter.py +7 -1
  62. omnibase_infra/services/service_timeout_scanner.py +7 -3
  63. omnibase_infra/services/session/__init__.py +56 -0
  64. omnibase_infra/services/session/config_consumer.py +120 -0
  65. omnibase_infra/services/session/config_store.py +139 -0
  66. omnibase_infra/services/session/consumer.py +1007 -0
  67. omnibase_infra/services/session/protocol_session_aggregator.py +117 -0
  68. omnibase_infra/services/session/store.py +997 -0
  69. omnibase_infra/utils/__init__.py +19 -0
  70. omnibase_infra/utils/util_atomic_file.py +261 -0
  71. omnibase_infra/utils/util_db_transaction.py +239 -0
  72. omnibase_infra/utils/util_retry_optimistic.py +281 -0
  73. omnibase_infra/validation/__init__.py +16 -0
  74. omnibase_infra/validation/validation_exemptions.yaml +27 -0
  75. {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.4.dist-info}/METADATA +3 -3
  76. {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.4.dist-info}/RECORD +79 -58
  77. {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.4.dist-info}/WHEEL +0 -0
  78. {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.4.dist-info}/entry_points.txt +0 -0
  79. {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,200 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Protocol extension for Infrastructure Handlers with Container DI.
4
+
5
+ This module defines the ProtocolContainerAware interface, which extends the base
6
+ ProtocolHandler from omnibase_spi to add container-based dependency injection
7
+ requirements. All infrastructure handlers in omnibase_infra must implement this
8
+ extended protocol.
9
+
10
+ Why This Protocol Exists:
11
+ The base ProtocolHandler in omnibase_spi is intentionally minimal and doesn't
12
+ mandate a specific constructor signature. This keeps the SPI layer decoupled
13
+ from implementation details.
14
+
15
+ However, omnibase_infra handlers require ModelONEXContainer for:
16
+ - Dependency injection of shared services (connection pools, clients)
17
+ - Configuration access without global state
18
+ - Testability through container mocking
19
+
20
+ This protocol adds the __init__ signature requirement while inheriting all
21
+ method requirements from the base protocol.
22
+
23
+ Protocol Hierarchy:
24
+ omnibase_spi.ProtocolHandler (base)
25
+ - handler_type property
26
+ - initialize()
27
+ - shutdown()
28
+ - execute()
29
+ - describe()
30
+ - health_check()
31
+
32
+ omnibase_infra.ProtocolContainerAware (extension)
33
+ - All methods from ProtocolHandler
34
+ - __init__(container: ModelONEXContainer) requirement
35
+
36
+ Usage:
37
+ Infrastructure handlers should implement this extended protocol:
38
+
39
+ ```python
40
+ from omnibase_core.container import ModelONEXContainer
41
+ from omnibase_infra.protocols import ProtocolContainerAware
42
+
43
+ class HandlerDatabase:
44
+ '''Database handler implementing the infra protocol.'''
45
+
46
+ def __init__(self, container: ModelONEXContainer) -> None:
47
+ self._container = container
48
+ # Access shared resources via container
49
+
50
+ @property
51
+ def handler_type(self) -> str:
52
+ return "db"
53
+
54
+ # ... implement remaining protocol methods ...
55
+ ```
56
+
57
+ The RuntimeHostProcess uses this protocol for type-safe handler instantiation:
58
+
59
+ ```python
60
+ handler_cls: type[ProtocolContainerAware] = handler_registry.get(handler_type)
61
+ handler_instance = handler_cls(container=container) # Type-safe
62
+ ```
63
+
64
+ Thread Safety:
65
+ Handler implementations must be thread-safe. The container provides access
66
+ to shared resources that may be used concurrently.
67
+
68
+ See Also:
69
+ - omnibase_spi.protocols.handlers.protocol_handler.ProtocolHandler
70
+ - omnibase_core.container.ModelONEXContainer
71
+ - CLAUDE.md section "Container-Based Dependency Injection"
72
+ - PR #186: Container DI refactoring for handlers
73
+
74
+ .. versionadded:: 0.7.1
75
+ Created as part of OMN-1434 container DI standardization.
76
+ """
77
+
78
+ from __future__ import annotations
79
+
80
+ from typing import TYPE_CHECKING, Protocol, runtime_checkable
81
+
82
+ from omnibase_spi.protocols.handlers.protocol_handler import (
83
+ ProtocolHandler as ProtocolHandlerBase,
84
+ )
85
+
86
+ if TYPE_CHECKING:
87
+ from omnibase_core.container import ModelONEXContainer
88
+
89
+ __all__ = [
90
+ "ProtocolContainerAware",
91
+ ]
92
+
93
+
94
+ @runtime_checkable
95
+ class ProtocolContainerAware(ProtocolHandlerBase, Protocol):
96
+ """Extended protocol for infrastructure handlers with container DI.
97
+
98
+ This protocol extends the base ProtocolHandler from omnibase_spi to require
99
+ a constructor that accepts ModelONEXContainer for dependency injection.
100
+
101
+ All infrastructure handlers in omnibase_infra must implement this protocol.
102
+ The container provides access to:
103
+ - Database connection pools
104
+ - HTTP clients
105
+ - Service discovery clients
106
+ - Configuration values
107
+ - Logging context
108
+
109
+ Methods inherited from ProtocolHandler:
110
+ handler_type: Property returning the handler type identifier.
111
+ initialize: Initialize clients and connection pools.
112
+ shutdown: Release resources and close connections.
113
+ execute: Execute protocol-specific operations.
114
+ describe: Return handler metadata and capabilities.
115
+ health_check: Check handler health and connectivity.
116
+
117
+ Constructor Requirement (added by this protocol):
118
+ __init__: Must accept container: ModelONEXContainer as first positional argument.
119
+
120
+ Example:
121
+ ```python
122
+ from omnibase_core.container import ModelONEXContainer
123
+
124
+ class HandlerConsul:
125
+ def __init__(self, container: ModelONEXContainer) -> None:
126
+ self._container = container
127
+ self._consul_url = container.config.get("consul_url")
128
+
129
+ @property
130
+ def handler_type(self) -> str:
131
+ return "consul"
132
+
133
+ async def initialize(self, config):
134
+ # Use container for shared resources
135
+ ...
136
+
137
+ # ... implement remaining methods ...
138
+ ```
139
+
140
+ Protocol Verification:
141
+ Per ONEX conventions, verify protocol compliance via duck typing:
142
+
143
+ ```python
144
+ handler_cls = handler_registry.get("http")
145
+
146
+ # Verify constructor signature accepts container
147
+ import inspect
148
+ sig = inspect.signature(handler_cls.__init__)
149
+ params = list(sig.parameters.keys())
150
+ assert "container" in params or len(params) > 1 # self + container
151
+
152
+ # Verify protocol methods exist
153
+ assert hasattr(handler_cls, "handler_type")
154
+ assert hasattr(handler_cls, "initialize") and callable(getattr(handler_cls, "initialize"))
155
+ ```
156
+
157
+ Note:
158
+ Method bodies in this Protocol use ``...`` (Ellipsis) rather than
159
+ ``raise NotImplementedError()``. This is the standard Python convention
160
+ for ``typing.Protocol`` classes per PEP 544.
161
+ """
162
+
163
+ def __init__(self, container: ModelONEXContainer) -> None:
164
+ """Initialize the handler with a dependency injection container.
165
+
166
+ All infrastructure handlers receive their dependencies through the
167
+ container rather than as individual constructor arguments. This enables:
168
+
169
+ - Consistent initialization across all handlers
170
+ - Easy testing through container mocking
171
+ - Runtime configuration without code changes
172
+ - Shared resource management (connection pools, clients)
173
+
174
+ Args:
175
+ container: ONEX dependency injection container providing access to:
176
+ - Configuration values (container.config)
177
+ - Shared services (database pools, HTTP clients)
178
+ - Logging context
179
+ - Runtime metadata
180
+
181
+ Note:
182
+ Handlers should store the container reference and access dependencies
183
+ lazily when needed, rather than extracting all values in __init__.
184
+ This improves startup time and allows for late-bound configuration.
185
+
186
+ Example:
187
+ ```python
188
+ def __init__(self, container: ModelONEXContainer) -> None:
189
+ self._container = container
190
+ self._client: httpx.AsyncClient | None = None # Lazy init
191
+
192
+ async def initialize(self, config):
193
+ # Create client during initialize(), not __init__()
194
+ self._client = httpx.AsyncClient(
195
+ base_url=self._container.config.get("base_url"),
196
+ timeout=config.get("timeout", 30.0),
197
+ )
198
+ ```
199
+ """
200
+ ...
@@ -160,6 +160,15 @@ from omnibase_infra.runtime.handler_bootstrap_source import (
160
160
  SOURCE_TYPE_BOOTSTRAP,
161
161
  )
162
162
 
163
+ # Handler identity helper (OMN-1095)
164
+ from omnibase_infra.runtime.handler_identity import (
165
+ HANDLER_IDENTITY_PREFIX,
166
+ handler_identity,
167
+ )
168
+
169
+ # Handler source resolver (OMN-1095)
170
+ from omnibase_infra.runtime.handler_source_resolver import HandlerSourceResolver
171
+
163
172
  # Handler contract config loader
164
173
  from omnibase_infra.runtime.handler_contract_config_loader import (
165
174
  MAX_CONTRACT_SIZE_BYTES,
@@ -212,6 +221,20 @@ from omnibase_infra.runtime.transition_notification_outbox import (
212
221
  TransitionNotificationOutbox,
213
222
  )
214
223
 
224
+ # Registry contract source (OMN-1100)
225
+ from omnibase_infra.runtime.registry_contract_source import (
226
+ DEFAULT_CONSUL_HOST,
227
+ DEFAULT_CONSUL_PORT,
228
+ DEFAULT_CONTRACT_PREFIX,
229
+ RegistryContractSource,
230
+ adelete_contract_from_consul,
231
+ alist_contracts_in_consul,
232
+ astore_contract_in_consul,
233
+ delete_contract_from_consul,
234
+ list_contracts_in_consul,
235
+ store_contract_in_consul,
236
+ )
237
+
215
238
  # Chain-aware dispatch (OMN-951) - must be imported LAST to avoid circular import
216
239
  from omnibase_infra.runtime.chain_aware_dispatch import (
217
240
  ChainAwareDispatcher,
@@ -320,6 +343,11 @@ __all__: list[str] = [
320
343
  # Handler bootstrap source (OMN-1087)
321
344
  "HandlerBootstrapSource",
322
345
  "SOURCE_TYPE_BOOTSTRAP",
346
+ # Handler identity helper (OMN-1095)
347
+ "HANDLER_IDENTITY_PREFIX",
348
+ "handler_identity",
349
+ # Handler source resolver (OMN-1095)
350
+ "HandlerSourceResolver",
323
351
  # Handler contract config loader
324
352
  "MAX_CONTRACT_SIZE_BYTES",
325
353
  "extract_handler_config",
@@ -343,4 +371,15 @@ __all__: list[str] = [
343
371
  # Transition notification publisher and outbox (OMN-1139)
344
372
  "TransitionNotificationOutbox",
345
373
  "TransitionNotificationPublisher",
374
+ # Registry contract source (OMN-1100)
375
+ "DEFAULT_CONSUL_HOST",
376
+ "DEFAULT_CONSUL_PORT",
377
+ "DEFAULT_CONTRACT_PREFIX",
378
+ "RegistryContractSource",
379
+ "adelete_contract_from_consul",
380
+ "alist_contracts_in_consul",
381
+ "astore_contract_in_consul",
382
+ "delete_contract_from_consul",
383
+ "list_contracts_in_consul",
384
+ "store_contract_in_consul",
346
385
  ]
@@ -79,6 +79,7 @@ from omnibase_infra.runtime.handler_contract_config_loader import (
79
79
  extract_handler_config,
80
80
  load_handler_contract_config,
81
81
  )
82
+ from omnibase_infra.runtime.handler_identity import handler_identity
82
83
  from omnibase_infra.runtime.protocol_contract_source import ProtocolContractSource
83
84
 
84
85
 
@@ -95,7 +96,7 @@ class BootstrapEffectDefinition(TypedDict):
95
96
  must always specify their implementation class.
96
97
 
97
98
  Attributes:
98
- handler_id: Unique identifier with "bootstrap." prefix.
99
+ handler_id: Unique identifier with "proto." prefix (protocol identity namespace).
99
100
  name: Human-readable display name.
100
101
  description: Handler purpose description.
101
102
  handler_kind: ONEX handler archetype (all are "effect" for I/O handlers).
@@ -116,36 +117,27 @@ class BootstrapEffectDefinition(TypedDict):
116
117
 
117
118
 
118
119
  # =============================================================================
119
- # Thread-Safe Model Rebuild Pattern (Deferred Execution)
120
+ # Thread-Safe Model Rebuild Pattern (Safety Net)
120
121
  # =============================================================================
121
122
  #
122
- # This module uses a DEFERRED model_rebuild() pattern with thread-safe
123
- # double-checked locking. This differs from HandlerContractSource which uses
124
- # a simpler module-level model_rebuild() call.
123
+ # ModelContractDiscoveryResult.model_rebuild() is now called CENTRALLY in
124
+ # omnibase_infra.models.handlers.__init__ to resolve forward references.
125
+ # This ensures the forward reference to ModelHandlerValidationError is resolved
126
+ # as soon as the handlers package is imported.
125
127
  #
126
- # WHY DEFERRED (runtime) vs IMMEDIATE (module-load):
127
- # - HandlerBootstrapSource is imported through runtime.__init__.py during
128
- # application bootstrap, BEFORE all model dependencies are fully resolved
129
- # - If we called model_rebuild() at module load time (like HandlerContractSource),
130
- # it would fail with circular import errors because ModelHandlerValidationError
131
- # may not be fully defined yet in the import chain
132
- # - HandlerContractSource can use immediate module-level model_rebuild() because
133
- # by the time that module is imported (via explicit user code, not runtime init),
134
- # all dependencies are already resolved
128
+ # This module retains a DEFERRED, thread-safe model_rebuild() call as a SAFETY NET:
129
+ # - model_rebuild() is idempotent - multiple calls are harmless
130
+ # - The flag-guarded pattern ensures at most one rebuild per process
131
+ # - This provides fallback protection if import order changes in the future
135
132
  #
136
- # WHY THREAD-SAFE:
133
+ # WHY THREAD-SAFE (historical context):
137
134
  # - discover_handlers() may be called concurrently from multiple threads
138
135
  # - Unlike module-level code (which Python imports once, thread-safely),
139
136
  # runtime-invoked code needs explicit synchronization
140
- # - The double-checked locking pattern minimizes lock contention: after the
141
- # first successful rebuild, subsequent calls hit only the fast path check
142
- #
143
- # PATTERN COMPARISON:
144
- # - HandlerBootstrapSource: Deferred + thread-safe (this file)
145
- # - HandlerContractSource: Immediate + module-level (see that file for rationale)
137
+ # - The double-checked locking pattern minimizes lock contention
146
138
  #
147
139
  # See Also:
148
- # - handler_contract_source.py lines 49-68 for the immediate pattern rationale
140
+ # - omnibase_infra.models.handlers.__init__: Central model_rebuild() location
149
141
  # - OMN-1087 for the ticket tracking this design decision
150
142
  # =============================================================================
151
143
 
@@ -229,7 +221,7 @@ _HANDLER_TYPE_VAULT = "vault"
229
221
  # Bootstrap handler definitions.
230
222
  #
231
223
  # Each entry contains the metadata needed to create a ModelHandlerDescriptor:
232
- # handler_id: Unique identifier with "bootstrap." prefix
224
+ # handler_id: Unique identifier with "proto." prefix (protocol identity namespace)
233
225
  # name: Human-readable display name
234
226
  # description: Handler purpose description
235
227
  # handler_kind: ONEX handler archetype (all are "effect" for I/O handlers)
@@ -252,7 +244,7 @@ _HANDLER_TYPE_VAULT = "vault"
252
244
  # providing compile-time type safety for the hardcoded values.
253
245
  _BOOTSTRAP_HANDLER_DEFINITIONS: list[BootstrapEffectDefinition] = [
254
246
  {
255
- "handler_id": f"bootstrap.{_HANDLER_TYPE_CONSUL}",
247
+ "handler_id": handler_identity(_HANDLER_TYPE_CONSUL),
256
248
  "name": "Consul Handler",
257
249
  "description": "HashiCorp Consul service discovery handler",
258
250
  "handler_kind": "effect",
@@ -262,7 +254,7 @@ _BOOTSTRAP_HANDLER_DEFINITIONS: list[BootstrapEffectDefinition] = [
262
254
  "contract_path": "contracts/handlers/consul/handler_contract.yaml",
263
255
  },
264
256
  {
265
- "handler_id": f"bootstrap.{_HANDLER_TYPE_DATABASE}",
257
+ "handler_id": handler_identity(_HANDLER_TYPE_DATABASE),
266
258
  "name": "Database Handler",
267
259
  "description": "PostgreSQL database handler",
268
260
  "handler_kind": "effect",
@@ -272,7 +264,7 @@ _BOOTSTRAP_HANDLER_DEFINITIONS: list[BootstrapEffectDefinition] = [
272
264
  "contract_path": "contracts/handlers/db/handler_contract.yaml",
273
265
  },
274
266
  {
275
- "handler_id": f"bootstrap.{_HANDLER_TYPE_HTTP}",
267
+ "handler_id": handler_identity(_HANDLER_TYPE_HTTP),
276
268
  "name": "HTTP Handler",
277
269
  "description": "HTTP REST protocol handler",
278
270
  "handler_kind": "effect",
@@ -282,7 +274,7 @@ _BOOTSTRAP_HANDLER_DEFINITIONS: list[BootstrapEffectDefinition] = [
282
274
  "contract_path": "contracts/handlers/http/handler_contract.yaml",
283
275
  },
284
276
  {
285
- "handler_id": f"bootstrap.{_HANDLER_TYPE_VAULT}",
277
+ "handler_id": handler_identity(_HANDLER_TYPE_VAULT),
286
278
  "name": "Vault Handler",
287
279
  "description": "HashiCorp Vault secret management handler",
288
280
  "handler_kind": "effect",
@@ -292,7 +284,7 @@ _BOOTSTRAP_HANDLER_DEFINITIONS: list[BootstrapEffectDefinition] = [
292
284
  "contract_path": "contracts/handlers/vault/handler_contract.yaml",
293
285
  },
294
286
  {
295
- "handler_id": f"bootstrap.{_HANDLER_TYPE_MCP}",
287
+ "handler_id": handler_identity(_HANDLER_TYPE_MCP),
296
288
  "name": "MCP Handler",
297
289
  "description": "Model Context Protocol handler for AI agent integration",
298
290
  "handler_kind": "effect",
@@ -331,13 +323,14 @@ class HandlerBootstrapSource(
331
323
  >>> source = HandlerBootstrapSource()
332
324
  >>> result = await source.discover_handlers()
333
325
  >>> print(f"Found {len(result.descriptors)} bootstrap handlers")
334
- Found 4 bootstrap handlers
326
+ Found 5 bootstrap handlers
335
327
  >>> for desc in result.descriptors:
336
328
  ... print(f" - {desc.handler_id}: {desc.description}")
337
- - bootstrap.consul: HashiCorp Consul service discovery handler
338
- - bootstrap.db: PostgreSQL database handler
339
- - bootstrap.http: HTTP REST protocol handler
340
- - bootstrap.vault: HashiCorp Vault secret management handler
329
+ - proto.consul: HashiCorp Consul service discovery handler
330
+ - proto.db: PostgreSQL database handler
331
+ - proto.http: HTTP REST protocol handler
332
+ - proto.mcp: Model Context Protocol handler for AI agent integration
333
+ - proto.vault: HashiCorp Vault secret management handler
341
334
 
342
335
  Performance Characteristics:
343
336
  - No filesystem or network I/O required
@@ -95,7 +95,7 @@ def load_handler_contract_config(
95
95
  Example:
96
96
  >>> contract = load_handler_contract_config(
97
97
  ... "contracts/handlers/consul/handler_contract.yaml",
98
- ... "bootstrap.consul",
98
+ ... "proto.consul",
99
99
  ... )
100
100
  >>> contract["name"]
101
101
  'handler-consul'
@@ -31,6 +31,7 @@ from __future__ import annotations
31
31
  import logging
32
32
  import time
33
33
  from pathlib import Path
34
+ from typing import cast
34
35
 
35
36
  import yaml
36
37
  from pydantic import ValidationError
@@ -41,62 +42,18 @@ from omnibase_core.models.primitives import ModelSemVer
41
42
  from omnibase_infra.enums import EnumHandlerErrorType, EnumHandlerSourceType
42
43
  from omnibase_infra.models.errors import ModelHandlerValidationError
43
44
  from omnibase_infra.models.handlers import (
45
+ LiteralHandlerKind,
44
46
  ModelContractDiscoveryResult,
45
47
  ModelHandlerDescriptor,
46
48
  ModelHandlerIdentifier,
47
49
  )
48
50
  from omnibase_infra.runtime.protocol_contract_source import ProtocolContractSource
49
51
 
50
- # =============================================================================
51
- # Module-Level Model Rebuild Pattern (Immediate Execution)
52
- # =============================================================================
53
- #
54
- # This module uses a simple MODULE-LEVEL model_rebuild() call. This differs from
55
- # HandlerBootstrapSource which uses a deferred, thread-safe pattern.
56
- #
57
- # WHY IMMEDIATE (module-load) IS SAFE HERE:
58
- # - HandlerContractSource is NOT imported through runtime.__init__.py
59
- # - This module is imported explicitly by user code AFTER the runtime is
60
- # initialized and all model dependencies are resolved
61
- # - Python's import mechanism is inherently thread-safe for module-level code:
62
- # the import lock ensures module initialization runs exactly once, even if
63
- # multiple threads import the same module simultaneously
64
- # - Therefore, no explicit thread-safety mechanism (locks) is needed
65
- #
66
- # WHY HANDLERBOOTSTRAPSOURCE NEEDS DEFERRED PATTERN:
67
- # - HandlerBootstrapSource is imported during runtime bootstrap (via __init__.py)
68
- # - At that point, ModelHandlerValidationError may not be fully resolved
69
- # - Additionally, discover_handlers() may be called from multiple threads,
70
- # requiring explicit synchronization for the rebuild call
71
- #
72
- # PATTERN COMPARISON:
73
- # - HandlerContractSource: Immediate + module-level (this file)
74
- # - HandlerBootstrapSource: Deferred + thread-safe (see that file for rationale)
75
- #
76
- # See Also:
77
- # - handler_bootstrap_source.py lines 68-100 for the deferred pattern rationale
78
- # - OMN-1087 for the ticket tracking this design decision
79
- # =============================================================================
80
- #
81
- # Rebuild ModelContractDiscoveryResult to resolve the forward reference
82
- # to ModelHandlerValidationError. This must happen after ModelHandlerValidationError
83
- # is imported to make the type available for Pydantic validation.
84
- #
85
- # Why forward reference resolution is needed:
86
- # ModelContractDiscoveryResult has a field typed as list[ModelHandlerValidationError].
87
- # ModelHandlerValidationError imports ModelHandlerIdentifier from models.handlers.
88
- # If ModelContractDiscoveryResult directly imported ModelHandlerValidationError,
89
- # it would cause a circular import because models.handlers.__init__.py imports
90
- # ModelContractDiscoveryResult.
91
- #
92
- # The solution:
93
- # 1. ModelContractDiscoveryResult uses TYPE_CHECKING to defer the import
94
- # 2. With PEP 563 (from __future__ import annotations), the annotation becomes
95
- # a string at runtime, avoiding the circular import
96
- # 3. model_rebuild() resolves the string annotation to the actual type after
97
- # both classes are defined
98
- #
99
- # This is tested in: tests/unit/runtime/test_handler_contract_source.py
52
+ # Forward Reference Resolution:
53
+ # ModelContractDiscoveryResult uses a forward reference to ModelHandlerValidationError.
54
+ # Since we import ModelHandlerValidationError above, we can call model_rebuild() here
55
+ # to resolve the forward reference. This call is idempotent - multiple calls are harmless.
56
+ # This ensures the model is fully defined before we create instances in discover_handlers().
100
57
  ModelContractDiscoveryResult.model_rebuild()
101
58
 
102
59
  logger = logging.getLogger(__name__)
@@ -735,7 +692,9 @@ class HandlerContractSource(ProtocolContractSource):
735
692
  handler_id=contract.handler_id,
736
693
  name=contract.name,
737
694
  version=contract.contract_version,
738
- handler_kind=contract.descriptor.handler_kind,
695
+ handler_kind=cast(
696
+ "LiteralHandlerKind", contract.descriptor.node_archetype.value
697
+ ),
739
698
  input_model=contract.input_model,
740
699
  output_model=contract.output_model,
741
700
  description=contract.description,
@@ -0,0 +1,81 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Handler Identity Utilities for HYBRID Mode Resolution.
4
+
5
+ This module provides the `handler_identity()` function used by both bootstrap
6
+ and contract sources to generate consistent handler IDs. This enables per-handler
7
+ identity matching in HYBRID mode.
8
+
9
+ The Problem:
10
+ Prior to this module, contract-discovered handlers used a "bootstrap." prefix
11
+ for handler_id to enable HYBRID mode identity matching. This was semantically
12
+ confusing because "bootstrap" reads like "where it came from," not "what it is."
13
+
14
+ The Solution:
15
+ A neutral "proto." prefix that indicates this is a **protocol identity namespace**,
16
+ not a source indicator. Both HandlerBootstrapSource and PluginLoaderContractSource
17
+ use this shared helper to generate consistent IDs.
18
+
19
+ Example:
20
+ >>> from omnibase_infra.runtime.handler_identity import handler_identity
21
+ >>> handler_identity("consul")
22
+ 'proto.consul'
23
+ >>> handler_identity("http")
24
+ 'proto.http'
25
+
26
+ See Also:
27
+ - HandlerSourceResolver._resolve_hybrid(): Resolution logic that compares handler_id
28
+ - HandlerBootstrapSource: Uses this to generate bootstrap handler IDs
29
+ - PluginLoaderContractSource: Uses this for contract-discovered handlers
30
+
31
+ Part of OMN-1095: Handler Source Mode Feature Flag / Bootstrap Contract Hybrid.
32
+
33
+ .. versionadded:: 0.7.0
34
+ Introduced to fix handler ID namespace confusion.
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ # Prefix used for handler identity in HYBRID mode resolution.
40
+ # This is a protocol namespace, NOT a source indicator.
41
+ # Both bootstrap and contract sources use this prefix.
42
+ HANDLER_IDENTITY_PREFIX = "proto"
43
+
44
+
45
+ def handler_identity(protocol_type: str) -> str:
46
+ """Generate stable handler identity for HYBRID mode resolution.
47
+
48
+ Both bootstrap and contract sources use this to generate consistent IDs,
49
+ enabling per-handler identity matching in HYBRID mode. When both sources
50
+ provide a handler with the same identity, the resolver applies precedence
51
+ rules (contract wins by default, or bootstrap wins if allow_bootstrap_override=True).
52
+
53
+ The "proto." prefix indicates this is a **protocol identity namespace**, not
54
+ a source origin indicator. Contract-discovered handlers use this prefix
55
+ specifically so they can be compared against bootstrap-discovered handlers
56
+ with the same protocol_type.
57
+
58
+ Args:
59
+ protocol_type: The protocol type (e.g., "consul", "http", "db", "vault", "mcp").
60
+
61
+ Returns:
62
+ Stable handler identity string (e.g., "proto.consul", "proto.http").
63
+
64
+ Example:
65
+ >>> handler_identity("consul")
66
+ 'proto.consul'
67
+ >>> handler_identity("http")
68
+ 'proto.http'
69
+
70
+ See Also:
71
+ HandlerSourceResolver._resolve_hybrid() for resolution logic that uses
72
+ these identities to determine which handler wins when both sources
73
+ provide handlers with the same identity.
74
+ """
75
+ return f"{HANDLER_IDENTITY_PREFIX}.{protocol_type}"
76
+
77
+
78
+ __all__ = [
79
+ "HANDLER_IDENTITY_PREFIX",
80
+ "handler_identity",
81
+ ]
@@ -659,6 +659,20 @@ class HandlerPluginLoader(ProtocolHandlerPluginLoader):
659
659
  },
660
660
  )
661
661
 
662
+ # contract.handler_version is guaranteed non-None by model_validator
663
+ if contract.handler_version is None:
664
+ context = ModelInfraErrorContext.with_correlation(
665
+ correlation_id=correlation_id,
666
+ transport_type=EnumInfraTransportType.RUNTIME,
667
+ operation="load_from_contract",
668
+ )
669
+ raise ProtocolConfigurationError(
670
+ "handler_version should be set by model_validator",
671
+ context=context,
672
+ loader_error=EnumHandlerLoaderError.MISSING_REQUIRED_FIELDS.value,
673
+ contract_path=str(contract_path),
674
+ )
675
+
662
676
  return ModelLoadedHandler(
663
677
  handler_name=handler_name,
664
678
  protocol_type=protocol_type,
@@ -667,6 +681,7 @@ class HandlerPluginLoader(ProtocolHandlerPluginLoader):
667
681
  contract_path=resolved_contract_path,
668
682
  capability_tags=capability_tags,
669
683
  loaded_at=datetime.now(UTC),
684
+ handler_version=contract.handler_version,
670
685
  )
671
686
 
672
687
  def load_from_directory(
@@ -74,7 +74,7 @@ from omnibase_infra.runtime.registry.registry_protocol_binding import (
74
74
 
75
75
  if TYPE_CHECKING:
76
76
  from omnibase_core.protocol.protocol_event_bus import ProtocolEventBus
77
- from omnibase_spi.protocols.handlers.protocol_handler import ProtocolHandler
77
+ from omnibase_infra.protocols import ProtocolContainerAware
78
78
 
79
79
  # =============================================================================
80
80
  # Handler Type Constants
@@ -115,6 +115,12 @@ HANDLER_TYPE_MCP: str = "mcp"
115
115
  The MCP handler exposes ONEX nodes as tools for AI agents via streamable HTTP.
116
116
  Supports tools/list and tools/call operations per the MCP specification."""
117
117
 
118
+ HANDLER_TYPE_GRAPH: str = "graph"
119
+ """Graph database (Memgraph/Neo4j) protocol handler type."""
120
+
121
+ HANDLER_TYPE_INTENT: str = "intent" # DEMO (OMN-1515)
122
+ """Intent storage and query handler type for demo wiring."""
123
+
118
124
 
119
125
  # =============================================================================
120
126
  # Event Bus Kind Constants
@@ -192,7 +198,7 @@ def get_event_bus_registry() -> RegistryEventBusBinding:
192
198
  # =============================================================================
193
199
 
194
200
 
195
- def get_handler_class(handler_type: str) -> type[ProtocolHandler]:
201
+ def get_handler_class(handler_type: str) -> type[ProtocolContainerAware]:
196
202
  """Get handler class for the given type from the singleton registry.
197
203
 
198
204
  Convenience function that wraps get_handler_registry().get().
@@ -275,7 +281,7 @@ def register_handlers_from_config(
275
281
 
276
282
  TODO(OMN-41): Implement full handler resolution:
277
283
  1. Use importlib to resolve protocol_class string to actual class
278
- 2. Validate class implements ProtocolHandler protocol
284
+ 2. Validate class implements ProtocolContainerAware protocol
279
285
  3. Register handler with runtime via get_handler_registry()
280
286
  4. Support handler instantiation options from config.options
281
287
  """
@@ -300,8 +306,10 @@ __all__: list[str] = [
300
306
  "HANDLER_TYPE_CONSUL",
301
307
  "HANDLER_TYPE_DATABASE",
302
308
  "HANDLER_TYPE_GRPC",
309
+ "HANDLER_TYPE_GRAPH",
303
310
  # Handler type constants
304
311
  "HANDLER_TYPE_HTTP",
312
+ "HANDLER_TYPE_INTENT",
305
313
  "HANDLER_TYPE_KAFKA",
306
314
  "HANDLER_TYPE_MCP",
307
315
  "HANDLER_TYPE_VALKEY",