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
@@ -53,6 +53,7 @@ Related Tickets:
53
53
  from __future__ import annotations
54
54
 
55
55
  import logging
56
+ import re
56
57
  import time
57
58
  from datetime import datetime, timedelta
58
59
  from typing import TYPE_CHECKING
@@ -237,6 +238,39 @@ class HandlerNodeIntrospected:
237
238
  """Check if HandlerConsul is configured for Consul registration."""
238
239
  return self._consul_handler is not None
239
240
 
241
+ def _sanitize_tool_name(self, name: str) -> str:
242
+ """Sanitize tool name for use in Consul tags.
243
+
244
+ Converts free-form text (like descriptions) into stable, Consul-safe
245
+ identifiers. This ensures consistent service discovery matching.
246
+
247
+ Transformation rules:
248
+ 1. Convert to lowercase
249
+ 2. Replace non-alphanumeric characters with dashes
250
+ 3. Collapse multiple consecutive dashes into one
251
+ 4. Remove leading/trailing dashes
252
+ 5. Truncate to 63 characters (Consul tag limit)
253
+
254
+ Args:
255
+ name: Raw tool name or description text.
256
+
257
+ Returns:
258
+ Sanitized string suitable for Consul tags (lowercase, alphanumeric
259
+ with dashes, max 63 chars).
260
+
261
+ Example:
262
+ >>> handler._sanitize_tool_name("My Cool Tool (v2.0)")
263
+ 'my-cool-tool-v2-0'
264
+ >>> handler._sanitize_tool_name(" Spaces & Special!Chars ")
265
+ 'spaces-special-chars'
266
+ """
267
+ # Replace non-alphanumeric with dash, lowercase
268
+ sanitized = re.sub(r"[^a-zA-Z0-9]+", "-", name.lower())
269
+ # Remove leading/trailing dashes
270
+ sanitized = sanitized.strip("-")
271
+ # Truncate to Consul tag limit (63 chars is common limit for DNS labels)
272
+ return sanitized[:63]
273
+
240
274
  async def handle(
241
275
  self,
242
276
  envelope: ModelEventEnvelope[ModelNodeIntrospectionEvent],
@@ -422,11 +456,19 @@ class HandlerNodeIntrospected:
422
456
 
423
457
  # Register with Consul for service discovery (dual registration)
424
458
  # This happens AFTER PostgreSQL persistence (source of truth)
459
+ # Pass MCP config from capabilities (if present) for MCP tag generation
460
+ mcp_config = (
461
+ event.declared_capabilities.mcp
462
+ if event.declared_capabilities is not None
463
+ else None
464
+ )
425
465
  await self._register_with_consul(
426
466
  node_id=node_id,
427
467
  node_type=event.node_type.value,
428
468
  endpoints=event.endpoints,
429
469
  correlation_id=correlation_id,
470
+ mcp_config=mcp_config,
471
+ node_name=event.metadata.description if event.metadata else None,
430
472
  )
431
473
 
432
474
  logger.info(
@@ -458,6 +500,8 @@ class HandlerNodeIntrospected:
458
500
  node_type: str,
459
501
  endpoints: dict[str, str] | None,
460
502
  correlation_id: UUID,
503
+ mcp_config: object | None = None,
504
+ node_name: str | None = None,
461
505
  ) -> None:
462
506
  """Register node with Consul for service discovery.
463
507
 
@@ -465,8 +509,16 @@ class HandlerNodeIntrospected:
465
509
  - service_name: `onex-{node_type}` (ONEX convention for service discovery)
466
510
  - service_id: `onex-{node_type}-{node_id}` (unique identifier)
467
511
  - tags: [`onex`, `node-type:{node_type}`]
512
+ - MCP tags (orchestrators only): [`mcp-enabled`, `mcp-tool:{tool_name}`]
468
513
  - address/port: Extracted from endpoints if available
469
514
 
515
+ MCP Tags:
516
+ MCP tags are added ONLY when:
517
+ 1. node_type is "orchestrator"
518
+ 2. mcp_config is provided with expose=True
519
+
520
+ This ensures only orchestrators can be exposed as MCP tools.
521
+
470
522
  This method is idempotent - re-registering the same service_id updates it.
471
523
  Errors are logged but not propagated (PostgreSQL is source of truth).
472
524
 
@@ -475,6 +527,8 @@ class HandlerNodeIntrospected:
475
527
  node_type: ONEX node type (effect, compute, reducer, orchestrator).
476
528
  endpoints: Optional dict of endpoint URLs from introspection event.
477
529
  correlation_id: Correlation ID for tracing.
530
+ mcp_config: Optional MCP configuration from capabilities.
531
+ node_name: Optional node name for MCP tool naming.
478
532
  """
479
533
  if self._consul_handler is None:
480
534
  logger.debug(
@@ -554,11 +608,42 @@ class HandlerNodeIntrospected:
554
608
  },
555
609
  )
556
610
 
611
+ # Build base tags
612
+ tags: list[str] = ["onex", f"node-type:{node_type}"]
613
+
614
+ # Add MCP tags for orchestrators with MCP config enabled
615
+ # MCP tags are ONLY added when:
616
+ # 1. node_type is "orchestrator" (enforces orchestrator-only rule)
617
+ # 2. mcp_config exists with expose=True
618
+ if node_type == "orchestrator" and mcp_config is not None:
619
+ # Check if mcp_config has expose attribute and it's True
620
+ mcp_expose = getattr(mcp_config, "expose", False)
621
+ if mcp_expose:
622
+ # Get tool name from mcp_config or fall back to node_name
623
+ mcp_tool_name_raw = getattr(mcp_config, "tool_name", None)
624
+ if not mcp_tool_name_raw:
625
+ # Fall back to node_name (description), then service_name
626
+ mcp_tool_name_raw = node_name or service_name
627
+ # Sanitize tool name for Consul tag safety
628
+ # node_name comes from metadata.description which can be free-form text
629
+ mcp_tool_name = self._sanitize_tool_name(mcp_tool_name_raw)
630
+ tags.extend(["mcp-enabled", f"mcp-tool:{mcp_tool_name}"])
631
+
632
+ logger.info(
633
+ "Adding MCP tags to Consul registration",
634
+ extra={
635
+ "node_id": str(node_id),
636
+ "tool_name": mcp_tool_name,
637
+ "tool_name_raw": mcp_tool_name_raw,
638
+ "correlation_id": str(correlation_id),
639
+ },
640
+ )
641
+
557
642
  # Build Consul registration payload
558
643
  consul_payload: dict[str, object] = {
559
644
  "name": service_name,
560
645
  "service_id": service_id,
561
- "tags": ["onex", f"node-type:{node_type}"],
646
+ "tags": tags,
562
647
  }
563
648
  if address:
564
649
  consul_payload["address"] = address
@@ -392,7 +392,7 @@ class IntrospectionEventRouter:
392
392
  correlation_id=raw_envelope.correlation_id or callback_correlation_id,
393
393
  source_tool=raw_envelope.source_tool,
394
394
  target_tool=raw_envelope.target_tool,
395
- metadata=_normalize_metadata(raw_envelope.metadata),
395
+ metadata=_normalize_metadata(raw_envelope.metadata), # type: ignore[arg-type]
396
396
  priority=raw_envelope.priority,
397
397
  timeout_seconds=raw_envelope.timeout_seconds,
398
398
  trace_id=raw_envelope.trace_id,
@@ -419,7 +419,7 @@ class IntrospectionEventRouter:
419
419
  },
420
420
  )
421
421
  dispatcher_start_time = time.time()
422
- result = await self._dispatcher.handle(event_envelope)
422
+ result = await self._dispatcher.handle(event_envelope) # type: ignore[arg-type]
423
423
  dispatcher_duration = time.time() - dispatcher_start_time
424
424
 
425
425
  if result.is_successful():
@@ -441,7 +441,7 @@ class IntrospectionEventRouter:
441
441
  if result.output_events:
442
442
  for output_event in result.output_events:
443
443
  # Wrap output event in envelope
444
- output_envelope = ModelEventEnvelope(
444
+ output_envelope = ModelEventEnvelope( # type: ignore[var-annotated]
445
445
  payload=output_event,
446
446
  correlation_id=event_envelope.correlation_id,
447
447
  envelope_timestamp=datetime.now(UTC),
@@ -13,7 +13,7 @@ Handler Wiring (from contract.yaml):
13
13
  - ModelNodeHeartbeatEvent -> HandlerNodeHeartbeat
14
14
 
15
15
  Handler Implementation:
16
- All handlers implement ProtocolMessageHandler directly with:
16
+ All handlers implement ProtocolHandler directly with:
17
17
  - handler_id, category, message_types, node_kind properties
18
18
  - handle(envelope) -> ModelHandlerOutput signature
19
19
 
@@ -88,7 +88,7 @@ from __future__ import annotations
88
88
  import importlib
89
89
  import logging
90
90
  from pathlib import Path
91
- from typing import TYPE_CHECKING
91
+ from typing import TYPE_CHECKING, cast
92
92
 
93
93
  from omnibase_core.services.service_handler_registry import ServiceHandlerRegistry
94
94
  from omnibase_infra.enums import EnumInfraTransportType
@@ -96,6 +96,7 @@ from omnibase_infra.errors import ModelInfraErrorContext, ProtocolConfigurationE
96
96
  from omnibase_infra.runtime.contract_loaders import (
97
97
  load_handler_class_info_from_contract,
98
98
  )
99
+ from omnibase_spi.protocols.handlers import ProtocolHandler
99
100
 
100
101
  logger = logging.getLogger(__name__)
101
102
 
@@ -110,14 +111,14 @@ ALLOWED_NAMESPACES: tuple[str, ...] = (
110
111
 
111
112
 
112
113
  def _validate_handler_protocol(handler: object) -> tuple[bool, list[str]]:
113
- """Validate handler implements ProtocolMessageHandler via duck typing.
114
+ """Validate handler implements ProtocolHandler via duck typing.
114
115
 
115
116
  Uses duck typing to verify the handler has the required properties and
116
- methods for ProtocolMessageHandler compliance. Per ONEX conventions,
117
+ methods for ProtocolHandler compliance. Per ONEX conventions,
117
118
  protocol compliance is verified via structural typing rather than
118
119
  isinstance checks.
119
120
 
120
- Protocol Requirements (ProtocolMessageHandler):
121
+ Protocol Requirements (ProtocolHandler):
121
122
  - handler_id (property): Unique identifier string
122
123
  - category (property): EnumMessageCategory value
123
124
  - message_types (property): set[str] of message type names
@@ -492,7 +493,7 @@ class RegistryInfraNodeRegistrationOrchestrator:
492
493
  context=ctx,
493
494
  ) from e
494
495
 
495
- # Validate handler implements ProtocolMessageHandler
496
+ # Validate handler implements ProtocolHandler
496
497
  is_valid, missing = _validate_handler_protocol(handler_instance)
497
498
  if not is_valid:
498
499
  ctx = ModelInfraErrorContext.with_correlation(
@@ -501,7 +502,7 @@ class RegistryInfraNodeRegistrationOrchestrator:
501
502
  target_name=handler_class_name,
502
503
  )
503
504
  raise ProtocolConfigurationError(
504
- f"Handler '{handler_class_name}' does not implement ProtocolMessageHandler. "
505
+ f"Handler '{handler_class_name}' does not implement ProtocolHandler. "
505
506
  f"Missing required members: {', '.join(missing)}. "
506
507
  f"Handlers must have: handler_id, category, message_types, node_kind properties "
507
508
  f"and handle(envelope) method. "
@@ -510,7 +511,7 @@ class RegistryInfraNodeRegistrationOrchestrator:
510
511
  )
511
512
 
512
513
  # Register handler
513
- registry.register_handler(handler_instance)
514
+ registry.register_handler(handler_instance) # type: ignore[arg-type]
514
515
  logger.debug(
515
516
  "Registered handler from contract: %s",
516
517
  handler_class_name,
@@ -44,6 +44,7 @@ import logging
44
44
  from typing import TYPE_CHECKING, TypedDict, cast
45
45
  from uuid import UUID
46
46
 
47
+ from omnibase_core.enums import EnumInjectionScope
47
48
  from omnibase_infra.enums import EnumInfraTransportType
48
49
  from omnibase_infra.errors import (
49
50
  ContainerValidationError,
@@ -269,12 +270,14 @@ async def wire_registration_dispatchers(
269
270
  # because MessageDispatchEngine.register_dispatcher() takes a callable
270
271
 
271
272
  # 3a. Register DispatcherNodeIntrospected
273
+ # Note: node_kind is NOT passed to register_dispatcher because the dispatcher's
274
+ # handle() method doesn't accept ModelDispatchContext - it handles time injection
275
+ # internally. The node_kind property is informational only.
272
276
  engine.register_dispatcher(
273
277
  dispatcher_id=dispatcher_introspected.dispatcher_id,
274
278
  dispatcher=dispatcher_introspected.handle,
275
279
  category=dispatcher_introspected.category,
276
280
  message_types=dispatcher_introspected.message_types,
277
- node_kind=dispatcher_introspected.node_kind,
278
281
  )
279
282
  dispatchers_registered.append(dispatcher_introspected.dispatcher_id)
280
283
 
@@ -284,7 +287,6 @@ async def wire_registration_dispatchers(
284
287
  dispatcher=dispatcher_runtime_tick.handle,
285
288
  category=dispatcher_runtime_tick.category,
286
289
  message_types=dispatcher_runtime_tick.message_types,
287
- node_kind=dispatcher_runtime_tick.node_kind,
288
290
  )
289
291
  dispatchers_registered.append(dispatcher_runtime_tick.dispatcher_id)
290
292
 
@@ -294,7 +296,6 @@ async def wire_registration_dispatchers(
294
296
  dispatcher=dispatcher_acked.handle,
295
297
  category=dispatcher_acked.category,
296
298
  message_types=dispatcher_acked.message_types,
297
- node_kind=dispatcher_acked.node_kind,
298
299
  )
299
300
  dispatchers_registered.append(dispatcher_acked.dispatcher_id)
300
301
 
@@ -409,7 +410,7 @@ async def wire_registration_handlers(
409
410
  ContainerWiringError: If service registration fails.
410
411
 
411
412
  Note:
412
- Services are registered with scope="global" and may conflict if multiple
413
+ Services are registered with scope=EnumInjectionScope.GLOBAL and may conflict if multiple
413
414
  plugins register the same interface type. This is acceptable for the
414
415
  Registration domain as these handlers are singletons by design. If you
415
416
  need to register multiple implementations of the same interface, use
@@ -440,7 +441,7 @@ async def wire_registration_handlers(
440
441
  await container.service_registry.register_instance(
441
442
  interface=ProjectionReaderRegistration,
442
443
  instance=projection_reader,
443
- scope="global",
444
+ scope=EnumInjectionScope.GLOBAL,
444
445
  metadata={
445
446
  "description": "Registration projection reader",
446
447
  "version": str(semver_default),
@@ -453,7 +454,7 @@ async def wire_registration_handlers(
453
454
  await container.service_registry.register_instance(
454
455
  interface=ProjectorShell,
455
456
  instance=projector,
456
- scope="global",
457
+ scope=EnumInjectionScope.GLOBAL,
457
458
  metadata={
458
459
  "description": "Registration projector",
459
460
  "version": str(semver_default),
@@ -470,7 +471,7 @@ async def wire_registration_handlers(
470
471
  await container.service_registry.register_instance(
471
472
  interface=HandlerNodeIntrospected,
472
473
  instance=handler_introspected,
473
- scope="global",
474
+ scope=EnumInjectionScope.GLOBAL,
474
475
  metadata={
475
476
  "description": "Handler for NodeIntrospectionEvent",
476
477
  "version": str(semver_default),
@@ -485,7 +486,7 @@ async def wire_registration_handlers(
485
486
  await container.service_registry.register_instance(
486
487
  interface=HandlerRuntimeTick,
487
488
  instance=handler_runtime_tick,
488
- scope="global",
489
+ scope=EnumInjectionScope.GLOBAL,
489
490
  metadata={
490
491
  "description": "Handler for RuntimeTick",
491
492
  "version": str(semver_default),
@@ -501,7 +502,7 @@ async def wire_registration_handlers(
501
502
  await container.service_registry.register_instance(
502
503
  interface=HandlerNodeRegistrationAcked,
503
504
  instance=handler_acked,
504
- scope="global",
505
+ scope=EnumInjectionScope.GLOBAL,
505
506
  metadata={
506
507
  "description": "Handler for NodeRegistrationAcked",
507
508
  "version": str(semver_default),
@@ -579,7 +580,7 @@ async def get_projection_reader_from_container(
579
580
  _validate_service_registry(container, "resolve ProjectionReaderRegistration")
580
581
  try:
581
582
  return cast(
582
- ProjectionReaderRegistration,
583
+ "ProjectionReaderRegistration",
583
584
  await container.service_registry.resolve_service(
584
585
  ProjectionReaderRegistration
585
586
  ),
@@ -622,7 +623,7 @@ async def get_handler_node_introspected_from_container(
622
623
  _validate_service_registry(container, "resolve HandlerNodeIntrospected")
623
624
  try:
624
625
  return cast(
625
- HandlerNodeIntrospected,
626
+ "HandlerNodeIntrospected",
626
627
  await container.service_registry.resolve_service(HandlerNodeIntrospected),
627
628
  )
628
629
  except Exception as e:
@@ -663,7 +664,7 @@ async def get_handler_runtime_tick_from_container(
663
664
  _validate_service_registry(container, "resolve HandlerRuntimeTick")
664
665
  try:
665
666
  return cast(
666
- HandlerRuntimeTick,
667
+ "HandlerRuntimeTick",
667
668
  await container.service_registry.resolve_service(HandlerRuntimeTick),
668
669
  )
669
670
  except Exception as e:
@@ -704,7 +705,7 @@ async def get_handler_node_registration_acked_from_container(
704
705
  _validate_service_registry(container, "resolve HandlerNodeRegistrationAcked")
705
706
  try:
706
707
  return cast(
707
- HandlerNodeRegistrationAcked,
708
+ "HandlerNodeRegistrationAcked",
708
709
  await container.service_registry.resolve_service(
709
710
  HandlerNodeRegistrationAcked
710
711
  ),
@@ -15,11 +15,6 @@
15
15
  name: "node_registration_storage_effect"
16
16
  contract_name: "node_registration_storage_effect"
17
17
  node_name: "node_registration_storage_effect"
18
- # Version (semantic versioning)
19
- version:
20
- major: 1
21
- minor: 0
22
- patch: 0
23
18
  contract_version:
24
19
  major: 1
25
20
  minor: 0
@@ -17,6 +17,12 @@ Related:
17
17
  - NodeRegistrationStorageEffect: Effect node that uses these dependencies
18
18
  - ProtocolRegistrationPersistence: Protocol for storage backends
19
19
  - ModelONEXContainer: ONEX dependency injection container
20
+
21
+ Note:
22
+ This registry uses a module-level dict for handler storage because the
23
+ ServiceRegistry in omnibase_core v1.0 doesn't support dict-style access
24
+ or string-keyed multi-handler routing. The handlers are still validated
25
+ against the protocol but stored separately.
20
26
  """
21
27
 
22
28
  from __future__ import annotations
@@ -31,6 +37,12 @@ if TYPE_CHECKING:
31
37
 
32
38
  __all__ = ["RegistryInfraRegistrationStorage"]
33
39
 
40
+ # Module-level storage for handlers and metadata
41
+ # ServiceRegistry in v1.0 doesn't support dict-style access needed for
42
+ # multi-handler routing (e.g., "postgresql", "mock" handler types)
43
+ _HANDLER_STORAGE: dict[str, object] = {}
44
+ _PROTOCOL_METADATA: dict[str, dict[str, object]] = {}
45
+
34
46
 
35
47
  class RegistryInfraRegistrationStorage:
36
48
  """Registry for registration storage node dependencies.
@@ -72,14 +84,18 @@ class RegistryInfraRegistrationStorage:
72
84
  DEFAULT_HANDLER_TYPE = "postgresql"
73
85
 
74
86
  @staticmethod
75
- def register(container: ModelONEXContainer) -> None:
87
+ def register(_container: ModelONEXContainer) -> None:
76
88
  """Register registration storage dependencies with the container.
77
89
 
78
90
  Registers the protocol key for later handler binding. This method
79
91
  sets up the infrastructure but does not bind a specific handler.
80
92
 
81
93
  Args:
82
- container: ONEX dependency injection container.
94
+ _container: ONEX dependency injection container. Currently unused
95
+ because ServiceRegistry v1.0 doesn't support dict-style access
96
+ for multi-handler routing. The parameter is retained for API
97
+ consistency with other registry methods and future migration
98
+ when ServiceRegistry supports the required access patterns.
83
99
 
84
100
  Example:
85
101
  >>> from omnibase_core.models.container import ModelONEXContainer
@@ -88,20 +104,19 @@ class RegistryInfraRegistrationStorage:
88
104
  """
89
105
  # Register protocol metadata for discovery
90
106
  # Actual handler binding happens via register_handler()
91
- if container.service_registry is not None:
92
- container.service_registry[
93
- RegistryInfraRegistrationStorage.PROTOCOL_KEY
94
- ] = {
95
- "protocol": "ProtocolRegistrationPersistence",
96
- "module": "omnibase_infra.nodes.node_registration_storage_effect.protocols",
97
- "description": "Protocol for registration storage backends",
98
- "pluggable": True,
99
- "implementations": ["postgresql", "mock"],
100
- }
107
+ # Note: Uses module-level storage since ServiceRegistry v1.0 doesn't
108
+ # support dict-style access for multi-handler routing
109
+ _PROTOCOL_METADATA[RegistryInfraRegistrationStorage.PROTOCOL_KEY] = {
110
+ "protocol": "ProtocolRegistrationPersistence",
111
+ "module": "omnibase_infra.nodes.node_registration_storage_effect.protocols",
112
+ "description": "Protocol for registration storage backends",
113
+ "pluggable": True,
114
+ "implementations": ["postgresql", "mock"],
115
+ }
101
116
 
102
117
  @staticmethod
103
118
  def register_handler(
104
- container: ModelONEXContainer,
119
+ _container: ModelONEXContainer,
105
120
  handler: ProtocolRegistrationPersistence,
106
121
  ) -> None:
107
122
  """Register a specific storage handler with the container.
@@ -110,7 +125,11 @@ class RegistryInfraRegistrationStorage:
110
125
  The handler must implement ProtocolRegistrationPersistence.
111
126
 
112
127
  Args:
113
- container: ONEX dependency injection container.
128
+ _container: ONEX dependency injection container. Currently unused
129
+ because ServiceRegistry v1.0 doesn't support dict-style access
130
+ for multi-handler routing. The parameter is retained for API
131
+ consistency and future migration when ServiceRegistry supports
132
+ the required access patterns.
114
133
  handler: Handler implementation to register.
115
134
 
116
135
  Raises:
@@ -143,32 +162,35 @@ class RegistryInfraRegistrationStorage:
143
162
  f"got {type(handler).__name__}"
144
163
  )
145
164
 
146
- if container.service_registry is None:
147
- return
148
-
165
+ # Note: Uses module-level storage since ServiceRegistry v1.0 doesn't
166
+ # support dict-style access for multi-handler routing
149
167
  handler_key = (
150
168
  f"{RegistryInfraRegistrationStorage.PROTOCOL_KEY}.{handler.handler_type}"
151
169
  )
152
- container.service_registry[handler_key] = handler
170
+ _HANDLER_STORAGE[handler_key] = handler
153
171
 
154
172
  # Also register as default if it matches the default type
155
173
  if (
156
174
  handler.handler_type
157
175
  == RegistryInfraRegistrationStorage.DEFAULT_HANDLER_TYPE
158
176
  ):
159
- container.service_registry[
177
+ _HANDLER_STORAGE[
160
178
  RegistryInfraRegistrationStorage.PROTOCOL_KEY + ".default"
161
179
  ] = handler
162
180
 
163
181
  @staticmethod
164
182
  def get_handler(
165
- container: ModelONEXContainer,
183
+ _container: ModelONEXContainer,
166
184
  handler_type: str | None = None,
167
185
  ) -> ProtocolRegistrationPersistence | None:
168
186
  """Retrieve a registered storage handler from the container.
169
187
 
170
188
  Args:
171
- container: ONEX dependency injection container.
189
+ _container: ONEX dependency injection container. Currently unused
190
+ because ServiceRegistry v1.0 doesn't support dict-style access
191
+ for multi-handler routing. The parameter is retained for API
192
+ consistency and future migration when ServiceRegistry supports
193
+ the required access patterns.
172
194
  handler_type: Specific handler type to retrieve. If None, returns default.
173
195
 
174
196
  Returns:
@@ -180,9 +202,8 @@ class RegistryInfraRegistrationStorage:
180
202
  ... handler_type="postgresql",
181
203
  ... )
182
204
  """
183
- if container.service_registry is None:
184
- return None
185
-
205
+ # Note: Uses module-level storage since ServiceRegistry v1.0 doesn't
206
+ # support dict-style access for multi-handler routing
186
207
  if handler_type is not None:
187
208
  handler_key = (
188
209
  f"{RegistryInfraRegistrationStorage.PROTOCOL_KEY}.{handler_type}"
@@ -190,5 +211,5 @@ class RegistryInfraRegistrationStorage:
190
211
  else:
191
212
  handler_key = RegistryInfraRegistrationStorage.PROTOCOL_KEY + ".default"
192
213
 
193
- result = container.service_registry.get(handler_key)
214
+ result = _HANDLER_STORAGE.get(handler_key)
194
215
  return cast("ProtocolRegistrationPersistence | None", result)
@@ -12,11 +12,6 @@
12
12
  name: "node_registry_effect"
13
13
  contract_name: "node_registry_effect"
14
14
  node_name: "node_registry_effect"
15
- # Version (semantic versioning)
16
- version:
17
- major: 1
18
- minor: 1
19
- patch: 0
20
15
  contract_version:
21
16
  major: 1
22
17
  minor: 1
@@ -41,6 +41,7 @@ import time
41
41
  from typing import TYPE_CHECKING, Protocol, runtime_checkable
42
42
  from uuid import UUID
43
43
 
44
+ from omnibase_core.models.primitives import ModelSemVer
44
45
  from omnibase_infra.enums import EnumBackendType
45
46
  from omnibase_infra.errors import (
46
47
  InfraAuthenticationError,
@@ -80,7 +81,7 @@ class ProtocolPartialRetryRequest(Protocol):
80
81
 
81
82
  node_id: UUID
82
83
  node_type: EnumNodeKind
83
- node_version: str
84
+ node_version: ModelSemVer
84
85
  target_backend: EnumBackendType
85
86
  idempotency_key: str | None
86
87
  service_name: str | None
@@ -40,6 +40,7 @@ Related:
40
40
 
41
41
  from __future__ import annotations
42
42
 
43
+ import logging
43
44
  from typing import TYPE_CHECKING
44
45
 
45
46
  from omnibase_infra.errors import ProtocolConfigurationError
@@ -50,6 +51,8 @@ if TYPE_CHECKING:
50
51
  ProtocolDiscoveryOperations,
51
52
  )
52
53
 
54
+ logger = logging.getLogger(__name__)
55
+
53
56
 
54
57
  class RegistryInfraServiceDiscovery:
55
58
  """Registry for service discovery node dependencies.
@@ -59,11 +62,11 @@ class RegistryInfraServiceDiscovery:
59
62
  registration and explicit handler configuration.
60
63
 
61
64
  API Pattern Note:
62
- This registry uses ``container.register_factory()`` and
63
- ``container.register_instance()`` for protocol-based type-safe DI
64
- resolution. This differs from RegistryInfraRegistrationStorage which
65
- uses ``container.service_registry[key]`` dict access for multi-handler
66
- routing by type string (e.g., "postgresql", "mock").
65
+ This registry uses ``container.service_registry.register_instance()``
66
+ for protocol-based type-safe DI resolution. This differs from
67
+ RegistryInfraRegistrationStorage which uses a module-level dict for
68
+ multi-handler routing by type string (e.g., "postgresql", "mock"),
69
+ as ServiceRegistry does not support dict-style indexed access.
67
70
 
68
71
  The different patterns serve different purposes:
69
72
  - Protocol-based registration: Single handler per protocol type
@@ -113,21 +116,18 @@ class RegistryInfraServiceDiscovery:
113
116
  if container.service_registry is None:
114
117
  return
115
118
 
116
- # Import here to avoid circular imports
117
- from omnibase_infra.nodes.node_service_discovery_effect.protocols import (
118
- ProtocolDiscoveryOperations,
119
- )
120
-
121
- # Register protocol with lazy resolution
122
- # The actual implementation is determined at resolution time
123
- # based on configuration (CONSUL_AGENT_URL, K8S_NAMESPACE, etc.)
124
- container.register_factory(
125
- ProtocolDiscoveryOperations,
126
- RegistryInfraServiceDiscovery._create_handler_from_config,
119
+ # NOTE: Factory registration (register_factory) is not implemented in
120
+ # omnibase_core v1.0. This method provides no-op registration for forward
121
+ # compatibility. Use register_with_handler() to explicitly provide a
122
+ # pre-configured handler instance.
123
+ logger.debug(
124
+ "Service discovery factory registration skipped - "
125
+ "factory registration not implemented in v1.0. "
126
+ "Use register_with_handler() to register an explicit handler instance."
127
127
  )
128
128
 
129
129
  @staticmethod
130
- def register_with_handler(
130
+ async def register_with_handler(
131
131
  container: ModelONEXContainer,
132
132
  handler: ProtocolDiscoveryOperations,
133
133
  ) -> None:
@@ -146,12 +146,13 @@ class RegistryInfraServiceDiscovery:
146
146
  Example:
147
147
  >>> container = ModelONEXContainer()
148
148
  >>> handler = HandlerServiceDiscoveryConsul(config)
149
- >>> RegistryInfraServiceDiscovery.register_with_handler(
149
+ >>> await RegistryInfraServiceDiscovery.register_with_handler(
150
150
  ... container,
151
151
  ... handler=handler,
152
152
  ... )
153
153
  """
154
154
  # Import at runtime for isinstance check (protocol is @runtime_checkable)
155
+ from omnibase_core.enums import EnumInjectionScope
155
156
  from omnibase_infra.nodes.node_service_discovery_effect.protocols import (
156
157
  ProtocolDiscoveryOperations,
157
158
  )
@@ -174,7 +175,11 @@ class RegistryInfraServiceDiscovery:
174
175
  if container.service_registry is None:
175
176
  return
176
177
 
177
- container.register_instance(ProtocolDiscoveryOperations, handler)
178
+ await container.service_registry.register_instance(
179
+ interface=ProtocolDiscoveryOperations, # type: ignore[type-abstract]
180
+ instance=handler,
181
+ scope=EnumInjectionScope.GLOBAL,
182
+ )
178
183
 
179
184
  @staticmethod
180
185
  def _create_handler_from_config(