omnibase_infra 0.2.1__py3-none-any.whl → 0.2.2__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 (116) hide show
  1. omnibase_infra/__init__.py +1 -1
  2. omnibase_infra/adapters/adapter_onex_tool_execution.py +446 -0
  3. omnibase_infra/cli/commands.py +1 -1
  4. omnibase_infra/configs/widget_mapping.yaml +176 -0
  5. omnibase_infra/contracts/handlers/filesystem/handler_contract.yaml +4 -1
  6. omnibase_infra/contracts/handlers/mcp/handler_contract.yaml +4 -1
  7. omnibase_infra/errors/error_compute_registry.py +4 -1
  8. omnibase_infra/errors/error_event_bus_registry.py +4 -1
  9. omnibase_infra/errors/error_infra.py +3 -1
  10. omnibase_infra/errors/error_policy_registry.py +4 -1
  11. omnibase_infra/handlers/handler_db.py +2 -1
  12. omnibase_infra/handlers/handler_graph.py +10 -5
  13. omnibase_infra/handlers/handler_mcp.py +736 -63
  14. omnibase_infra/handlers/mixins/mixin_consul_kv.py +4 -3
  15. omnibase_infra/handlers/mixins/mixin_consul_service.py +2 -1
  16. omnibase_infra/handlers/service_discovery/handler_service_discovery_consul.py +301 -4
  17. omnibase_infra/handlers/service_discovery/models/model_service_info.py +10 -0
  18. omnibase_infra/mixins/mixin_async_circuit_breaker.py +3 -2
  19. omnibase_infra/mixins/mixin_node_introspection.py +24 -7
  20. omnibase_infra/mixins/mixin_retry_execution.py +1 -1
  21. omnibase_infra/models/handlers/__init__.py +10 -0
  22. omnibase_infra/models/handlers/model_bootstrap_handler_descriptor.py +162 -0
  23. omnibase_infra/models/handlers/model_handler_descriptor.py +15 -0
  24. omnibase_infra/models/mcp/__init__.py +15 -0
  25. omnibase_infra/models/mcp/model_mcp_contract_config.py +80 -0
  26. omnibase_infra/models/mcp/model_mcp_server_config.py +67 -0
  27. omnibase_infra/models/mcp/model_mcp_tool_definition.py +73 -0
  28. omnibase_infra/models/mcp/model_mcp_tool_parameter.py +35 -0
  29. omnibase_infra/models/registration/model_node_capabilities.py +11 -0
  30. omnibase_infra/nodes/architecture_validator/contract_architecture_validator.yaml +0 -5
  31. omnibase_infra/nodes/architecture_validator/registry/registry_infra_architecture_validator.py +17 -10
  32. omnibase_infra/nodes/effects/contract.yaml +0 -5
  33. omnibase_infra/nodes/node_registration_orchestrator/contract.yaml +7 -0
  34. omnibase_infra/nodes/node_registration_orchestrator/handlers/handler_node_introspected.py +86 -1
  35. omnibase_infra/nodes/node_registration_orchestrator/introspection_event_router.py +3 -3
  36. omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +9 -8
  37. omnibase_infra/nodes/node_registration_orchestrator/wiring.py +14 -13
  38. omnibase_infra/nodes/node_registration_storage_effect/contract.yaml +0 -5
  39. omnibase_infra/nodes/node_registration_storage_effect/registry/registry_infra_registration_storage.py +46 -25
  40. omnibase_infra/nodes/node_registry_effect/contract.yaml +0 -5
  41. omnibase_infra/nodes/node_registry_effect/handlers/handler_partial_retry.py +2 -1
  42. omnibase_infra/nodes/node_service_discovery_effect/registry/registry_infra_service_discovery.py +24 -19
  43. omnibase_infra/plugins/examples/plugin_json_normalizer.py +2 -2
  44. omnibase_infra/plugins/examples/plugin_json_normalizer_error_handling.py +2 -2
  45. omnibase_infra/plugins/plugin_compute_base.py +16 -2
  46. omnibase_infra/protocols/protocol_event_projector.py +1 -1
  47. omnibase_infra/runtime/__init__.py +51 -1
  48. omnibase_infra/runtime/binding_config_resolver.py +102 -37
  49. omnibase_infra/runtime/constants_notification.py +75 -0
  50. omnibase_infra/runtime/contract_handler_discovery.py +6 -1
  51. omnibase_infra/runtime/handler_bootstrap_source.py +514 -0
  52. omnibase_infra/runtime/handler_contract_config_loader.py +603 -0
  53. omnibase_infra/runtime/handler_contract_source.py +289 -167
  54. omnibase_infra/runtime/handler_plugin_loader.py +4 -2
  55. omnibase_infra/runtime/mixin_semver_cache.py +25 -1
  56. omnibase_infra/runtime/mixins/__init__.py +7 -0
  57. omnibase_infra/runtime/mixins/mixin_projector_notification_publishing.py +566 -0
  58. omnibase_infra/runtime/mixins/mixin_projector_sql_operations.py +31 -10
  59. omnibase_infra/runtime/models/__init__.py +24 -0
  60. omnibase_infra/runtime/models/model_health_check_result.py +2 -1
  61. omnibase_infra/runtime/models/model_projector_notification_config.py +171 -0
  62. omnibase_infra/runtime/models/model_transition_notification_outbox_config.py +112 -0
  63. omnibase_infra/runtime/models/model_transition_notification_outbox_metrics.py +140 -0
  64. omnibase_infra/runtime/models/model_transition_notification_publisher_metrics.py +357 -0
  65. omnibase_infra/runtime/projector_plugin_loader.py +1 -1
  66. omnibase_infra/runtime/projector_shell.py +229 -1
  67. omnibase_infra/runtime/protocols/__init__.py +10 -0
  68. omnibase_infra/runtime/registry/registry_protocol_binding.py +3 -2
  69. omnibase_infra/runtime/registry_policy.py +9 -326
  70. omnibase_infra/runtime/secret_resolver.py +4 -2
  71. omnibase_infra/runtime/service_kernel.py +10 -2
  72. omnibase_infra/runtime/service_message_dispatch_engine.py +4 -2
  73. omnibase_infra/runtime/service_runtime_host_process.py +225 -15
  74. omnibase_infra/runtime/transition_notification_outbox.py +1190 -0
  75. omnibase_infra/runtime/transition_notification_publisher.py +764 -0
  76. omnibase_infra/runtime/util_container_wiring.py +6 -5
  77. omnibase_infra/runtime/util_wiring.py +5 -1
  78. omnibase_infra/schemas/schema_transition_notification_outbox.sql +245 -0
  79. omnibase_infra/services/mcp/__init__.py +31 -0
  80. omnibase_infra/services/mcp/mcp_server_lifecycle.py +443 -0
  81. omnibase_infra/services/mcp/service_mcp_tool_discovery.py +411 -0
  82. omnibase_infra/services/mcp/service_mcp_tool_registry.py +329 -0
  83. omnibase_infra/services/mcp/service_mcp_tool_sync.py +547 -0
  84. omnibase_infra/services/registry_api/__init__.py +40 -0
  85. omnibase_infra/services/registry_api/main.py +243 -0
  86. omnibase_infra/services/registry_api/models/__init__.py +66 -0
  87. omnibase_infra/services/registry_api/models/model_capability_widget_mapping.py +38 -0
  88. omnibase_infra/services/registry_api/models/model_pagination_info.py +48 -0
  89. omnibase_infra/services/registry_api/models/model_registry_discovery_response.py +73 -0
  90. omnibase_infra/services/registry_api/models/model_registry_health_response.py +49 -0
  91. omnibase_infra/services/registry_api/models/model_registry_instance_view.py +88 -0
  92. omnibase_infra/services/registry_api/models/model_registry_node_view.py +88 -0
  93. omnibase_infra/services/registry_api/models/model_registry_summary.py +60 -0
  94. omnibase_infra/services/registry_api/models/model_response_list_instances.py +43 -0
  95. omnibase_infra/services/registry_api/models/model_response_list_nodes.py +51 -0
  96. omnibase_infra/services/registry_api/models/model_warning.py +49 -0
  97. omnibase_infra/services/registry_api/models/model_widget_defaults.py +28 -0
  98. omnibase_infra/services/registry_api/models/model_widget_mapping.py +51 -0
  99. omnibase_infra/services/registry_api/routes.py +371 -0
  100. omnibase_infra/services/registry_api/service.py +846 -0
  101. omnibase_infra/services/service_capability_query.py +4 -4
  102. omnibase_infra/services/service_health.py +3 -2
  103. omnibase_infra/services/service_timeout_emitter.py +13 -2
  104. omnibase_infra/utils/util_dsn_validation.py +1 -1
  105. omnibase_infra/validation/__init__.py +3 -19
  106. omnibase_infra/validation/contracts/security.validation.yaml +114 -0
  107. omnibase_infra/validation/infra_validators.py +35 -24
  108. omnibase_infra/validation/validation_exemptions.yaml +113 -9
  109. omnibase_infra/validation/validator_chain_propagation.py +2 -2
  110. omnibase_infra/validation/validator_runtime_shape.py +1 -1
  111. omnibase_infra/validation/validator_security.py +473 -370
  112. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.2.dist-info}/METADATA +2 -2
  113. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.2.dist-info}/RECORD +116 -74
  114. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.2.dist-info}/WHEEL +0 -0
  115. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.2.dist-info}/entry_points.txt +0 -0
  116. {omnibase_infra-0.2.1.dist-info → omnibase_infra-0.2.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,514 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Handler Bootstrap Source for Hardcoded Handler Registration.
4
+
5
+ This module provides HandlerBootstrapSource, which centralizes all hardcoded handler
6
+ wiring that was previously scattered in util_wiring.py. This source implements
7
+ ProtocolContractSource and provides handler descriptors for the core infrastructure
8
+ handlers (Consul, Database, HTTP, Vault, MCP).
9
+
10
+ Part of OMN-1087: HandlerBootstrapSource for hardcoded handler registration.
11
+
12
+ The bootstrap source provides handler descriptors for effect handlers that interact
13
+ with external infrastructure services. These handlers use envelope-based routing
14
+ and are registered as the foundation of the ONEX runtime handler ecosystem.
15
+
16
+ Contract Loading:
17
+ Bootstrap handlers now load their contract YAML files during discovery.
18
+ This enables:
19
+ - Security metadata (trusted_namespace, audit_logging, requires_authentication)
20
+ - Tags for handler discovery and filtering
21
+ - Validation that contract files exist and are well-formed
22
+
23
+ Contract file locations (relative to repo root):
24
+ - contracts/handlers/consul/handler_contract.yaml (basic contract)
25
+ - contracts/handlers/db/handler_contract.yaml (basic contract)
26
+ - contracts/handlers/http/handler_contract.yaml (basic contract)
27
+ - contracts/handlers/vault/handler_contract.yaml (basic contract)
28
+ - src/omnibase_infra/contracts/handlers/mcp/handler_contract.yaml (rich contract with transport config)
29
+
30
+ Basic contracts provide: name, handler_class, handler_type, tags, security
31
+ Rich contracts (MCP only) additionally provide: descriptor, metadata.transport, metadata.security
32
+
33
+ If a contract file is missing or invalid, discovery fails fast with
34
+ ProtocolConfigurationError. This ensures bootstrap handlers always have
35
+ valid configuration.
36
+
37
+ Registered Handlers:
38
+ - consul: HandlerConsul for HashiCorp Consul service discovery
39
+ - db: HandlerDb for PostgreSQL database operations
40
+ - http: HandlerHttpRest for HTTP/REST protocol operations
41
+ - vault: HandlerVault for HashiCorp Vault secret management
42
+ - mcp: HandlerMCP for Model Context Protocol AI agent integration
43
+
44
+ All handlers are registered with handler_kind="effect" as they perform external I/O
45
+ operations with infrastructure services.
46
+
47
+ See Also:
48
+ - ProtocolContractSource: Protocol definition for handler sources
49
+ - HandlerContractSource: Filesystem-based contract discovery source
50
+ - handler_contract_config_loader: Loads and parses handler contract YAML files
51
+ - util_wiring: Module that previously contained hardcoded handler wiring
52
+ - ModelHandlerDescriptor: Descriptor model for discovered handlers
53
+
54
+ .. versionadded:: 0.6.4
55
+ Created as part of OMN-1087 bootstrap handler registration.
56
+
57
+ .. versionchanged:: 0.6.5
58
+ Added contract loading support. Bootstrap handlers now load and validate
59
+ their contract YAML files during discovery, populating contract_config
60
+ in the descriptor.
61
+ """
62
+
63
+ from __future__ import annotations
64
+
65
+ import logging
66
+ import threading
67
+ import time
68
+ from typing import TypedDict, final
69
+
70
+ from omnibase_core.models.primitives import ModelSemVer
71
+ from omnibase_infra.errors import ProtocolConfigurationError
72
+ from omnibase_infra.models.handlers import (
73
+ LiteralHandlerKind,
74
+ ModelBootstrapHandlerDescriptor,
75
+ ModelContractDiscoveryResult,
76
+ ModelHandlerDescriptor,
77
+ )
78
+ from omnibase_infra.runtime.handler_contract_config_loader import (
79
+ extract_handler_config,
80
+ load_handler_contract_config,
81
+ )
82
+ from omnibase_infra.runtime.protocol_contract_source import ProtocolContractSource
83
+
84
+
85
+ class BootstrapEffectDefinition(TypedDict):
86
+ """Type definition for bootstrap effect node configuration entries.
87
+
88
+ This TypedDict provides compile-time type safety for the hardcoded effect
89
+ definitions, ensuring kind values are correctly typed as LiteralHandlerKind
90
+ rather than generic str. This eliminates the need for type: ignore comments
91
+ when constructing ModelBootstrapHandlerDescriptor instances.
92
+
93
+ Note that handler_class is a required field here, matching the
94
+ ModelBootstrapHandlerDescriptor requirement that bootstrap handlers
95
+ must always specify their implementation class.
96
+
97
+ Attributes:
98
+ handler_id: Unique identifier with "bootstrap." prefix.
99
+ name: Human-readable display name.
100
+ description: Handler purpose description.
101
+ handler_kind: ONEX handler archetype (all are "effect" for I/O handlers).
102
+ handler_class: Fully qualified Python class path for dynamic import.
103
+ input_model: Fully qualified path to input type.
104
+ output_model: Fully qualified path to output type.
105
+ contract_path: Relative path to handler_contract.yaml from repo root.
106
+ """
107
+
108
+ handler_id: str
109
+ name: str
110
+ description: str
111
+ handler_kind: LiteralHandlerKind
112
+ handler_class: str
113
+ input_model: str
114
+ output_model: str
115
+ contract_path: str
116
+
117
+
118
+ # =============================================================================
119
+ # Thread-Safe Model Rebuild Pattern (Deferred Execution)
120
+ # =============================================================================
121
+ #
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.
125
+ #
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
135
+ #
136
+ # WHY THREAD-SAFE:
137
+ # - discover_handlers() may be called concurrently from multiple threads
138
+ # - Unlike module-level code (which Python imports once, thread-safely),
139
+ # 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)
146
+ #
147
+ # See Also:
148
+ # - handler_contract_source.py lines 49-68 for the immediate pattern rationale
149
+ # - OMN-1087 for the ticket tracking this design decision
150
+ # =============================================================================
151
+
152
+ # Lock ensures only one thread performs the rebuild
153
+ _model_rebuild_lock = threading.Lock()
154
+
155
+ # Mutable container to track if model_rebuild() has been called
156
+ # Using a list avoids the need for global statement (PLW0603)
157
+ _model_rebuild_state: list[bool] = [False]
158
+
159
+
160
+ def _ensure_model_rebuilt() -> None:
161
+ """Ensure ModelContractDiscoveryResult has resolved forward references.
162
+
163
+ This must be called before creating ModelContractDiscoveryResult instances.
164
+ It's deferred from module load time to avoid circular import issues when
165
+ this module is imported through the runtime.__init__.py chain.
166
+
167
+ The rebuild resolves the forward reference to ModelHandlerValidationError
168
+ in the validation_errors field of ModelContractDiscoveryResult.
169
+
170
+ Why Deferred (Not Module-Level):
171
+ Unlike HandlerContractSource which uses module-level model_rebuild(),
172
+ this module is imported early in the runtime bootstrap chain before all
173
+ model dependencies are resolved. Deferring the rebuild to first use
174
+ avoids circular import failures.
175
+
176
+ Thread Safety:
177
+ Uses double-checked locking pattern to ensure thread-safe initialization
178
+ while minimizing lock contention after the first successful rebuild.
179
+ This is necessary because discover_handlers() may be called from multiple
180
+ threads, unlike module-level code which Python imports once.
181
+
182
+ See Also:
183
+ handler_contract_source.py for the simpler immediate pattern used when
184
+ import order constraints don't apply.
185
+ """
186
+ # Fast path - already rebuilt (no lock needed)
187
+ if _model_rebuild_state[0]:
188
+ return
189
+
190
+ # Thread-safe initialization with double-checked locking
191
+ with _model_rebuild_lock:
192
+ # Re-check after acquiring lock (another thread may have completed rebuild)
193
+ if _model_rebuild_state[0]:
194
+ return
195
+
196
+ # Import ModelHandlerValidationError here to avoid circular import at module load.
197
+ # This import MUST be in scope when model_rebuild() is called, as Pydantic uses
198
+ # the local namespace to resolve forward references in the validation_errors field.
199
+ from omnibase_infra.models.errors import ModelHandlerValidationError
200
+
201
+ # Rebuild the model to resolve forward references.
202
+ # If this fails, provide a clear error message rather than obscure Pydantic errors.
203
+ try:
204
+ ModelContractDiscoveryResult.model_rebuild()
205
+ except Exception as e:
206
+ raise RuntimeError(
207
+ f"Failed to rebuild ModelContractDiscoveryResult during bootstrap "
208
+ f"initialization. This typically indicates a circular import or missing "
209
+ f"type definition: {e}"
210
+ ) from e
211
+
212
+ # Keep import reference in scope - required for Pydantic forward reference resolution
213
+ _ = ModelHandlerValidationError
214
+ _model_rebuild_state[0] = True
215
+
216
+
217
+ logger = logging.getLogger(__name__)
218
+
219
+ # Source type identifier for bootstrap handlers
220
+ SOURCE_TYPE_BOOTSTRAP = "BOOTSTRAP"
221
+
222
+ # Handler type constants (matching handler_registry.py)
223
+ _HANDLER_TYPE_CONSUL = "consul"
224
+ _HANDLER_TYPE_DATABASE = "db"
225
+ _HANDLER_TYPE_HTTP = "http"
226
+ _HANDLER_TYPE_MCP = "mcp"
227
+ _HANDLER_TYPE_VAULT = "vault"
228
+
229
+ # Bootstrap handler definitions.
230
+ #
231
+ # Each entry contains the metadata needed to create a ModelHandlerDescriptor:
232
+ # handler_id: Unique identifier with "bootstrap." prefix
233
+ # name: Human-readable display name
234
+ # description: Handler purpose description
235
+ # handler_kind: ONEX handler archetype (all are "effect" for I/O handlers)
236
+ # handler_class: Fully qualified Python class path for dynamic import
237
+ # input_model: Fully qualified path to input type (envelope-based handlers use JsonDict)
238
+ # output_model: Fully qualified path to output type (all handlers return ModelHandlerOutput)
239
+ #
240
+ # Design Note (handler_class vs handler_module):
241
+ # ModelHandlerDescriptor uses a single handler_class field with the fully qualified
242
+ # path (e.g., "module.path.ClassName") rather than separate handler_module and
243
+ # handler_class fields. This follows the standard Python import convention and
244
+ # avoids redundancy. The runtime extracts module/class via rsplit(".", 1):
245
+ # module_path, class_name = handler_class.rsplit(".", 1)
246
+ # See: handler_plugin_loader.py::_import_handler_class() for implementation.
247
+ #
248
+ # These handlers are the core infrastructure handlers that support envelope-based
249
+ # routing patterns for external service integration.
250
+ #
251
+ # The BootstrapEffectDefinition TypedDict ensures handler_kind is typed as LiteralHandlerKind,
252
+ # providing compile-time type safety for the hardcoded values.
253
+ _BOOTSTRAP_HANDLER_DEFINITIONS: list[BootstrapEffectDefinition] = [
254
+ {
255
+ "handler_id": f"bootstrap.{_HANDLER_TYPE_CONSUL}",
256
+ "name": "Consul Handler",
257
+ "description": "HashiCorp Consul service discovery handler",
258
+ "handler_kind": "effect",
259
+ "handler_class": "omnibase_infra.handlers.handler_consul.HandlerConsul",
260
+ "input_model": "omnibase_infra.models.types.JsonDict",
261
+ "output_model": "omnibase_core.models.dispatch.ModelHandlerOutput",
262
+ "contract_path": "contracts/handlers/consul/handler_contract.yaml",
263
+ },
264
+ {
265
+ "handler_id": f"bootstrap.{_HANDLER_TYPE_DATABASE}",
266
+ "name": "Database Handler",
267
+ "description": "PostgreSQL database handler",
268
+ "handler_kind": "effect",
269
+ "handler_class": "omnibase_infra.handlers.handler_db.HandlerDb",
270
+ "input_model": "omnibase_infra.models.types.JsonDict",
271
+ "output_model": "omnibase_core.models.dispatch.ModelHandlerOutput",
272
+ "contract_path": "contracts/handlers/db/handler_contract.yaml",
273
+ },
274
+ {
275
+ "handler_id": f"bootstrap.{_HANDLER_TYPE_HTTP}",
276
+ "name": "HTTP Handler",
277
+ "description": "HTTP REST protocol handler",
278
+ "handler_kind": "effect",
279
+ "handler_class": "omnibase_infra.handlers.handler_http.HandlerHttpRest",
280
+ "input_model": "omnibase_infra.models.types.JsonDict",
281
+ "output_model": "omnibase_core.models.dispatch.ModelHandlerOutput",
282
+ "contract_path": "contracts/handlers/http/handler_contract.yaml",
283
+ },
284
+ {
285
+ "handler_id": f"bootstrap.{_HANDLER_TYPE_VAULT}",
286
+ "name": "Vault Handler",
287
+ "description": "HashiCorp Vault secret management handler",
288
+ "handler_kind": "effect",
289
+ "handler_class": "omnibase_infra.handlers.handler_vault.HandlerVault",
290
+ "input_model": "omnibase_infra.models.types.JsonDict",
291
+ "output_model": "omnibase_core.models.dispatch.ModelHandlerOutput",
292
+ "contract_path": "contracts/handlers/vault/handler_contract.yaml",
293
+ },
294
+ {
295
+ "handler_id": f"bootstrap.{_HANDLER_TYPE_MCP}",
296
+ "name": "MCP Handler",
297
+ "description": "Model Context Protocol handler for AI agent integration",
298
+ "handler_kind": "effect",
299
+ "handler_class": "omnibase_infra.handlers.handler_mcp.HandlerMCP",
300
+ "input_model": "omnibase_infra.models.types.JsonDict",
301
+ "output_model": "omnibase_core.models.dispatch.ModelHandlerOutput",
302
+ "contract_path": "src/omnibase_infra/contracts/handlers/mcp/handler_contract.yaml",
303
+ },
304
+ ]
305
+
306
+ # Version for all bootstrap handlers (hardcoded handlers use stable version)
307
+ _BOOTSTRAP_HANDLER_VERSION = ModelSemVer(major=1, minor=0, patch=0)
308
+
309
+
310
+ @final
311
+ class HandlerBootstrapSource(
312
+ ProtocolContractSource
313
+ ): # naming-ok: Handler prefix required by ProtocolHandlerSource convention
314
+ """Handler source that provides hardcoded bootstrap handler descriptors.
315
+
316
+ This class implements ProtocolContractSource by returning predefined handler
317
+ descriptors for core infrastructure handlers. Unlike HandlerContractSource
318
+ which discovers handlers from filesystem contracts, this source provides
319
+ handlers that are essential for the ONEX runtime bootstrap process.
320
+
321
+ Protocol Compliance:
322
+ This class explicitly inherits from ProtocolContractSource and implements
323
+ all required protocol methods: discover_handlers() async method and
324
+ source_type property. Protocol compliance is verified at runtime through
325
+ Python's structural subtyping and enforced by type checkers.
326
+
327
+ Attributes:
328
+ source_type: Returns "BOOTSTRAP" as the source type identifier.
329
+
330
+ Example:
331
+ >>> source = HandlerBootstrapSource()
332
+ >>> result = await source.discover_handlers()
333
+ >>> print(f"Found {len(result.descriptors)} bootstrap handlers")
334
+ Found 4 bootstrap handlers
335
+ >>> for desc in result.descriptors:
336
+ ... 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
341
+
342
+ Performance Characteristics:
343
+ - No filesystem or network I/O required
344
+ - Constant time O(1) discovery (hardcoded definitions)
345
+ - Typical performance: <1ms for all handlers (local dev)
346
+ - Test threshold: 100ms (generous for CI runner variance)
347
+ - Memory: ~500 bytes per handler descriptor
348
+
349
+ .. versionadded:: 0.6.4
350
+ Created as part of OMN-1087 bootstrap handler registration.
351
+ """
352
+
353
+ @property
354
+ def source_type(self) -> str:
355
+ """Return the source type identifier.
356
+
357
+ Returns:
358
+ "BOOTSTRAP" as the source type.
359
+ """
360
+ return SOURCE_TYPE_BOOTSTRAP
361
+
362
+ async def discover_handlers(
363
+ self,
364
+ ) -> ModelContractDiscoveryResult:
365
+ """Discover bootstrap handler descriptors.
366
+
367
+ Returns predefined handler descriptors for core infrastructure handlers.
368
+ Unlike filesystem-based discovery, this method returns hardcoded
369
+ definitions that are essential for ONEX runtime bootstrap.
370
+
371
+ Returns:
372
+ ModelContractDiscoveryResult containing bootstrap handler descriptors.
373
+ The validation_errors list will always be empty since bootstrap
374
+ handlers are hardcoded and validated at development time.
375
+
376
+ Note:
377
+ This method is idempotent and can be called multiple times safely.
378
+ Each call returns the same set of handler descriptors.
379
+
380
+ Implementation Note:
381
+ Uses ModelBootstrapHandlerDescriptor (which requires handler_class)
382
+ for construction validation, ensuring all bootstrap handlers have
383
+ the required handler_class field. The descriptors are instances
384
+ of ModelHandlerDescriptor due to inheritance.
385
+ """
386
+ # Ensure forward references are resolved before creating result
387
+ _ensure_model_rebuilt()
388
+
389
+ start_time = time.perf_counter()
390
+ descriptors: list[ModelHandlerDescriptor] = []
391
+
392
+ logger.debug(
393
+ "Starting bootstrap handler discovery",
394
+ extra={
395
+ "source_type": SOURCE_TYPE_BOOTSTRAP,
396
+ "expected_handler_count": len(_BOOTSTRAP_HANDLER_DEFINITIONS),
397
+ },
398
+ )
399
+
400
+ # Create descriptors from hardcoded definitions
401
+ # Uses ModelBootstrapHandlerDescriptor to enforce handler_class requirement
402
+ for handler_def in _BOOTSTRAP_HANDLER_DEFINITIONS:
403
+ contract_path = handler_def["contract_path"]
404
+ contract_config = None
405
+
406
+ # Load contract configuration if path is specified
407
+ try:
408
+ contract = load_handler_contract_config(
409
+ contract_path,
410
+ handler_def["handler_id"],
411
+ )
412
+ handler_type = handler_def["handler_id"].split(".")[-1]
413
+ # Bootstrap handlers get handler_class from _BOOTSTRAP_HANDLER_DEFINITIONS,
414
+ # not from the contract file. Rich contracts (like MCP) and basic contracts
415
+ # don't include handler_class since it would be redundant with the definition.
416
+ contract_config = extract_handler_config(
417
+ contract, handler_type, require_basic_fields=False
418
+ )
419
+ logger.debug(
420
+ "Loaded contract config for bootstrap handler",
421
+ extra={
422
+ "handler_id": handler_def["handler_id"],
423
+ "contract_path": contract_path,
424
+ "config_keys": list(contract_config.keys()),
425
+ },
426
+ )
427
+ except ProtocolConfigurationError:
428
+ # Fail fast for bootstrap handlers - contracts must exist
429
+ logger.exception(
430
+ "Failed to load contract config for bootstrap handler",
431
+ extra={
432
+ "handler_id": handler_def["handler_id"],
433
+ "contract_path": contract_path,
434
+ },
435
+ )
436
+ raise
437
+
438
+ descriptor = ModelBootstrapHandlerDescriptor(
439
+ handler_id=handler_def["handler_id"],
440
+ name=handler_def["name"],
441
+ version=_BOOTSTRAP_HANDLER_VERSION,
442
+ handler_kind=handler_def["handler_kind"],
443
+ input_model=handler_def["input_model"],
444
+ output_model=handler_def["output_model"],
445
+ description=handler_def["description"],
446
+ handler_class=handler_def["handler_class"],
447
+ contract_path=contract_path,
448
+ contract_config=contract_config,
449
+ )
450
+ descriptors.append(descriptor)
451
+
452
+ logger.debug(
453
+ "Created bootstrap handler descriptor",
454
+ extra={
455
+ "handler_id": descriptor.handler_id,
456
+ "handler_name": descriptor.name,
457
+ "handler_kind": descriptor.handler_kind,
458
+ "contract_path": descriptor.contract_path,
459
+ "has_contract_config": descriptor.contract_config is not None,
460
+ "source_type": SOURCE_TYPE_BOOTSTRAP,
461
+ },
462
+ )
463
+
464
+ # Calculate duration and log results
465
+ duration_seconds = time.perf_counter() - start_time
466
+ self._log_discovery_results(len(descriptors), duration_seconds)
467
+
468
+ return ModelContractDiscoveryResult(
469
+ descriptors=descriptors,
470
+ validation_errors=[], # Bootstrap handlers have no validation errors
471
+ )
472
+
473
+ def _log_discovery_results(
474
+ self,
475
+ discovered_count: int,
476
+ duration_seconds: float,
477
+ ) -> None:
478
+ """Log the discovery results with structured counts and timing.
479
+
480
+ Args:
481
+ discovered_count: Number of successfully discovered handlers.
482
+ duration_seconds: Total discovery duration in seconds.
483
+ """
484
+ # Cap handlers_per_sec at 1M to avoid float("inf") which can cause issues
485
+ # in downstream logging/monitoring systems expecting finite numbers.
486
+ # A value of 1M represents "effectively instant" discovery.
487
+ if duration_seconds > 0:
488
+ handlers_per_sec = discovered_count / duration_seconds
489
+ elif discovered_count > 0:
490
+ handlers_per_sec = 1_000_000.0 # Cap for instant discovery
491
+ else:
492
+ handlers_per_sec = 0.0
493
+
494
+ logger.info(
495
+ "Bootstrap handler discovery completed: "
496
+ "discovered_handler_count=%d, "
497
+ "duration_seconds=%.6f, handlers_per_second=%.1f",
498
+ discovered_count,
499
+ duration_seconds,
500
+ handlers_per_sec,
501
+ extra={
502
+ "discovered_handler_count": discovered_count,
503
+ "validation_failure_count": 0,
504
+ "source_type": SOURCE_TYPE_BOOTSTRAP,
505
+ "duration_seconds": duration_seconds,
506
+ "handlers_per_second": handlers_per_sec,
507
+ },
508
+ )
509
+
510
+
511
+ __all__ = [
512
+ "HandlerBootstrapSource",
513
+ "SOURCE_TYPE_BOOTSTRAP",
514
+ ]