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.
- omnibase_infra/__init__.py +1 -1
- omnibase_infra/adapters/adapter_onex_tool_execution.py +451 -0
- 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/cli/commands.py +1 -1
- omnibase_infra/configs/widget_mapping.yaml +176 -0
- omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +5 -2
- omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +5 -2
- 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/errors/error_compute_registry.py +4 -1
- omnibase_infra/errors/error_event_bus_registry.py +4 -1
- omnibase_infra/errors/error_infra.py +3 -1
- omnibase_infra/errors/error_policy_registry.py +4 -1
- 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 +10 -3
- omnibase_infra/handlers/handler_graph.py +10 -5
- omnibase_infra/handlers/handler_http.py +8 -2
- omnibase_infra/handlers/handler_intent.py +387 -0
- omnibase_infra/handlers/handler_mcp.py +745 -63
- omnibase_infra/handlers/handler_vault.py +11 -5
- omnibase_infra/handlers/mixins/mixin_consul_kv.py +4 -3
- omnibase_infra/handlers/mixins/mixin_consul_service.py +2 -1
- omnibase_infra/handlers/registration_storage/handler_registration_storage_postgres.py +7 -0
- omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +308 -4
- omnibase_infra/handlers/service_discovery/models/model_service_info.py +10 -0
- omnibase_infra/mixins/mixin_async_circuit_breaker.py +3 -2
- omnibase_infra/mixins/mixin_node_introspection.py +42 -7
- omnibase_infra/mixins/mixin_retry_execution.py +1 -1
- omnibase_infra/models/discovery/model_introspection_config.py +11 -0
- omnibase_infra/models/handlers/__init__.py +48 -5
- omnibase_infra/models/handlers/model_bootstrap_handler_descriptor.py +162 -0
- omnibase_infra/models/handlers/model_contract_discovery_result.py +6 -4
- omnibase_infra/models/handlers/model_handler_descriptor.py +15 -0
- omnibase_infra/models/handlers/model_handler_source_config.py +220 -0
- omnibase_infra/models/mcp/__init__.py +15 -0
- omnibase_infra/models/mcp/model_mcp_contract_config.py +80 -0
- omnibase_infra/models/mcp/model_mcp_server_config.py +67 -0
- omnibase_infra/models/mcp/model_mcp_tool_definition.py +73 -0
- omnibase_infra/models/mcp/model_mcp_tool_parameter.py +35 -0
- omnibase_infra/models/registration/model_node_capabilities.py +11 -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/architecture_validator/contract_architecture_validator.yaml +0 -5
- omnibase_infra/nodes/architecture_validator/registry/registry_infra_architecture_validator.py +17 -10
- omnibase_infra/nodes/effects/contract.yaml +0 -5
- omnibase_infra/nodes/node_registration_orchestrator/contract.yaml +7 -0
- omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_introspected.py +86 -1
- omnibase_infra/nodes/node_registration_orchestrator/introspection_event_router.py +3 -3
- omnibase_infra/nodes/node_registration_orchestrator/plugin.py +1 -1
- omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +9 -8
- omnibase_infra/nodes/node_registration_orchestrator/timeout_coordinator.py +4 -3
- omnibase_infra/nodes/node_registration_orchestrator/wiring.py +14 -13
- omnibase_infra/nodes/node_registration_storage_effect/contract.yaml +0 -5
- omnibase_infra/nodes/node_registration_storage_effect/node.py +4 -1
- omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +47 -26
- omnibase_infra/nodes/node_registry_effect/contract.yaml +0 -5
- omnibase_infra/nodes/node_registry_effect/handlers/handler_partial_retry.py +2 -1
- omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +28 -20
- omnibase_infra/plugins/examples/plugin_json_normalizer.py +2 -2
- omnibase_infra/plugins/examples/plugin_json_normalizer_error_handling.py +2 -2
- omnibase_infra/plugins/plugin_compute_base.py +16 -2
- omnibase_infra/protocols/__init__.py +2 -0
- omnibase_infra/protocols/protocol_container_aware.py +200 -0
- omnibase_infra/protocols/protocol_event_projector.py +1 -1
- omnibase_infra/runtime/__init__.py +90 -1
- omnibase_infra/runtime/binding_config_resolver.py +102 -37
- omnibase_infra/runtime/constants_notification.py +75 -0
- omnibase_infra/runtime/contract_handler_discovery.py +6 -1
- omnibase_infra/runtime/handler_bootstrap_source.py +507 -0
- omnibase_infra/runtime/handler_contract_config_loader.py +603 -0
- omnibase_infra/runtime/handler_contract_source.py +267 -186
- omnibase_infra/runtime/handler_identity.py +81 -0
- omnibase_infra/runtime/handler_plugin_loader.py +19 -2
- omnibase_infra/runtime/handler_registry.py +11 -3
- omnibase_infra/runtime/handler_source_resolver.py +326 -0
- omnibase_infra/runtime/mixin_semver_cache.py +25 -1
- omnibase_infra/runtime/mixins/__init__.py +7 -0
- omnibase_infra/runtime/mixins/mixin_projector_notification_publishing.py +566 -0
- omnibase_infra/runtime/mixins/mixin_projector_sql_operations.py +31 -10
- omnibase_infra/runtime/models/__init__.py +24 -0
- omnibase_infra/runtime/models/model_health_check_result.py +2 -1
- omnibase_infra/runtime/models/model_projector_notification_config.py +171 -0
- omnibase_infra/runtime/models/model_transition_notification_outbox_config.py +112 -0
- omnibase_infra/runtime/models/model_transition_notification_outbox_metrics.py +140 -0
- omnibase_infra/runtime/models/model_transition_notification_publisher_metrics.py +357 -0
- omnibase_infra/runtime/projector_plugin_loader.py +1 -1
- omnibase_infra/runtime/projector_shell.py +229 -1
- omnibase_infra/runtime/protocol_lifecycle_executor.py +6 -6
- omnibase_infra/runtime/protocols/__init__.py +10 -0
- omnibase_infra/runtime/registry/registry_protocol_binding.py +16 -15
- omnibase_infra/runtime/registry_contract_source.py +693 -0
- omnibase_infra/runtime/registry_policy.py +9 -326
- omnibase_infra/runtime/secret_resolver.py +4 -2
- omnibase_infra/runtime/service_kernel.py +11 -3
- omnibase_infra/runtime/service_message_dispatch_engine.py +4 -2
- omnibase_infra/runtime/service_runtime_host_process.py +589 -106
- omnibase_infra/runtime/transition_notification_outbox.py +1190 -0
- omnibase_infra/runtime/transition_notification_publisher.py +764 -0
- omnibase_infra/runtime/util_container_wiring.py +6 -5
- omnibase_infra/runtime/util_wiring.py +17 -4
- omnibase_infra/schemas/schema_transition_notification_outbox.sql +245 -0
- omnibase_infra/services/__init__.py +21 -0
- omnibase_infra/services/corpus_capture.py +7 -1
- omnibase_infra/services/mcp/__init__.py +31 -0
- omnibase_infra/services/mcp/mcp_server_lifecycle.py +449 -0
- omnibase_infra/services/mcp/service_mcp_tool_discovery.py +411 -0
- omnibase_infra/services/mcp/service_mcp_tool_registry.py +329 -0
- omnibase_infra/services/mcp/service_mcp_tool_sync.py +547 -0
- omnibase_infra/services/registry_api/__init__.py +40 -0
- omnibase_infra/services/registry_api/main.py +261 -0
- omnibase_infra/services/registry_api/models/__init__.py +66 -0
- omnibase_infra/services/registry_api/models/model_capability_widget_mapping.py +38 -0
- omnibase_infra/services/registry_api/models/model_pagination_info.py +48 -0
- omnibase_infra/services/registry_api/models/model_registry_discovery_response.py +73 -0
- omnibase_infra/services/registry_api/models/model_registry_health_response.py +49 -0
- omnibase_infra/services/registry_api/models/model_registry_instance_view.py +88 -0
- omnibase_infra/services/registry_api/models/model_registry_node_view.py +88 -0
- omnibase_infra/services/registry_api/models/model_registry_summary.py +60 -0
- omnibase_infra/services/registry_api/models/model_response_list_instances.py +43 -0
- omnibase_infra/services/registry_api/models/model_response_list_nodes.py +51 -0
- omnibase_infra/services/registry_api/models/model_warning.py +49 -0
- omnibase_infra/services/registry_api/models/model_widget_defaults.py +28 -0
- omnibase_infra/services/registry_api/models/model_widget_mapping.py +51 -0
- omnibase_infra/services/registry_api/routes.py +371 -0
- omnibase_infra/services/registry_api/service.py +837 -0
- omnibase_infra/services/service_capability_query.py +4 -4
- omnibase_infra/services/service_health.py +3 -2
- omnibase_infra/services/service_timeout_emitter.py +20 -3
- 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_dsn_validation.py +1 -1
- omnibase_infra/utils/util_retry_optimistic.py +281 -0
- omnibase_infra/validation/__init__.py +3 -19
- omnibase_infra/validation/contracts/security.validation.yaml +114 -0
- omnibase_infra/validation/infra_validators.py +35 -24
- omnibase_infra/validation/validation_exemptions.yaml +140 -9
- omnibase_infra/validation/validator_chain_propagation.py +2 -2
- omnibase_infra/validation/validator_runtime_shape.py +1 -1
- omnibase_infra/validation/validator_security.py +473 -370
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/METADATA +3 -3
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/RECORD +161 -98
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/WHEEL +0 -0
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/entry_points.txt +0 -0
- {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -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(
|
|
@@ -820,7 +835,8 @@ class HandlerPluginLoader(ProtocolHandlerPluginLoader):
|
|
|
820
835
|
# Extract error code if available
|
|
821
836
|
error_code: str | None = None
|
|
822
837
|
if hasattr(e, "model") and hasattr(e.model, "context"):
|
|
823
|
-
|
|
838
|
+
loader_error = e.model.context.get("loader_error")
|
|
839
|
+
error_code = str(loader_error) if loader_error is not None else None
|
|
824
840
|
|
|
825
841
|
failed_handlers.append(
|
|
826
842
|
ModelFailedPluginLoad(
|
|
@@ -1126,7 +1142,8 @@ class HandlerPluginLoader(ProtocolHandlerPluginLoader):
|
|
|
1126
1142
|
# Extract error code if available
|
|
1127
1143
|
error_code: str | None = None
|
|
1128
1144
|
if hasattr(e, "model") and hasattr(e.model, "context"):
|
|
1129
|
-
|
|
1145
|
+
loader_error = e.model.context.get("loader_error")
|
|
1146
|
+
error_code = str(loader_error) if loader_error is not None else None
|
|
1130
1147
|
|
|
1131
1148
|
failed_handlers.append(
|
|
1132
1149
|
ModelFailedPluginLoad(
|
|
@@ -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
|
|
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[
|
|
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
|
|
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",
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Handler Source Resolver for Multi-Source Handler Discovery.
|
|
4
|
+
|
|
5
|
+
This module provides the HandlerSourceResolver class, which resolves handlers
|
|
6
|
+
from multiple sources (bootstrap, contract) based on the configured mode.
|
|
7
|
+
|
|
8
|
+
Part of OMN-1095: Handler Source Mode Hybrid Resolution.
|
|
9
|
+
|
|
10
|
+
Resolution Modes:
|
|
11
|
+
- BOOTSTRAP: Only use hardcoded bootstrap handlers.
|
|
12
|
+
- CONTRACT: Only use YAML contract-discovered handlers.
|
|
13
|
+
- HYBRID: Per-handler resolution with configurable precedence.
|
|
14
|
+
|
|
15
|
+
In HYBRID mode, the resolver performs per-handler identity resolution:
|
|
16
|
+
1. Discovers handlers from both bootstrap and contract sources
|
|
17
|
+
2. Builds a handler map keyed by handler_id
|
|
18
|
+
3. Resolves conflicts based on allow_bootstrap_override:
|
|
19
|
+
- False (default): Contract handlers override bootstrap handlers
|
|
20
|
+
- True: Bootstrap handlers override contract handlers
|
|
21
|
+
4. Non-conflicting handlers are included from both sources
|
|
22
|
+
|
|
23
|
+
See Also:
|
|
24
|
+
- EnumHandlerSourceMode: Defines the resolution modes
|
|
25
|
+
- HandlerBootstrapSource: Provides bootstrap handlers
|
|
26
|
+
- HandlerContractSource: Provides contract-discovered handlers
|
|
27
|
+
- ProtocolContractSource: Protocol for handler sources
|
|
28
|
+
|
|
29
|
+
.. versionadded:: 0.7.0
|
|
30
|
+
Created as part of OMN-1095 handler source mode hybrid resolution.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import logging
|
|
36
|
+
from typing import TYPE_CHECKING
|
|
37
|
+
|
|
38
|
+
from omnibase_infra.enums.enum_handler_source_mode import EnumHandlerSourceMode
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from omnibase_infra.runtime.protocol_contract_source import ProtocolContractSource
|
|
42
|
+
|
|
43
|
+
# Import models after TYPE_CHECKING to avoid circular imports
|
|
44
|
+
from omnibase_infra.models.errors import ModelHandlerValidationError
|
|
45
|
+
from omnibase_infra.models.handlers import (
|
|
46
|
+
ModelContractDiscoveryResult,
|
|
47
|
+
ModelHandlerDescriptor,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Forward Reference Resolution:
|
|
51
|
+
# ModelContractDiscoveryResult uses a forward reference to ModelHandlerValidationError.
|
|
52
|
+
# Since we import ModelHandlerValidationError above, we can call model_rebuild() here
|
|
53
|
+
# to resolve the forward reference. This call is idempotent - multiple calls are harmless.
|
|
54
|
+
ModelContractDiscoveryResult.model_rebuild()
|
|
55
|
+
|
|
56
|
+
logger = logging.getLogger(__name__)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class HandlerSourceResolver:
|
|
60
|
+
"""Resolver for multi-source handler discovery with configurable modes.
|
|
61
|
+
|
|
62
|
+
This class resolves handlers from bootstrap and contract sources based on
|
|
63
|
+
the configured mode. It supports three resolution strategies:
|
|
64
|
+
|
|
65
|
+
- BOOTSTRAP: Use only bootstrap handlers, ignore contracts.
|
|
66
|
+
- CONTRACT: Use only contract handlers, ignore bootstrap.
|
|
67
|
+
- HYBRID: Per-handler resolution with configurable precedence:
|
|
68
|
+
- allow_bootstrap_override=False (default): Contract handlers take
|
|
69
|
+
precedence over bootstrap handlers with the same handler_id.
|
|
70
|
+
- allow_bootstrap_override=True: Bootstrap handlers take precedence
|
|
71
|
+
over contract handlers with the same handler_id.
|
|
72
|
+
In both cases, handlers without conflicts are included from both sources.
|
|
73
|
+
|
|
74
|
+
Attributes:
|
|
75
|
+
mode: The configured resolution mode.
|
|
76
|
+
allow_bootstrap_override: If True, bootstrap handlers take precedence
|
|
77
|
+
in HYBRID mode. Default is False (contract precedence).
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
>>> resolver = HandlerSourceResolver(
|
|
81
|
+
... bootstrap_source=bootstrap_source,
|
|
82
|
+
... contract_source=contract_source,
|
|
83
|
+
... mode=EnumHandlerSourceMode.HYBRID,
|
|
84
|
+
... )
|
|
85
|
+
>>> result = await resolver.resolve_handlers()
|
|
86
|
+
>>> print(f"Discovered {len(result.descriptors)} handlers")
|
|
87
|
+
|
|
88
|
+
.. versionadded:: 0.7.0
|
|
89
|
+
Created as part of OMN-1095 handler source mode hybrid resolution.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(
|
|
93
|
+
self,
|
|
94
|
+
bootstrap_source: ProtocolContractSource,
|
|
95
|
+
contract_source: ProtocolContractSource,
|
|
96
|
+
mode: EnumHandlerSourceMode,
|
|
97
|
+
*,
|
|
98
|
+
allow_bootstrap_override: bool = False,
|
|
99
|
+
) -> None:
|
|
100
|
+
"""Initialize the handler source resolver.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
bootstrap_source: Source for bootstrap handlers. Must implement
|
|
104
|
+
ProtocolContractSource with discover_handlers() method.
|
|
105
|
+
contract_source: Source for contract-discovered handlers. Must
|
|
106
|
+
implement ProtocolContractSource with discover_handlers() method.
|
|
107
|
+
mode: Resolution mode determining which sources are used and how
|
|
108
|
+
handlers are merged.
|
|
109
|
+
allow_bootstrap_override: If True, bootstrap handlers override
|
|
110
|
+
contract handlers with the same handler_id in HYBRID mode.
|
|
111
|
+
Default is False (contract handlers take precedence).
|
|
112
|
+
Has no effect in BOOTSTRAP or CONTRACT modes.
|
|
113
|
+
"""
|
|
114
|
+
self._bootstrap_source = bootstrap_source
|
|
115
|
+
self._contract_source = contract_source
|
|
116
|
+
self._mode = mode
|
|
117
|
+
self._allow_bootstrap_override = allow_bootstrap_override
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def mode(self) -> EnumHandlerSourceMode:
|
|
121
|
+
"""Get the configured resolution mode.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
EnumHandlerSourceMode: The mode used for handler resolution.
|
|
125
|
+
"""
|
|
126
|
+
return self._mode
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def allow_bootstrap_override(self) -> bool:
|
|
130
|
+
"""Get the bootstrap override configuration.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
bool: True if bootstrap handlers take precedence in HYBRID mode,
|
|
134
|
+
False if contract handlers take precedence (default).
|
|
135
|
+
"""
|
|
136
|
+
return self._allow_bootstrap_override
|
|
137
|
+
|
|
138
|
+
async def resolve_handlers(self) -> ModelContractDiscoveryResult:
|
|
139
|
+
"""Resolve handlers based on the configured mode.
|
|
140
|
+
|
|
141
|
+
Discovers handlers from the appropriate source(s) based on mode:
|
|
142
|
+
- BOOTSTRAP: Only queries bootstrap source
|
|
143
|
+
- CONTRACT: Only queries contract source
|
|
144
|
+
- HYBRID: Queries both sources and merges with contract precedence
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
ModelContractDiscoveryResult: Container with discovered descriptors
|
|
148
|
+
and any validation errors from the queried source(s).
|
|
149
|
+
"""
|
|
150
|
+
if self._mode == EnumHandlerSourceMode.BOOTSTRAP:
|
|
151
|
+
return await self._resolve_bootstrap()
|
|
152
|
+
elif self._mode == EnumHandlerSourceMode.CONTRACT:
|
|
153
|
+
return await self._resolve_contract()
|
|
154
|
+
else:
|
|
155
|
+
# HYBRID mode
|
|
156
|
+
return await self._resolve_hybrid()
|
|
157
|
+
|
|
158
|
+
async def _resolve_bootstrap(self) -> ModelContractDiscoveryResult:
|
|
159
|
+
"""Resolve handlers using only the bootstrap source.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
ModelContractDiscoveryResult: Handlers from bootstrap source only.
|
|
163
|
+
"""
|
|
164
|
+
result = await self._bootstrap_source.discover_handlers()
|
|
165
|
+
|
|
166
|
+
logger.info(
|
|
167
|
+
"Handler resolution completed (BOOTSTRAP mode)",
|
|
168
|
+
extra={
|
|
169
|
+
"mode": self._mode.value,
|
|
170
|
+
"bootstrap_handler_count": len(result.descriptors),
|
|
171
|
+
"resolved_handler_count": len(result.descriptors),
|
|
172
|
+
},
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return result
|
|
176
|
+
|
|
177
|
+
async def _resolve_contract(self) -> ModelContractDiscoveryResult:
|
|
178
|
+
"""Resolve handlers using only the contract source.
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
ModelContractDiscoveryResult: Handlers from contract source only.
|
|
182
|
+
"""
|
|
183
|
+
result = await self._contract_source.discover_handlers()
|
|
184
|
+
|
|
185
|
+
logger.info(
|
|
186
|
+
"Handler resolution completed (CONTRACT mode)",
|
|
187
|
+
extra={
|
|
188
|
+
"mode": self._mode.value,
|
|
189
|
+
"contract_handler_count": len(result.descriptors),
|
|
190
|
+
"resolved_handler_count": len(result.descriptors),
|
|
191
|
+
},
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
async def _resolve_hybrid(self) -> ModelContractDiscoveryResult:
|
|
197
|
+
"""Resolve handlers using both sources with configurable precedence.
|
|
198
|
+
|
|
199
|
+
In HYBRID mode:
|
|
200
|
+
1. Discover handlers from both bootstrap and contract sources
|
|
201
|
+
2. Build a handler map keyed by handler_id
|
|
202
|
+
3. Resolve conflicts based on allow_bootstrap_override:
|
|
203
|
+
- False (default): Contract handlers override bootstrap handlers
|
|
204
|
+
- True: Bootstrap handlers override contract handlers
|
|
205
|
+
4. Non-conflicting handlers are included from both sources
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
ModelContractDiscoveryResult: Merged handlers with configured
|
|
209
|
+
precedence and combined validation errors from both sources.
|
|
210
|
+
"""
|
|
211
|
+
# Get handlers from both sources
|
|
212
|
+
bootstrap_result = await self._bootstrap_source.discover_handlers()
|
|
213
|
+
contract_result = await self._contract_source.discover_handlers()
|
|
214
|
+
|
|
215
|
+
# Build lookup maps for both sources
|
|
216
|
+
bootstrap_by_id: dict[str, ModelHandlerDescriptor] = {
|
|
217
|
+
d.handler_id: d for d in bootstrap_result.descriptors
|
|
218
|
+
}
|
|
219
|
+
contract_by_id: dict[str, ModelHandlerDescriptor] = {
|
|
220
|
+
d.handler_id: d for d in contract_result.descriptors
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# Determine which source takes precedence
|
|
224
|
+
if self._allow_bootstrap_override:
|
|
225
|
+
# Bootstrap wins conflicts: add bootstrap first, then contract fallbacks
|
|
226
|
+
primary_source = bootstrap_result.descriptors
|
|
227
|
+
primary_by_id = bootstrap_by_id
|
|
228
|
+
secondary_source = contract_result.descriptors
|
|
229
|
+
secondary_by_id = contract_by_id
|
|
230
|
+
primary_name = "bootstrap"
|
|
231
|
+
secondary_name = "contract"
|
|
232
|
+
else:
|
|
233
|
+
# Contract wins conflicts (default): add contract first, then bootstrap fallbacks
|
|
234
|
+
primary_source = contract_result.descriptors
|
|
235
|
+
primary_by_id = contract_by_id
|
|
236
|
+
secondary_source = bootstrap_result.descriptors
|
|
237
|
+
secondary_by_id = bootstrap_by_id
|
|
238
|
+
primary_name = "contract"
|
|
239
|
+
secondary_name = "bootstrap"
|
|
240
|
+
|
|
241
|
+
# Build handler map - primary source handlers first (they take precedence)
|
|
242
|
+
handlers_by_id: dict[str, ModelHandlerDescriptor] = {}
|
|
243
|
+
|
|
244
|
+
# Add primary handlers (they win conflicts)
|
|
245
|
+
for descriptor in primary_source:
|
|
246
|
+
handlers_by_id[descriptor.handler_id] = descriptor
|
|
247
|
+
|
|
248
|
+
# Log primary-only handlers (no secondary equivalent)
|
|
249
|
+
if descriptor.handler_id not in secondary_by_id:
|
|
250
|
+
logger.debug(
|
|
251
|
+
f"Adding {primary_name}-only handler (no {secondary_name} equivalent)",
|
|
252
|
+
extra={
|
|
253
|
+
"handler_id": descriptor.handler_id,
|
|
254
|
+
"handler_name": descriptor.name,
|
|
255
|
+
"source": primary_name,
|
|
256
|
+
},
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# Add secondary handlers only if not already present (fallback)
|
|
260
|
+
fallback_count = 0
|
|
261
|
+
override_count = 0
|
|
262
|
+
for descriptor in secondary_source:
|
|
263
|
+
if descriptor.handler_id in handlers_by_id:
|
|
264
|
+
# Primary handler wins - this is an override
|
|
265
|
+
override_count += 1
|
|
266
|
+
primary_handler = handlers_by_id[descriptor.handler_id]
|
|
267
|
+
logger.debug(
|
|
268
|
+
f"{primary_name.capitalize()} handler overrides {secondary_name} handler",
|
|
269
|
+
extra={
|
|
270
|
+
"handler_id": descriptor.handler_id,
|
|
271
|
+
"primary_name": primary_handler.name,
|
|
272
|
+
"secondary_name": descriptor.name,
|
|
273
|
+
"primary_source": primary_name,
|
|
274
|
+
"secondary_source": secondary_name,
|
|
275
|
+
"contract_path": (
|
|
276
|
+
primary_handler.contract_path
|
|
277
|
+
if primary_name == "contract"
|
|
278
|
+
else descriptor.contract_path
|
|
279
|
+
),
|
|
280
|
+
},
|
|
281
|
+
)
|
|
282
|
+
else:
|
|
283
|
+
# No primary handler with this ID - use secondary as fallback
|
|
284
|
+
handlers_by_id[descriptor.handler_id] = descriptor
|
|
285
|
+
fallback_count += 1
|
|
286
|
+
logger.debug(
|
|
287
|
+
f"Using {secondary_name} handler as fallback (no {primary_name} match)",
|
|
288
|
+
extra={
|
|
289
|
+
"handler_id": descriptor.handler_id,
|
|
290
|
+
"handler_name": descriptor.name,
|
|
291
|
+
"source": secondary_name,
|
|
292
|
+
},
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# NOTE: Validation errors from bootstrap and contract sources are intentionally
|
|
296
|
+
# combined WITHOUT deduplication. During migration, the same error appearing from
|
|
297
|
+
# BOTH sources helps distinguish handler-level issues (error in both) from
|
|
298
|
+
# source-specific configuration problems (error in only one). This preserves
|
|
299
|
+
# diagnostic signal that would be lost if we deduplicated.
|
|
300
|
+
all_errors: list[ModelHandlerValidationError] = list(
|
|
301
|
+
bootstrap_result.validation_errors
|
|
302
|
+
) + list(contract_result.validation_errors)
|
|
303
|
+
|
|
304
|
+
# Log structured counts for observability
|
|
305
|
+
logger.info(
|
|
306
|
+
"Handler resolution completed (HYBRID mode)",
|
|
307
|
+
extra={
|
|
308
|
+
"mode": self._mode.value,
|
|
309
|
+
"allow_bootstrap_override": self._allow_bootstrap_override,
|
|
310
|
+
"precedence": primary_name,
|
|
311
|
+
"contract_handler_count": len(contract_result.descriptors),
|
|
312
|
+
"bootstrap_handler_count": len(bootstrap_result.descriptors),
|
|
313
|
+
"fallback_handler_count": fallback_count,
|
|
314
|
+
"override_count": override_count,
|
|
315
|
+
"resolved_handler_count": len(handlers_by_id),
|
|
316
|
+
"validation_error_count": len(all_errors),
|
|
317
|
+
},
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
return ModelContractDiscoveryResult(
|
|
321
|
+
descriptors=list(handlers_by_id.values()),
|
|
322
|
+
validation_errors=all_errors,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
__all__ = ["HandlerSourceResolver"]
|
|
@@ -23,7 +23,11 @@ from collections.abc import Callable
|
|
|
23
23
|
|
|
24
24
|
from omnibase_core.models.errors import ModelOnexError
|
|
25
25
|
from omnibase_core.models.primitives import ModelSemVer
|
|
26
|
+
from omnibase_infra.enums import EnumInfraTransportType
|
|
26
27
|
from omnibase_infra.errors import ProtocolConfigurationError
|
|
28
|
+
from omnibase_infra.models.errors.model_infra_error_context import (
|
|
29
|
+
ModelInfraErrorContext,
|
|
30
|
+
)
|
|
27
31
|
from omnibase_infra.runtime.util_version import normalize_version
|
|
28
32
|
|
|
29
33
|
|
|
@@ -98,10 +102,15 @@ class MixinSemverCache:
|
|
|
98
102
|
"""
|
|
99
103
|
with cls._semver_cache_lock:
|
|
100
104
|
if cls._semver_cache is not None:
|
|
105
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
106
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
107
|
+
operation="configure_semver_cache",
|
|
108
|
+
)
|
|
101
109
|
raise ProtocolConfigurationError(
|
|
102
110
|
"Cannot reconfigure semver cache after first use. "
|
|
103
111
|
"Set SEMVER_CACHE_SIZE before creating any "
|
|
104
|
-
"registry instances, or use _reset_semver_cache() for testing."
|
|
112
|
+
"registry instances, or use _reset_semver_cache() for testing.",
|
|
113
|
+
context=context,
|
|
105
114
|
)
|
|
106
115
|
cls.SEMVER_CACHE_SIZE = maxsize
|
|
107
116
|
|
|
@@ -219,14 +228,24 @@ class MixinSemverCache:
|
|
|
219
228
|
try:
|
|
220
229
|
return ModelSemVer.parse(normalized_version)
|
|
221
230
|
except ModelOnexError as e:
|
|
231
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
232
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
233
|
+
operation="parse_semver",
|
|
234
|
+
)
|
|
222
235
|
raise ProtocolConfigurationError(
|
|
223
236
|
str(e),
|
|
224
237
|
version=normalized_version,
|
|
238
|
+
context=context,
|
|
225
239
|
) from e
|
|
226
240
|
except ValueError as e:
|
|
241
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
242
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
243
|
+
operation="parse_semver",
|
|
244
|
+
)
|
|
227
245
|
raise ProtocolConfigurationError(
|
|
228
246
|
str(e),
|
|
229
247
|
version=normalized_version,
|
|
248
|
+
context=context,
|
|
230
249
|
) from e
|
|
231
250
|
|
|
232
251
|
def _parse_semver_impl(version: str) -> ModelSemVer:
|
|
@@ -248,9 +267,14 @@ class MixinSemverCache:
|
|
|
248
267
|
try:
|
|
249
268
|
normalized = normalize_version(version)
|
|
250
269
|
except ValueError as e:
|
|
270
|
+
context = ModelInfraErrorContext.with_correlation(
|
|
271
|
+
transport_type=EnumInfraTransportType.RUNTIME,
|
|
272
|
+
operation="normalize_version",
|
|
273
|
+
)
|
|
251
274
|
raise ProtocolConfigurationError(
|
|
252
275
|
str(e),
|
|
253
276
|
version=version,
|
|
277
|
+
context=context,
|
|
254
278
|
) from e
|
|
255
279
|
|
|
256
280
|
# Now call the cached function with the NORMALIZED version
|
|
@@ -6,12 +6,19 @@ This module provides mixins for runtime components such as projectors.
|
|
|
6
6
|
|
|
7
7
|
Exports:
|
|
8
8
|
- MixinProjectorSqlOperations: SQL execution methods for projector implementations
|
|
9
|
+
- MixinProjectorNotificationPublishing: Notification publishing for projector implementations
|
|
9
10
|
"""
|
|
10
11
|
|
|
12
|
+
from omnibase_infra.runtime.mixins.mixin_projector_notification_publishing import (
|
|
13
|
+
MixinProjectorNotificationPublishing,
|
|
14
|
+
ProtocolProjectorNotificationContext,
|
|
15
|
+
)
|
|
11
16
|
from omnibase_infra.runtime.mixins.mixin_projector_sql_operations import (
|
|
12
17
|
MixinProjectorSqlOperations,
|
|
13
18
|
)
|
|
14
19
|
|
|
15
20
|
__all__: list[str] = [
|
|
21
|
+
"MixinProjectorNotificationPublishing",
|
|
16
22
|
"MixinProjectorSqlOperations",
|
|
23
|
+
"ProtocolProjectorNotificationContext",
|
|
17
24
|
]
|