omnibase_infra 0.2.2__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.
- omnibase_infra/__init__.py +1 -1
- omnibase_infra/adapters/adapter_onex_tool_execution.py +6 -1
- omnibase_infra/capabilities/__init__.py +15 -0
- omnibase_infra/capabilities/capability_inference_rules.py +211 -0
- omnibase_infra/capabilities/contract_capability_extractor.py +221 -0
- omnibase_infra/capabilities/intent_type_extractor.py +160 -0
- omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +1 -1
- omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +1 -1
- omnibase_infra/enums/__init__.py +6 -0
- omnibase_infra/enums/enum_handler_error_type.py +10 -0
- omnibase_infra/enums/enum_handler_source_mode.py +72 -0
- omnibase_infra/enums/enum_kafka_acks.py +99 -0
- omnibase_infra/event_bus/event_bus_kafka.py +1 -1
- omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +59 -10
- omnibase_infra/handlers/__init__.py +8 -1
- omnibase_infra/handlers/handler_consul.py +7 -1
- omnibase_infra/handlers/handler_db.py +8 -2
- omnibase_infra/handlers/handler_http.py +8 -2
- omnibase_infra/handlers/handler_intent.py +387 -0
- omnibase_infra/handlers/handler_mcp.py +10 -1
- omnibase_infra/handlers/handler_vault.py +11 -5
- omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +7 -0
- omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +7 -0
- omnibase_infra/mixins/mixin_node_introspection.py +18 -0
- omnibase_infra/models/discovery/model_introspection_config.py +11 -0
- omnibase_infra/models/handlers/__init__.py +38 -5
- omnibase_infra/models/handlers/model_bootstrap_handler_descriptor.py +4 -4
- omnibase_infra/models/handlers/model_contract_discovery_result.py +6 -4
- omnibase_infra/models/handlers/model_handler_source_config.py +220 -0
- omnibase_infra/models/registration/model_node_introspection_event.py +9 -0
- omnibase_infra/models/runtime/model_handler_contract.py +25 -9
- omnibase_infra/models/runtime/model_loaded_handler.py +9 -0
- omnibase_infra/nodes/node_registration_orchestrator/plugin.py +1 -1
- omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +7 -7
- omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +4 -3
- omnibase_infra/nodes/node_registration_storage_effect/node.py +4 -1
- omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +1 -1
- omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +4 -1
- omnibase_infra/protocols/__init__.py +2 -0
- omnibase_infra/protocols/protocol_container_aware.py +200 -0
- omnibase_infra/runtime/__init__.py +39 -0
- omnibase_infra/runtime/handler_bootstrap_source.py +26 -33
- omnibase_infra/runtime/handler_contract_config_loader.py +1 -1
- omnibase_infra/runtime/handler_contract_source.py +10 -51
- omnibase_infra/runtime/handler_identity.py +81 -0
- omnibase_infra/runtime/handler_plugin_loader.py +15 -0
- omnibase_infra/runtime/handler_registry.py +11 -3
- omnibase_infra/runtime/handler_source_resolver.py +326 -0
- omnibase_infra/runtime/protocol_lifecycle_executor.py +6 -6
- omnibase_infra/runtime/registry/registry_protocol_binding.py +13 -13
- omnibase_infra/runtime/registry_contract_source.py +693 -0
- omnibase_infra/runtime/service_kernel.py +1 -1
- omnibase_infra/runtime/service_runtime_host_process.py +463 -190
- omnibase_infra/runtime/util_wiring.py +12 -3
- omnibase_infra/services/__init__.py +21 -0
- omnibase_infra/services/corpus_capture.py +7 -1
- omnibase_infra/services/mcp/mcp_server_lifecycle.py +9 -3
- omnibase_infra/services/registry_api/main.py +31 -13
- omnibase_infra/services/registry_api/service.py +10 -19
- omnibase_infra/services/service_timeout_emitter.py +7 -1
- omnibase_infra/services/service_timeout_scanner.py +7 -3
- omnibase_infra/services/session/__init__.py +56 -0
- omnibase_infra/services/session/config_consumer.py +120 -0
- omnibase_infra/services/session/config_store.py +139 -0
- omnibase_infra/services/session/consumer.py +1007 -0
- omnibase_infra/services/session/protocol_session_aggregator.py +117 -0
- omnibase_infra/services/session/store.py +997 -0
- omnibase_infra/utils/__init__.py +19 -0
- omnibase_infra/utils/util_atomic_file.py +261 -0
- omnibase_infra/utils/util_db_transaction.py +239 -0
- omnibase_infra/utils/util_retry_optimistic.py +281 -0
- omnibase_infra/validation/validation_exemptions.yaml +27 -0
- {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.3.dist-info}/METADATA +3 -3
- {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.3.dist-info}/RECORD +77 -56
- {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.3.dist-info}/WHEEL +0 -0
- {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.3.dist-info}/entry_points.txt +0 -0
- {omnibase_infra-0.2.2.dist-info → omnibase_infra-0.2.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -53,7 +53,11 @@ from uuid import UUID, uuid4
|
|
|
53
53
|
|
|
54
54
|
from pydantic import BaseModel
|
|
55
55
|
|
|
56
|
-
from omnibase_infra.enums import
|
|
56
|
+
from omnibase_infra.enums import (
|
|
57
|
+
EnumHandlerSourceMode,
|
|
58
|
+
EnumHandlerTypeCategory,
|
|
59
|
+
EnumInfraTransportType,
|
|
60
|
+
)
|
|
57
61
|
from omnibase_infra.errors import (
|
|
58
62
|
EnvelopeValidationError,
|
|
59
63
|
ModelInfraErrorContext,
|
|
@@ -80,14 +84,27 @@ if TYPE_CHECKING:
|
|
|
80
84
|
from omnibase_infra.idempotency.protocol_idempotency_store import (
|
|
81
85
|
ProtocolIdempotencyStore,
|
|
82
86
|
)
|
|
83
|
-
from omnibase_infra.models.handlers import
|
|
87
|
+
from omnibase_infra.models.handlers import ModelHandlerSourceConfig
|
|
84
88
|
from omnibase_infra.nodes.architecture_validator import ProtocolArchitectureRule
|
|
89
|
+
from omnibase_infra.protocols import ProtocolContainerAware
|
|
85
90
|
from omnibase_infra.runtime.contract_handler_discovery import (
|
|
86
91
|
ContractHandlerDiscovery,
|
|
87
92
|
)
|
|
88
|
-
from omnibase_spi.protocols.handlers.protocol_handler import ProtocolHandler
|
|
89
93
|
|
|
94
|
+
# Imports for PluginLoaderContractSource adapter class
|
|
95
|
+
from omnibase_infra.models.errors import ModelHandlerValidationError
|
|
96
|
+
from omnibase_infra.models.handlers import (
|
|
97
|
+
LiteralHandlerKind,
|
|
98
|
+
ModelContractDiscoveryResult,
|
|
99
|
+
ModelHandlerDescriptor,
|
|
100
|
+
)
|
|
90
101
|
from omnibase_infra.models.types import JsonDict
|
|
102
|
+
from omnibase_infra.runtime.handler_identity import (
|
|
103
|
+
HANDLER_IDENTITY_PREFIX,
|
|
104
|
+
handler_identity,
|
|
105
|
+
)
|
|
106
|
+
from omnibase_infra.runtime.handler_plugin_loader import HandlerPluginLoader
|
|
107
|
+
from omnibase_infra.runtime.protocol_contract_source import ProtocolContractSource
|
|
91
108
|
|
|
92
109
|
# Expose wire_default_handlers as wire_handlers for test patching compatibility
|
|
93
110
|
# Tests patch "omnibase_infra.runtime.service_runtime_host_process.wire_handlers"
|
|
@@ -95,6 +112,22 @@ wire_handlers = wire_default_handlers
|
|
|
95
112
|
|
|
96
113
|
logger = logging.getLogger(__name__)
|
|
97
114
|
|
|
115
|
+
# Mapping from EnumHandlerTypeCategory to LiteralHandlerKind for descriptor creation.
|
|
116
|
+
# COMPUTE and EFFECT map directly to their string values.
|
|
117
|
+
# NONDETERMINISTIC_COMPUTE maps to "compute" because it is architecturally pure
|
|
118
|
+
# (no I/O) even though it may produce different results between runs.
|
|
119
|
+
# "effect" is used as the fallback for any unknown types as the safer option
|
|
120
|
+
# (effect handlers have stricter policy envelopes for I/O operations).
|
|
121
|
+
_HANDLER_TYPE_TO_KIND: dict[EnumHandlerTypeCategory, LiteralHandlerKind] = {
|
|
122
|
+
EnumHandlerTypeCategory.COMPUTE: "compute",
|
|
123
|
+
EnumHandlerTypeCategory.EFFECT: "effect",
|
|
124
|
+
EnumHandlerTypeCategory.NONDETERMINISTIC_COMPUTE: "compute",
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Default handler kind for unknown handler types. "effect" is the safe default
|
|
128
|
+
# because effect handlers have stricter policy envelopes for I/O operations.
|
|
129
|
+
_DEFAULT_HANDLER_KIND: LiteralHandlerKind = "effect"
|
|
130
|
+
|
|
98
131
|
# Default configuration values
|
|
99
132
|
DEFAULT_INPUT_TOPIC = "requests"
|
|
100
133
|
DEFAULT_OUTPUT_TOPIC = "responses"
|
|
@@ -126,6 +159,152 @@ DEFAULT_DRAIN_TIMEOUT_SECONDS: float = parse_env_float(
|
|
|
126
159
|
)
|
|
127
160
|
|
|
128
161
|
|
|
162
|
+
class PluginLoaderContractSource(ProtocolContractSource):
|
|
163
|
+
"""Adapter that uses HandlerPluginLoader for contract discovery.
|
|
164
|
+
|
|
165
|
+
This adapter implements ProtocolContractSource using HandlerPluginLoader,
|
|
166
|
+
which uses the simpler contract schema (handler_name, handler_class,
|
|
167
|
+
handler_type, capability_tags) rather than the full ONEX contract schema.
|
|
168
|
+
|
|
169
|
+
This class wraps the HandlerPluginLoader to conform to the ProtocolContractSource
|
|
170
|
+
interface expected by HandlerSourceResolver, enabling plugin-based handler
|
|
171
|
+
discovery within the unified handler source resolution framework.
|
|
172
|
+
|
|
173
|
+
Attributes:
|
|
174
|
+
_contract_paths: List of filesystem paths to scan for handler contracts.
|
|
175
|
+
_plugin_loader: The underlying HandlerPluginLoader instance.
|
|
176
|
+
|
|
177
|
+
Example:
|
|
178
|
+
```python
|
|
179
|
+
from pathlib import Path
|
|
180
|
+
source = PluginLoaderContractSource(
|
|
181
|
+
contract_paths=[Path("/etc/onex/handlers")]
|
|
182
|
+
)
|
|
183
|
+
result = await source.discover_handlers()
|
|
184
|
+
for descriptor in result.descriptors:
|
|
185
|
+
print(f"Found handler: {descriptor.name}")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
.. versionadded:: 0.7.0
|
|
189
|
+
Extracted from _resolve_handler_descriptors() method for better
|
|
190
|
+
testability and code organization.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
def __init__(
|
|
194
|
+
self,
|
|
195
|
+
contract_paths: list[Path],
|
|
196
|
+
allowed_namespaces: tuple[str, ...] | None = None,
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Initialize the contract source with paths to scan.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
contract_paths: List of filesystem paths containing handler contracts.
|
|
202
|
+
allowed_namespaces: Optional tuple of allowed module namespaces for
|
|
203
|
+
handler class imports. If None, all namespaces are allowed.
|
|
204
|
+
"""
|
|
205
|
+
self._contract_paths = contract_paths
|
|
206
|
+
self._allowed_namespaces = allowed_namespaces
|
|
207
|
+
self._plugin_loader = HandlerPluginLoader(
|
|
208
|
+
allowed_namespaces=list(allowed_namespaces) if allowed_namespaces else None
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def source_type(self) -> str:
|
|
213
|
+
"""Return the source type identifier.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
str: Always "CONTRACT" for this filesystem-based source.
|
|
217
|
+
"""
|
|
218
|
+
return "CONTRACT"
|
|
219
|
+
|
|
220
|
+
async def discover_handlers(self) -> ModelContractDiscoveryResult:
|
|
221
|
+
"""Discover handlers using HandlerPluginLoader.
|
|
222
|
+
|
|
223
|
+
Scans all configured contract paths and loads handler contracts using
|
|
224
|
+
the HandlerPluginLoader. Each discovered handler is converted to a
|
|
225
|
+
ModelHandlerDescriptor for use by the handler resolution framework.
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
ModelContractDiscoveryResult: Container with discovered descriptors
|
|
229
|
+
and any validation errors encountered during discovery.
|
|
230
|
+
|
|
231
|
+
Note:
|
|
232
|
+
This method uses graceful degradation - if a single contract path
|
|
233
|
+
fails to load, discovery continues with remaining paths and the
|
|
234
|
+
error is logged but not raised.
|
|
235
|
+
"""
|
|
236
|
+
# NOTE: ModelContractDiscoveryResult.model_rebuild() is called at module-level
|
|
237
|
+
# in handler_source_resolver.py and handler_contract_source.py to resolve
|
|
238
|
+
# forward references. No need to call it here - see those modules for rationale.
|
|
239
|
+
|
|
240
|
+
descriptors: list[ModelHandlerDescriptor] = []
|
|
241
|
+
validation_errors: list[ModelHandlerValidationError] = []
|
|
242
|
+
|
|
243
|
+
for path in self._contract_paths:
|
|
244
|
+
path_obj = Path(path) if isinstance(path, str) else path
|
|
245
|
+
if not path_obj.exists():
|
|
246
|
+
logger.warning(
|
|
247
|
+
"Contract path does not exist, skipping: %s",
|
|
248
|
+
path_obj,
|
|
249
|
+
)
|
|
250
|
+
continue
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
# Use plugin loader to discover handlers with simpler schema
|
|
254
|
+
loaded_handlers = self._plugin_loader.load_from_directory(
|
|
255
|
+
directory=path_obj,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
# Convert ModelLoadedHandler to ModelHandlerDescriptor
|
|
259
|
+
for loaded in loaded_handlers:
|
|
260
|
+
# Map EnumHandlerTypeCategory to LiteralHandlerKind.
|
|
261
|
+
# handler_type is required on ModelLoadedHandler, so this always
|
|
262
|
+
# provides a valid value. The mapping handles COMPUTE, EFFECT,
|
|
263
|
+
# and NONDETERMINISTIC_COMPUTE. Falls back to "effect" for any
|
|
264
|
+
# unknown types as the safer option (stricter policy envelope).
|
|
265
|
+
handler_kind = _HANDLER_TYPE_TO_KIND.get(
|
|
266
|
+
loaded.handler_type, _DEFAULT_HANDLER_KIND
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
descriptor = ModelHandlerDescriptor(
|
|
270
|
+
# NOTE: Uses handler_identity() for consistent ID generation.
|
|
271
|
+
# In HYBRID mode, HandlerSourceResolver compares handler_id values to
|
|
272
|
+
# determine which handler wins when both sources provide the same handler.
|
|
273
|
+
# Contract handlers need matching IDs to override their bootstrap equivalents.
|
|
274
|
+
#
|
|
275
|
+
# The "proto." prefix is a **protocol identity namespace**, NOT a source
|
|
276
|
+
# indicator. Both bootstrap and contract sources use this prefix via the
|
|
277
|
+
# shared handler_identity() helper. This enables per-handler identity
|
|
278
|
+
# matching regardless of which source discovered the handler.
|
|
279
|
+
#
|
|
280
|
+
# See: HandlerSourceResolver._resolve_hybrid() for resolution logic.
|
|
281
|
+
# See: handler_identity.py for the shared helper function.
|
|
282
|
+
handler_id=handler_identity(loaded.protocol_type),
|
|
283
|
+
name=loaded.handler_name,
|
|
284
|
+
version=loaded.handler_version,
|
|
285
|
+
handler_kind=handler_kind,
|
|
286
|
+
input_model="omnibase_infra.models.types.JsonDict",
|
|
287
|
+
output_model="omnibase_core.models.dispatch.ModelHandlerOutput",
|
|
288
|
+
description=f"Handler: {loaded.handler_name}",
|
|
289
|
+
handler_class=loaded.handler_class,
|
|
290
|
+
contract_path=str(loaded.contract_path),
|
|
291
|
+
)
|
|
292
|
+
descriptors.append(descriptor)
|
|
293
|
+
|
|
294
|
+
except Exception as e:
|
|
295
|
+
logger.warning(
|
|
296
|
+
"Failed to load handlers from path %s: %s",
|
|
297
|
+
path_obj,
|
|
298
|
+
e,
|
|
299
|
+
)
|
|
300
|
+
# Continue with other paths (graceful degradation)
|
|
301
|
+
|
|
302
|
+
return ModelContractDiscoveryResult(
|
|
303
|
+
descriptors=descriptors,
|
|
304
|
+
validation_errors=validation_errors,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
129
308
|
class RuntimeHostProcess:
|
|
130
309
|
"""Runtime host process that owns event bus and coordinates handlers.
|
|
131
310
|
|
|
@@ -233,7 +412,7 @@ class RuntimeHostProcess:
|
|
|
233
412
|
|
|
234
413
|
Purpose:
|
|
235
414
|
Provides the registry that maps handler_type strings (e.g., "http", "db")
|
|
236
|
-
to their corresponding
|
|
415
|
+
to their corresponding ProtocolContainerAware classes. The registry is queried
|
|
237
416
|
during start() to instantiate and initialize all registered handlers.
|
|
238
417
|
|
|
239
418
|
Resolution Order:
|
|
@@ -474,7 +653,7 @@ class RuntimeHostProcess:
|
|
|
474
653
|
|
|
475
654
|
# Handler registry (handler_type -> handler instance)
|
|
476
655
|
# This will be populated from the singleton registry during start()
|
|
477
|
-
self._handlers: dict[str,
|
|
656
|
+
self._handlers: dict[str, ProtocolContainerAware] = {}
|
|
478
657
|
|
|
479
658
|
# Track failed handler instantiations (handler_type -> error message)
|
|
480
659
|
# Used by health_check() to report degraded state
|
|
@@ -787,7 +966,7 @@ class RuntimeHostProcess:
|
|
|
787
966
|
" - Look for: AMBIGUOUS_CONTRACT (HANDLER_LOADER_040)\n\n"
|
|
788
967
|
" 6. If using wire_handlers() manually:\n"
|
|
789
968
|
" - Ensure wire_handlers() is called before RuntimeHostProcess.start()\n"
|
|
790
|
-
" - Check that handlers implement
|
|
969
|
+
" - Check that handlers implement ProtocolContainerAware interface\n\n"
|
|
791
970
|
" 7. Docker/container environment:\n"
|
|
792
971
|
" - Verify volume mounts include handler contract directories\n"
|
|
793
972
|
" - Check ONEX_CONTRACTS_DIR is set in docker-compose.yml/Dockerfile\n"
|
|
@@ -976,183 +1155,287 @@ class RuntimeHostProcess:
|
|
|
976
1155
|
|
|
977
1156
|
logger.info("RuntimeHostProcess stopped successfully")
|
|
978
1157
|
|
|
979
|
-
|
|
980
|
-
"""
|
|
1158
|
+
def _load_handler_source_config(self) -> ModelHandlerSourceConfig:
|
|
1159
|
+
"""Load handler source configuration from runtime config.
|
|
981
1160
|
|
|
982
|
-
|
|
983
|
-
|
|
1161
|
+
Loads the handler source mode configuration that controls how handlers
|
|
1162
|
+
are discovered (BOOTSTRAP, CONTRACT, or HYBRID mode).
|
|
984
1163
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
essential for the ONEX runtime and are loaded from descriptor-based
|
|
989
|
-
definitions rather than filesystem contracts.
|
|
1164
|
+
Config Keys:
|
|
1165
|
+
handler_source_mode: "bootstrap" | "contract" | "hybrid" (default: "hybrid")
|
|
1166
|
+
bootstrap_expires_at: ISO-8601 datetime string (optional, UTC required)
|
|
990
1167
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
to auto-discover and register additional handlers from the specified paths.
|
|
1168
|
+
Returns:
|
|
1169
|
+
ModelHandlerSourceConfig with validated settings.
|
|
994
1170
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1171
|
+
Note:
|
|
1172
|
+
If no configuration is provided, defaults to HYBRID mode with no
|
|
1173
|
+
bootstrap expiry (bootstrap handlers always available as fallback).
|
|
998
1174
|
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
initializes these handler classes.
|
|
1175
|
+
.. versionadded:: 0.7.0
|
|
1176
|
+
Part of OMN-1095 handler source mode integration.
|
|
1002
1177
|
"""
|
|
1003
|
-
#
|
|
1004
|
-
#
|
|
1005
|
-
|
|
1006
|
-
await self._register_bootstrap_handlers()
|
|
1178
|
+
# Deferred imports: avoid circular dependencies at module load time
|
|
1179
|
+
# and reduce import overhead when this method is not called.
|
|
1180
|
+
from datetime import datetime
|
|
1007
1181
|
|
|
1008
|
-
|
|
1009
|
-
# Contract-based handler discovery (OMN-1133)
|
|
1010
|
-
await self._discover_handlers_from_contracts()
|
|
1182
|
+
from pydantic import ValidationError
|
|
1011
1183
|
|
|
1012
|
-
|
|
1013
|
-
"""Discover and register handlers from contract files.
|
|
1184
|
+
from omnibase_infra.models.handlers import ModelHandlerSourceConfig
|
|
1014
1185
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
configured contract_paths, and registers them with the handler registry.
|
|
1186
|
+
config = self._config or {}
|
|
1187
|
+
handler_source_config = config.get("handler_source", {})
|
|
1018
1188
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1189
|
+
if isinstance(handler_source_config, dict):
|
|
1190
|
+
mode_str = handler_source_config.get(
|
|
1191
|
+
"mode", EnumHandlerSourceMode.HYBRID.value
|
|
1192
|
+
)
|
|
1193
|
+
expires_at_str = handler_source_config.get("bootstrap_expires_at")
|
|
1194
|
+
allow_override_raw = handler_source_config.get(
|
|
1195
|
+
"allow_bootstrap_override", False
|
|
1196
|
+
)
|
|
1023
1197
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1198
|
+
# Parse mode
|
|
1199
|
+
try:
|
|
1200
|
+
mode = EnumHandlerSourceMode(mode_str)
|
|
1201
|
+
except ValueError:
|
|
1202
|
+
logger.warning(
|
|
1203
|
+
"Invalid handler_source_mode, defaulting to HYBRID",
|
|
1204
|
+
extra={"invalid_value": mode_str},
|
|
1205
|
+
)
|
|
1206
|
+
mode = EnumHandlerSourceMode.HYBRID
|
|
1028
1207
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1208
|
+
# Parse expiry datetime
|
|
1209
|
+
expires_at = None
|
|
1210
|
+
if expires_at_str:
|
|
1211
|
+
try:
|
|
1212
|
+
expires_at = datetime.fromisoformat(str(expires_at_str))
|
|
1213
|
+
except ValueError:
|
|
1214
|
+
logger.warning(
|
|
1215
|
+
"Invalid bootstrap_expires_at format, ignoring",
|
|
1216
|
+
extra={"invalid_value": expires_at_str},
|
|
1217
|
+
)
|
|
1218
|
+
|
|
1219
|
+
# Construct config with validation - catch naive datetime errors
|
|
1220
|
+
# Note: allow_bootstrap_override coercion handled by Pydantic field validator
|
|
1221
|
+
try:
|
|
1222
|
+
return ModelHandlerSourceConfig(
|
|
1223
|
+
handler_source_mode=mode,
|
|
1224
|
+
bootstrap_expires_at=expires_at,
|
|
1225
|
+
allow_bootstrap_override=allow_override_raw,
|
|
1226
|
+
)
|
|
1227
|
+
except ValidationError as e:
|
|
1228
|
+
# Check if error is due to naive datetime (no timezone info)
|
|
1229
|
+
error_messages = [err.get("msg", "") for err in e.errors()]
|
|
1230
|
+
if any("timezone-aware" in msg for msg in error_messages):
|
|
1231
|
+
logger.warning(
|
|
1232
|
+
"bootstrap_expires_at must be timezone-aware (UTC recommended). "
|
|
1233
|
+
"Naive datetime provided - falling back to no expiry. "
|
|
1234
|
+
"Use ISO format with timezone: '2026-02-01T00:00:00+00:00' "
|
|
1235
|
+
"or '2026-02-01T00:00:00Z'",
|
|
1236
|
+
extra={
|
|
1237
|
+
"invalid_value": expires_at_str,
|
|
1238
|
+
"parsed_datetime": str(expires_at) if expires_at else None,
|
|
1239
|
+
},
|
|
1240
|
+
)
|
|
1241
|
+
# Fall back to config without expiry
|
|
1242
|
+
return ModelHandlerSourceConfig(
|
|
1243
|
+
handler_source_mode=mode,
|
|
1244
|
+
bootstrap_expires_at=None,
|
|
1245
|
+
allow_bootstrap_override=allow_override_raw,
|
|
1246
|
+
)
|
|
1247
|
+
# Re-raise other validation errors
|
|
1248
|
+
raise
|
|
1249
|
+
|
|
1250
|
+
# Default: HYBRID mode with no expiry
|
|
1251
|
+
return ModelHandlerSourceConfig(
|
|
1252
|
+
handler_source_mode=EnumHandlerSourceMode.HYBRID
|
|
1253
|
+
)
|
|
1254
|
+
|
|
1255
|
+
async def _resolve_handler_descriptors(self) -> list[ModelHandlerDescriptor]:
|
|
1256
|
+
"""Resolve handler descriptors using the configured source mode.
|
|
1257
|
+
|
|
1258
|
+
Uses HandlerSourceResolver to discover handlers based on the configured
|
|
1259
|
+
mode (BOOTSTRAP, CONTRACT, or HYBRID). This replaces the previous
|
|
1260
|
+
sequential discovery logic with a unified, mode-driven approach.
|
|
1261
|
+
|
|
1262
|
+
Resolution Modes:
|
|
1263
|
+
- BOOTSTRAP: Only hardcoded bootstrap handlers
|
|
1264
|
+
- CONTRACT: Only filesystem contract-discovered handlers
|
|
1265
|
+
- HYBRID: Contract handlers win per-identity, bootstrap as fallback
|
|
1266
|
+
|
|
1267
|
+
Returns:
|
|
1268
|
+
List of resolved handler descriptors.
|
|
1269
|
+
|
|
1270
|
+
Raises:
|
|
1271
|
+
RuntimeHostError: If validation errors occur and fail-fast is enabled.
|
|
1272
|
+
|
|
1273
|
+
.. versionadded:: 0.7.0
|
|
1274
|
+
Part of OMN-1095 handler source mode integration.
|
|
1033
1275
|
"""
|
|
1034
|
-
from omnibase_infra.runtime.
|
|
1035
|
-
|
|
1276
|
+
from omnibase_infra.runtime.handler_bootstrap_source import (
|
|
1277
|
+
HandlerBootstrapSource,
|
|
1036
1278
|
)
|
|
1037
|
-
from omnibase_infra.runtime.
|
|
1279
|
+
from omnibase_infra.runtime.handler_source_resolver import HandlerSourceResolver
|
|
1280
|
+
|
|
1281
|
+
source_config = self._load_handler_source_config()
|
|
1038
1282
|
|
|
1039
1283
|
logger.info(
|
|
1040
|
-
"
|
|
1284
|
+
"Resolving handlers with source mode",
|
|
1041
1285
|
extra={
|
|
1042
|
-
"
|
|
1043
|
-
"
|
|
1286
|
+
"mode": source_config.handler_source_mode.value,
|
|
1287
|
+
"effective_mode": source_config.effective_mode.value,
|
|
1288
|
+
"bootstrap_expires_at": str(source_config.bootstrap_expires_at)
|
|
1289
|
+
if source_config.bootstrap_expires_at
|
|
1290
|
+
else None,
|
|
1291
|
+
"is_bootstrap_expired": source_config.is_bootstrap_expired,
|
|
1044
1292
|
},
|
|
1045
1293
|
)
|
|
1046
1294
|
|
|
1047
|
-
# Create
|
|
1048
|
-
|
|
1049
|
-
handler_registry = await self._get_handler_registry()
|
|
1295
|
+
# Create bootstrap source
|
|
1296
|
+
bootstrap_source = HandlerBootstrapSource()
|
|
1050
1297
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1298
|
+
# Contract source needs paths - use configured paths or default
|
|
1299
|
+
# If no contract_paths provided, reuse bootstrap_source as placeholder
|
|
1300
|
+
if self._contract_paths:
|
|
1301
|
+
# Use PluginLoaderContractSource which uses the simpler contract schema
|
|
1302
|
+
# compatible with test contracts (handler_name, handler_class, handler_type)
|
|
1303
|
+
contract_source: ProtocolContractSource = PluginLoaderContractSource(
|
|
1304
|
+
contract_paths=self._contract_paths,
|
|
1305
|
+
)
|
|
1306
|
+
else:
|
|
1307
|
+
# No contract paths provided
|
|
1308
|
+
if source_config.effective_mode == EnumHandlerSourceMode.CONTRACT:
|
|
1309
|
+
# CONTRACT mode REQUIRES contract_paths - fail fast
|
|
1310
|
+
raise ProtocolConfigurationError(
|
|
1311
|
+
"CONTRACT mode requires contract_paths to be provided. "
|
|
1312
|
+
"Either provide contract_paths or use HYBRID/BOOTSTRAP mode.",
|
|
1313
|
+
context=ModelInfraErrorContext.with_correlation(
|
|
1314
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
1315
|
+
operation="resolve_handler_descriptors",
|
|
1316
|
+
),
|
|
1317
|
+
)
|
|
1318
|
+
# BOOTSTRAP or HYBRID mode without contract_paths - use bootstrap as fallback
|
|
1319
|
+
#
|
|
1320
|
+
# HYBRID MODE NOTE: When HYBRID mode is configured but no contract_paths
|
|
1321
|
+
# are provided, we reuse bootstrap_source for both the bootstrap_source
|
|
1322
|
+
# and contract_source parameters of HandlerSourceResolver. This means
|
|
1323
|
+
# discover_handlers() will be called twice on the same instance:
|
|
1324
|
+
# 1. Once as the "contract source" (returns bootstrap handlers)
|
|
1325
|
+
# 2. Once as the "bootstrap source" (returns same bootstrap handlers)
|
|
1326
|
+
#
|
|
1327
|
+
# This is intentional: HYBRID semantics require consulting both sources,
|
|
1328
|
+
# and with no contracts available, bootstrap provides all handlers.
|
|
1329
|
+
# The HandlerSourceResolver's HYBRID merge logic (contract wins per-identity,
|
|
1330
|
+
# bootstrap as fallback) produces the correct result since both sources
|
|
1331
|
+
# return identical handlers. The outcome is functionally equivalent to
|
|
1332
|
+
# BOOTSTRAP mode but maintains HYBRID logging/metrics for observability.
|
|
1333
|
+
#
|
|
1334
|
+
# DO NOT "optimize" this to skip the second call - it would break
|
|
1335
|
+
# metrics expectations (contract_handler_count would not be logged)
|
|
1336
|
+
# and change HYBRID mode semantics. See test_bootstrap_source_integration.py
|
|
1337
|
+
# test_bootstrap_source_called_during_start() for the verification test.
|
|
1338
|
+
logger.debug(
|
|
1339
|
+
"HYBRID mode: No contract_paths provided, using bootstrap source "
|
|
1340
|
+
"as fallback for contract source",
|
|
1341
|
+
extra={
|
|
1342
|
+
"mode": source_config.effective_mode.value,
|
|
1343
|
+
"behavior": "bootstrap_source_reused",
|
|
1344
|
+
},
|
|
1345
|
+
)
|
|
1346
|
+
contract_source = bootstrap_source
|
|
1347
|
+
|
|
1348
|
+
# Create resolver with the effective mode (handles expiry enforcement)
|
|
1349
|
+
resolver = HandlerSourceResolver(
|
|
1350
|
+
bootstrap_source=bootstrap_source,
|
|
1351
|
+
contract_source=contract_source,
|
|
1352
|
+
mode=source_config.effective_mode,
|
|
1353
|
+
allow_bootstrap_override=source_config.allow_bootstrap_override,
|
|
1054
1354
|
)
|
|
1055
1355
|
|
|
1056
|
-
#
|
|
1057
|
-
|
|
1058
|
-
|
|
1356
|
+
# Resolve handlers
|
|
1357
|
+
result = await resolver.resolve_handlers()
|
|
1358
|
+
|
|
1359
|
+
# Log resolution results
|
|
1360
|
+
logger.info(
|
|
1361
|
+
"Handler resolution completed",
|
|
1362
|
+
extra={
|
|
1363
|
+
"descriptor_count": len(result.descriptors),
|
|
1364
|
+
"validation_error_count": len(result.validation_errors),
|
|
1365
|
+
"mode": source_config.effective_mode.value,
|
|
1366
|
+
},
|
|
1059
1367
|
)
|
|
1060
1368
|
|
|
1061
|
-
# Log
|
|
1062
|
-
if
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
"handlers_registered": discovery_result.handlers_registered,
|
|
1068
|
-
"error_count": len(discovery_result.errors),
|
|
1069
|
-
},
|
|
1369
|
+
# Log validation errors but continue with valid descriptors (graceful degradation)
|
|
1370
|
+
# This allows the runtime to start with bootstrap handlers even if some contracts fail
|
|
1371
|
+
if result.validation_errors:
|
|
1372
|
+
error_summary = "; ".join(
|
|
1373
|
+
f"{e.handler_identity.handler_id or 'unknown'}: {e.message}"
|
|
1374
|
+
for e in result.validation_errors[:5] # Show first 5
|
|
1070
1375
|
)
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
extra={
|
|
1077
|
-
"error_code": error.error_code,
|
|
1078
|
-
"handler_name": error.handler_name,
|
|
1079
|
-
"contract_path": str(error.contract_path)
|
|
1080
|
-
if error.contract_path
|
|
1081
|
-
else None,
|
|
1082
|
-
},
|
|
1083
|
-
)
|
|
1084
|
-
else:
|
|
1085
|
-
logger.info(
|
|
1086
|
-
"Handler discovery completed successfully",
|
|
1376
|
+
if len(result.validation_errors) > 5:
|
|
1377
|
+
error_summary += f" ... and {len(result.validation_errors) - 5} more"
|
|
1378
|
+
|
|
1379
|
+
logger.warning(
|
|
1380
|
+
"Handler resolution completed with validation errors (continuing with valid handlers)",
|
|
1087
1381
|
extra={
|
|
1088
|
-
"
|
|
1089
|
-
"
|
|
1382
|
+
"error_count": len(result.validation_errors),
|
|
1383
|
+
"valid_descriptor_count": len(result.descriptors),
|
|
1384
|
+
"error_summary": error_summary,
|
|
1090
1385
|
},
|
|
1091
1386
|
)
|
|
1092
1387
|
|
|
1093
|
-
|
|
1094
|
-
"""Register core infrastructure handlers from HandlerBootstrapSource.
|
|
1388
|
+
return list(result.descriptors)
|
|
1095
1389
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
core infrastructure handlers (Consul, DB, HTTP, Vault) without requiring
|
|
1099
|
-
contract.yaml files on the filesystem.
|
|
1390
|
+
async def _discover_or_wire_handlers(self) -> None:
|
|
1391
|
+
"""Discover and register handlers for the runtime.
|
|
1100
1392
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1393
|
+
This method implements the handler discovery/wiring step (Step 3) of the
|
|
1394
|
+
start() sequence. It uses HandlerSourceResolver to discover handlers
|
|
1395
|
+
based on the configured source mode.
|
|
1104
1396
|
|
|
1105
|
-
Handler
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
a. Extract protocol type from handler_id (e.g., "bootstrap.consul" -> "consul")
|
|
1110
|
-
b. Import handler class from fully qualified class path
|
|
1111
|
-
c. Register class with handler registry
|
|
1397
|
+
Handler Source Modes (OMN-1095):
|
|
1398
|
+
- BOOTSTRAP: Only hardcoded bootstrap handlers (fast, no filesystem I/O)
|
|
1399
|
+
- CONTRACT: Only filesystem contract-discovered handlers
|
|
1400
|
+
- HYBRID: Contract handlers win per-identity, bootstrap as fallback
|
|
1112
1401
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1402
|
+
The mode is configured via runtime config:
|
|
1403
|
+
handler_source:
|
|
1404
|
+
mode: "hybrid" # bootstrap|contract|hybrid
|
|
1405
|
+
bootstrap_expires_at: "2026-02-01T00:00:00Z" # Optional, UTC
|
|
1117
1406
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
- _discover_or_wire_handlers: Caller that orchestrates all handler loading
|
|
1122
|
-
"""
|
|
1123
|
-
from omnibase_infra.runtime.handler_bootstrap_source import (
|
|
1124
|
-
SOURCE_TYPE_BOOTSTRAP,
|
|
1125
|
-
HandlerBootstrapSource,
|
|
1126
|
-
)
|
|
1407
|
+
The discovery/wiring step registers handler CLASSES with the handler registry.
|
|
1408
|
+
The subsequent _populate_handlers_from_registry() step instantiates and
|
|
1409
|
+
initializes these handler classes.
|
|
1127
1410
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1411
|
+
.. versionchanged:: 0.7.0
|
|
1412
|
+
Replaced sequential bootstrap+contract discovery with unified
|
|
1413
|
+
HandlerSourceResolver-based resolution (OMN-1095).
|
|
1414
|
+
"""
|
|
1415
|
+
# Resolve handlers using configured source mode
|
|
1416
|
+
descriptors = await self._resolve_handler_descriptors()
|
|
1132
1417
|
|
|
1133
1418
|
# Get handler registry for registration
|
|
1134
1419
|
handler_registry = await self._get_handler_registry()
|
|
1135
1420
|
|
|
1136
|
-
# Create bootstrap source and discover handlers
|
|
1137
|
-
bootstrap_source = HandlerBootstrapSource()
|
|
1138
|
-
discovery_result = await bootstrap_source.discover_handlers()
|
|
1139
|
-
|
|
1140
1421
|
registered_count = 0
|
|
1141
1422
|
error_count = 0
|
|
1142
1423
|
|
|
1143
|
-
for descriptor in
|
|
1424
|
+
for descriptor in descriptors:
|
|
1144
1425
|
try:
|
|
1145
1426
|
# Extract protocol type from handler_id
|
|
1146
|
-
# Handler IDs
|
|
1147
|
-
#
|
|
1148
|
-
# removeprefix
|
|
1149
|
-
protocol_type = descriptor.handler_id.removeprefix(
|
|
1427
|
+
# Handler IDs use "proto." prefix for identity matching (e.g., "proto.consul" -> "consul")
|
|
1428
|
+
# Contract handlers also use this prefix for HYBRID mode resolution
|
|
1429
|
+
# removeprefix() is a no-op if prefix doesn't exist, so handlers without prefix keep their name as-is
|
|
1430
|
+
protocol_type = descriptor.handler_id.removeprefix(
|
|
1431
|
+
f"{HANDLER_IDENTITY_PREFIX}."
|
|
1432
|
+
)
|
|
1150
1433
|
|
|
1151
1434
|
# Import the handler class from fully qualified path
|
|
1152
1435
|
handler_class_path = descriptor.handler_class
|
|
1153
1436
|
if handler_class_path is None:
|
|
1154
1437
|
logger.warning(
|
|
1155
|
-
"
|
|
1438
|
+
"Handler descriptor missing handler_class, skipping",
|
|
1156
1439
|
extra={
|
|
1157
1440
|
"handler_id": descriptor.handler_id,
|
|
1158
1441
|
"handler_name": descriptor.name,
|
|
@@ -1161,7 +1444,7 @@ class RuntimeHostProcess:
|
|
|
1161
1444
|
error_count += 1
|
|
1162
1445
|
continue
|
|
1163
1446
|
|
|
1164
|
-
# Import class using rsplit pattern
|
|
1447
|
+
# Import class using rsplit pattern
|
|
1165
1448
|
if "." not in handler_class_path:
|
|
1166
1449
|
logger.error(
|
|
1167
1450
|
"Invalid handler class path (must be fully qualified): %s",
|
|
@@ -1175,13 +1458,13 @@ class RuntimeHostProcess:
|
|
|
1175
1458
|
module = importlib.import_module(module_path)
|
|
1176
1459
|
handler_cls = getattr(module, class_name)
|
|
1177
1460
|
|
|
1178
|
-
# Verify
|
|
1461
|
+
# Verify handler_cls is actually a class before registration
|
|
1179
1462
|
if not isinstance(handler_cls, type):
|
|
1180
1463
|
logger.error(
|
|
1181
|
-
"Handler path does not resolve to a class
|
|
1182
|
-
handler_class_path,
|
|
1464
|
+
"Handler class path does not resolve to a class type",
|
|
1183
1465
|
extra={
|
|
1184
1466
|
"handler_id": descriptor.handler_id,
|
|
1467
|
+
"handler_class_path": handler_class_path,
|
|
1185
1468
|
"resolved_type": type(handler_cls).__name__,
|
|
1186
1469
|
},
|
|
1187
1470
|
)
|
|
@@ -1190,67 +1473,47 @@ class RuntimeHostProcess:
|
|
|
1190
1473
|
|
|
1191
1474
|
# Register with handler registry
|
|
1192
1475
|
handler_registry.register(protocol_type, handler_cls)
|
|
1193
|
-
registered_count += 1
|
|
1194
1476
|
|
|
1195
|
-
# Store descriptor for later use during
|
|
1196
|
-
# This enables passing contract_config to handler.initialize()
|
|
1477
|
+
# Store descriptor for later use during initialization
|
|
1197
1478
|
self._handler_descriptors[protocol_type] = descriptor
|
|
1198
1479
|
|
|
1480
|
+
registered_count += 1
|
|
1199
1481
|
logger.debug(
|
|
1200
|
-
"Registered
|
|
1201
|
-
protocol_type,
|
|
1202
|
-
handler_class_path,
|
|
1482
|
+
"Registered handler from descriptor",
|
|
1203
1483
|
extra={
|
|
1204
1484
|
"handler_id": descriptor.handler_id,
|
|
1205
1485
|
"protocol_type": protocol_type,
|
|
1206
1486
|
"handler_class": handler_class_path,
|
|
1207
|
-
"has_contract_config": descriptor.contract_config is not None,
|
|
1208
|
-
"source_type": SOURCE_TYPE_BOOTSTRAP,
|
|
1209
1487
|
},
|
|
1210
1488
|
)
|
|
1211
1489
|
|
|
1212
1490
|
except (ImportError, AttributeError):
|
|
1213
|
-
# Module or class import failed
|
|
1214
|
-
error_count += 1
|
|
1215
1491
|
logger.exception(
|
|
1216
|
-
"Failed to import
|
|
1492
|
+
"Failed to import handler",
|
|
1217
1493
|
extra={
|
|
1218
1494
|
"handler_id": descriptor.handler_id,
|
|
1219
1495
|
"handler_class": descriptor.handler_class,
|
|
1220
1496
|
},
|
|
1221
1497
|
)
|
|
1222
|
-
|
|
1223
|
-
except Exception:
|
|
1224
|
-
# Unexpected error - log but continue with other handlers
|
|
1225
1498
|
error_count += 1
|
|
1499
|
+
except Exception:
|
|
1226
1500
|
logger.exception(
|
|
1227
|
-
"Unexpected error registering
|
|
1501
|
+
"Unexpected error registering handler",
|
|
1228
1502
|
extra={
|
|
1229
1503
|
"handler_id": descriptor.handler_id,
|
|
1230
1504
|
"handler_class": descriptor.handler_class,
|
|
1231
1505
|
},
|
|
1232
1506
|
)
|
|
1507
|
+
error_count += 1
|
|
1233
1508
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
"
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
"source_type": SOURCE_TYPE_BOOTSTRAP,
|
|
1243
|
-
},
|
|
1244
|
-
)
|
|
1245
|
-
else:
|
|
1246
|
-
logger.info(
|
|
1247
|
-
"Bootstrap handler registration completed successfully",
|
|
1248
|
-
extra={
|
|
1249
|
-
"registered_count": registered_count,
|
|
1250
|
-
"total_descriptors": len(discovery_result.descriptors),
|
|
1251
|
-
"source_type": SOURCE_TYPE_BOOTSTRAP,
|
|
1252
|
-
},
|
|
1253
|
-
)
|
|
1509
|
+
logger.info(
|
|
1510
|
+
"Handler discovery completed",
|
|
1511
|
+
extra={
|
|
1512
|
+
"registered_count": registered_count,
|
|
1513
|
+
"error_count": error_count,
|
|
1514
|
+
"total_descriptors": len(descriptors),
|
|
1515
|
+
},
|
|
1516
|
+
)
|
|
1254
1517
|
|
|
1255
1518
|
async def _populate_handlers_from_registry(self) -> None:
|
|
1256
1519
|
"""Populate self._handlers from handler registry (container or singleton).
|
|
@@ -1288,6 +1551,10 @@ class RuntimeHostProcess:
|
|
|
1288
1551
|
},
|
|
1289
1552
|
)
|
|
1290
1553
|
|
|
1554
|
+
# Get or create container once for all handlers to share
|
|
1555
|
+
# This ensures all handlers have access to the same DI container
|
|
1556
|
+
container = self._get_or_create_container()
|
|
1557
|
+
|
|
1291
1558
|
for handler_type in registered_types:
|
|
1292
1559
|
# Skip if handler is already registered (e.g., by tests or explicit registration)
|
|
1293
1560
|
if handler_type in self._handlers:
|
|
@@ -1304,10 +1571,15 @@ class RuntimeHostProcess:
|
|
|
1304
1571
|
|
|
1305
1572
|
try:
|
|
1306
1573
|
# Get handler class from singleton registry
|
|
1307
|
-
handler_cls: type[
|
|
1574
|
+
handler_cls: type[ProtocolContainerAware] = handler_registry.get(
|
|
1575
|
+
handler_type
|
|
1576
|
+
)
|
|
1308
1577
|
|
|
1309
|
-
# Instantiate the handler
|
|
1310
|
-
|
|
1578
|
+
# Instantiate the handler with container for dependency injection
|
|
1579
|
+
# ProtocolContainerAware defines __init__(container: ModelONEXContainer)
|
|
1580
|
+
handler_instance: ProtocolContainerAware = handler_cls(
|
|
1581
|
+
container=container
|
|
1582
|
+
)
|
|
1311
1583
|
|
|
1312
1584
|
# Call initialize() if the handler has this method
|
|
1313
1585
|
# Handlers may require async initialization with config
|
|
@@ -1912,12 +2184,14 @@ class RuntimeHostProcess:
|
|
|
1912
2184
|
"no_handlers_registered": no_handlers_registered,
|
|
1913
2185
|
}
|
|
1914
2186
|
|
|
1915
|
-
def register_handler(
|
|
2187
|
+
def register_handler(
|
|
2188
|
+
self, handler_type: str, handler: ProtocolContainerAware
|
|
2189
|
+
) -> None:
|
|
1916
2190
|
"""Register a handler for a specific type.
|
|
1917
2191
|
|
|
1918
2192
|
Args:
|
|
1919
2193
|
handler_type: Protocol type identifier (e.g., "http", "db").
|
|
1920
|
-
handler: Handler instance implementing the
|
|
2194
|
+
handler: Handler instance implementing the ProtocolContainerAware protocol.
|
|
1921
2195
|
"""
|
|
1922
2196
|
self._handlers[handler_type] = handler
|
|
1923
2197
|
logger.debug(
|
|
@@ -1928,7 +2202,7 @@ class RuntimeHostProcess:
|
|
|
1928
2202
|
},
|
|
1929
2203
|
)
|
|
1930
2204
|
|
|
1931
|
-
def get_handler(self, handler_type: str) ->
|
|
2205
|
+
def get_handler(self, handler_type: str) -> ProtocolContainerAware | None:
|
|
1932
2206
|
"""Get handler for type, returns None if not registered.
|
|
1933
2207
|
|
|
1934
2208
|
Args:
|
|
@@ -2015,7 +2289,7 @@ class RuntimeHostProcess:
|
|
|
2015
2289
|
# after validation in _populate_handlers_from_registry). We validate the
|
|
2016
2290
|
# handler CLASSES from the registry, not handler instances.
|
|
2017
2291
|
handler_registry = await self._get_handler_registry()
|
|
2018
|
-
handler_classes: list[type[
|
|
2292
|
+
handler_classes: list[type[ProtocolContainerAware]] = []
|
|
2019
2293
|
for handler_type in handler_registry.list_protocols():
|
|
2020
2294
|
try:
|
|
2021
2295
|
handler_cls = handler_registry.get(handler_type)
|
|
@@ -2086,29 +2360,28 @@ class RuntimeHostProcess:
|
|
|
2086
2360
|
)
|
|
2087
2361
|
|
|
2088
2362
|
def _get_or_create_container(self) -> ModelONEXContainer:
|
|
2089
|
-
"""Get the injected container or create a new one.
|
|
2363
|
+
"""Get the injected container or create and cache a new one.
|
|
2090
2364
|
|
|
2091
2365
|
Returns:
|
|
2092
|
-
ModelONEXContainer instance for
|
|
2366
|
+
ModelONEXContainer instance for dependency injection.
|
|
2093
2367
|
|
|
2094
2368
|
Note:
|
|
2095
|
-
If no container was provided at init, a new container is created
|
|
2096
|
-
|
|
2097
|
-
|
|
2369
|
+
If no container was provided at init, a new container is created
|
|
2370
|
+
and cached in self._container. This ensures all handlers share
|
|
2371
|
+
the same container instance. The container provides basic
|
|
2372
|
+
infrastructure for node execution but may not have all services wired.
|
|
2098
2373
|
"""
|
|
2099
2374
|
if self._container is not None:
|
|
2100
2375
|
return self._container
|
|
2101
2376
|
|
|
2102
|
-
# Create container for
|
|
2377
|
+
# Create container and cache it for reuse
|
|
2103
2378
|
from omnibase_core.models.container.model_onex_container import (
|
|
2104
2379
|
ModelONEXContainer,
|
|
2105
2380
|
)
|
|
2106
2381
|
|
|
2107
|
-
logger.debug(
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
)
|
|
2111
|
-
return ModelONEXContainer()
|
|
2382
|
+
logger.debug("Creating and caching container (no container provided at init)")
|
|
2383
|
+
self._container = ModelONEXContainer()
|
|
2384
|
+
return self._container
|
|
2112
2385
|
|
|
2113
2386
|
# =========================================================================
|
|
2114
2387
|
# Idempotency Guard Methods (OMN-945)
|