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
|
@@ -207,7 +207,9 @@ from typing import TYPE_CHECKING, ClassVar, TypedDict, cast
|
|
|
207
207
|
from uuid import UUID, uuid4
|
|
208
208
|
|
|
209
209
|
from omnibase_core.enums import EnumNodeKind
|
|
210
|
+
from omnibase_core.models.events.model_event_envelope import ModelEventEnvelope
|
|
210
211
|
from omnibase_core.models.primitives.model_semver import ModelSemVer
|
|
212
|
+
from omnibase_infra.capabilities import ContractCapabilityExtractor
|
|
211
213
|
from omnibase_infra.enums import EnumInfraTransportType, EnumIntrospectionReason
|
|
212
214
|
from omnibase_infra.errors import ModelInfraErrorContext, ProtocolConfigurationError
|
|
213
215
|
from omnibase_infra.models.discovery import (
|
|
@@ -227,8 +229,11 @@ from omnibase_infra.models.registration.model_node_introspection_event import (
|
|
|
227
229
|
)
|
|
228
230
|
|
|
229
231
|
if TYPE_CHECKING:
|
|
232
|
+
from omnibase_core.models.contracts import ModelContractBase
|
|
230
233
|
from omnibase_core.protocols.event_bus.protocol_event_bus import ProtocolEventBus
|
|
231
|
-
from
|
|
234
|
+
from omnibase_core.protocols.event_bus.protocol_event_message import (
|
|
235
|
+
ProtocolEventMessage,
|
|
236
|
+
)
|
|
232
237
|
|
|
233
238
|
logger = logging.getLogger(__name__)
|
|
234
239
|
|
|
@@ -243,6 +248,9 @@ PERF_THRESHOLD_DISCOVER_CAPABILITIES_MS = 30.0
|
|
|
243
248
|
PERF_THRESHOLD_GET_INTROSPECTION_DATA_MS = 50.0
|
|
244
249
|
PERF_THRESHOLD_CACHE_HIT_MS = 1.0
|
|
245
250
|
|
|
251
|
+
# Module-level capability extractor instance (stateless, can be shared)
|
|
252
|
+
_CAPABILITY_EXTRACTOR = ContractCapabilityExtractor()
|
|
253
|
+
|
|
246
254
|
|
|
247
255
|
class PerformanceMetricsCacheDict(TypedDict, total=False):
|
|
248
256
|
"""TypedDict for JSON-serialized ModelIntrospectionPerformanceMetrics.
|
|
@@ -455,6 +463,7 @@ class MixinNodeIntrospection:
|
|
|
455
463
|
_introspection_event_bus: ProtocolEventBus | None
|
|
456
464
|
_introspection_version: str
|
|
457
465
|
_introspection_start_time: float | None
|
|
466
|
+
_introspection_contract: ModelContractBase | None
|
|
458
467
|
|
|
459
468
|
# Capability discovery configuration
|
|
460
469
|
_introspection_operation_keywords: frozenset[str]
|
|
@@ -647,6 +656,9 @@ class MixinNodeIntrospection:
|
|
|
647
656
|
self._heartbeat_topic = config.heartbeat_topic
|
|
648
657
|
self._request_introspection_topic = config.request_introspection_topic
|
|
649
658
|
|
|
659
|
+
# Contract for capability extraction (may be None for legacy nodes)
|
|
660
|
+
self._introspection_contract = config.contract
|
|
661
|
+
|
|
650
662
|
# State
|
|
651
663
|
self._introspection_cache = None
|
|
652
664
|
self._introspection_cached_at = None
|
|
@@ -1335,6 +1347,14 @@ class MixinNodeIntrospection:
|
|
|
1335
1347
|
# Fallback to 1.0.0 if version parsing fails
|
|
1336
1348
|
node_version = ModelSemVer(major=1, minor=0, patch=0)
|
|
1337
1349
|
|
|
1350
|
+
# Extract contract capabilities if contract is available
|
|
1351
|
+
# This is automatic and non-skippable when contract is provided
|
|
1352
|
+
contract_capabilities = None
|
|
1353
|
+
if self._introspection_contract is not None:
|
|
1354
|
+
contract_capabilities = _CAPABILITY_EXTRACTOR.extract(
|
|
1355
|
+
self._introspection_contract
|
|
1356
|
+
)
|
|
1357
|
+
|
|
1338
1358
|
# Create event with performance metrics (metrics is already Pydantic model)
|
|
1339
1359
|
event = ModelNodeIntrospectionEvent(
|
|
1340
1360
|
node_id=node_id_uuid,
|
|
@@ -1342,6 +1362,7 @@ class MixinNodeIntrospection:
|
|
|
1342
1362
|
node_version=node_version,
|
|
1343
1363
|
declared_capabilities=ModelNodeCapabilities(),
|
|
1344
1364
|
discovered_capabilities=discovered_capabilities,
|
|
1365
|
+
contract_capabilities=contract_capabilities,
|
|
1345
1366
|
endpoints=endpoints,
|
|
1346
1367
|
current_state=current_state,
|
|
1347
1368
|
reason=EnumIntrospectionReason.HEARTBEAT, # cache_refresh maps to heartbeat
|
|
@@ -1353,7 +1374,7 @@ class MixinNodeIntrospection:
|
|
|
1353
1374
|
# Update cache - cast the model_dump output to our typed dict since we know
|
|
1354
1375
|
# the structure matches (model_dump returns dict[str, Any] by default)
|
|
1355
1376
|
self._introspection_cache = cast(
|
|
1356
|
-
IntrospectionCacheDict, event.model_dump(mode="json")
|
|
1377
|
+
"IntrospectionCacheDict", event.model_dump(mode="json")
|
|
1357
1378
|
)
|
|
1358
1379
|
self._introspection_cached_at = current_time
|
|
1359
1380
|
|
|
@@ -1489,8 +1510,13 @@ class MixinNodeIntrospection:
|
|
|
1489
1510
|
assert event_bus is not None # Redundant but helps mypy
|
|
1490
1511
|
topic = self._introspection_topic
|
|
1491
1512
|
if hasattr(event_bus, "publish_envelope"):
|
|
1513
|
+
# Wrap event in ModelEventEnvelope for protocol compliance
|
|
1514
|
+
envelope: ModelEventEnvelope[object] = ModelEventEnvelope(
|
|
1515
|
+
payload=publish_event,
|
|
1516
|
+
correlation_id=final_correlation_id,
|
|
1517
|
+
)
|
|
1492
1518
|
await event_bus.publish_envelope(
|
|
1493
|
-
envelope=
|
|
1519
|
+
envelope=envelope, # type: ignore[arg-type]
|
|
1494
1520
|
topic=topic,
|
|
1495
1521
|
)
|
|
1496
1522
|
else:
|
|
@@ -1604,8 +1630,13 @@ class MixinNodeIntrospection:
|
|
|
1604
1630
|
assert event_bus is not None # Redundant but helps mypy
|
|
1605
1631
|
topic = self._heartbeat_topic
|
|
1606
1632
|
if hasattr(event_bus, "publish_envelope"):
|
|
1633
|
+
# Wrap event in ModelEventEnvelope for protocol compliance
|
|
1634
|
+
envelope: ModelEventEnvelope[object] = ModelEventEnvelope(
|
|
1635
|
+
payload=heartbeat,
|
|
1636
|
+
correlation_id=heartbeat.correlation_id,
|
|
1637
|
+
)
|
|
1607
1638
|
await event_bus.publish_envelope(
|
|
1608
|
-
envelope=
|
|
1639
|
+
envelope=envelope, # type: ignore[arg-type]
|
|
1609
1640
|
topic=topic,
|
|
1610
1641
|
)
|
|
1611
1642
|
else:
|
|
@@ -1771,7 +1802,9 @@ class MixinNodeIntrospection:
|
|
|
1771
1802
|
)
|
|
1772
1803
|
self._registry_unsubscribe = None
|
|
1773
1804
|
|
|
1774
|
-
async def _handle_introspection_request(
|
|
1805
|
+
async def _handle_introspection_request(
|
|
1806
|
+
self, message: ProtocolEventMessage
|
|
1807
|
+
) -> None:
|
|
1775
1808
|
"""Handle incoming introspection request.
|
|
1776
1809
|
|
|
1777
1810
|
Includes error recovery with rate-limited logging to prevent
|
|
@@ -1779,7 +1812,7 @@ class MixinNodeIntrospection:
|
|
|
1779
1812
|
non-fatal errors to maintain graceful degradation.
|
|
1780
1813
|
|
|
1781
1814
|
Args:
|
|
1782
|
-
message: The incoming event message
|
|
1815
|
+
message: The incoming event message (implements ProtocolEventMessage protocol)
|
|
1783
1816
|
"""
|
|
1784
1817
|
try:
|
|
1785
1818
|
await self._process_introspection_request(message)
|
|
@@ -1788,7 +1821,9 @@ class MixinNodeIntrospection:
|
|
|
1788
1821
|
except Exception as e:
|
|
1789
1822
|
self._handle_request_error(e)
|
|
1790
1823
|
|
|
1791
|
-
async def _process_introspection_request(
|
|
1824
|
+
async def _process_introspection_request(
|
|
1825
|
+
self, message: ProtocolEventMessage
|
|
1826
|
+
) -> None:
|
|
1792
1827
|
"""Process the introspection request message.
|
|
1793
1828
|
|
|
1794
1829
|
Args:
|
|
@@ -196,7 +196,7 @@ class MixinRetryExecution(ABC):
|
|
|
196
196
|
This should only be called when _circuit_breaker_initialized is True,
|
|
197
197
|
which guarantees the circuit breaker methods are available.
|
|
198
198
|
"""
|
|
199
|
-
return cast(ProtocolCircuitBreakerAware, self)
|
|
199
|
+
return cast("ProtocolCircuitBreakerAware", self)
|
|
200
200
|
|
|
201
201
|
async def _record_circuit_failure_if_enabled(
|
|
202
202
|
self, operation: str, correlation_id: UUID
|
|
@@ -27,6 +27,7 @@ from uuid import UUID
|
|
|
27
27
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
28
28
|
|
|
29
29
|
from omnibase_core.enums import EnumNodeKind
|
|
30
|
+
from omnibase_core.models.contracts import ModelContractBase
|
|
30
31
|
|
|
31
32
|
if TYPE_CHECKING:
|
|
32
33
|
from omnibase_core.protocols.event_bus.protocol_event_bus import ProtocolEventBus
|
|
@@ -86,6 +87,9 @@ class ModelIntrospectionConfig(BaseModel):
|
|
|
86
87
|
request_introspection_topic: Topic for receiving introspection requests.
|
|
87
88
|
Defaults to "node.request_introspection". ONEX topics (onex.*)
|
|
88
89
|
require version suffix (.v1, .v2, etc.).
|
|
90
|
+
contract: Optional typed contract model for capability extraction.
|
|
91
|
+
When provided, MixinNodeIntrospection extracts contract_capabilities
|
|
92
|
+
using ContractCapabilityExtractor. None for legacy nodes.
|
|
89
93
|
|
|
90
94
|
Example:
|
|
91
95
|
```python
|
|
@@ -185,6 +189,13 @@ class ModelIntrospectionConfig(BaseModel):
|
|
|
185
189
|
"ONEX topics (onex.*) require version suffix (.v1, .v2, etc.).",
|
|
186
190
|
)
|
|
187
191
|
|
|
192
|
+
contract: ModelContractBase | None = Field(
|
|
193
|
+
default=None,
|
|
194
|
+
description="Typed contract model for capability extraction. "
|
|
195
|
+
"When provided, MixinNodeIntrospection will extract contract_capabilities "
|
|
196
|
+
"using ContractCapabilityExtractor. None for legacy nodes without contracts.",
|
|
197
|
+
)
|
|
198
|
+
|
|
188
199
|
@field_validator("node_type", mode="before")
|
|
189
200
|
@classmethod
|
|
190
201
|
def validate_node_type(cls, v: object) -> EnumNodeKind:
|
|
@@ -12,26 +12,69 @@ and error reporting in ONEX handlers.
|
|
|
12
12
|
Added ModelHandlerDescriptor and ModelContractDiscoveryResult for
|
|
13
13
|
OMN-1097 filesystem handler discovery.
|
|
14
14
|
|
|
15
|
+
.. versionchanged:: 0.6.4
|
|
16
|
+
Added ModelBootstrapHandlerDescriptor for OMN-1087 bootstrap handler
|
|
17
|
+
validation with required handler_class field.
|
|
18
|
+
|
|
19
|
+
.. versionchanged:: 0.7.0
|
|
20
|
+
Added ModelHandlerSourceConfig for OMN-1095 handler source mode
|
|
21
|
+
configuration with production hardening features.
|
|
22
|
+
|
|
15
23
|
Note:
|
|
16
|
-
ModelContractDiscoveryResult uses a forward reference to
|
|
17
|
-
|
|
18
|
-
reference is resolved via model_rebuild() in
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
ModelContractDiscoveryResult uses a forward reference to ModelHandlerValidationError
|
|
25
|
+
to avoid circular imports between models.handlers and models.errors packages.
|
|
26
|
+
The forward reference is resolved via model_rebuild() calls in runtime modules
|
|
27
|
+
that import ModelHandlerValidationError (e.g., handler_contract_source.py,
|
|
28
|
+
handler_bootstrap_source.py, registry_contract_source.py). Each module calls
|
|
29
|
+
model_rebuild() after importing both the model and the forward-referenced type.
|
|
30
|
+
This pattern is required because:
|
|
31
|
+
1. models.errors imports ModelHandlerIdentifier from models.handlers
|
|
32
|
+
2. models.handlers cannot import from models.errors at module level (circular)
|
|
33
|
+
3. model_rebuild() is idempotent, so multiple calls are harmless
|
|
21
34
|
"""
|
|
22
35
|
|
|
36
|
+
from omnibase_infra.models.handlers.model_bootstrap_handler_descriptor import (
|
|
37
|
+
ModelBootstrapHandlerDescriptor,
|
|
38
|
+
)
|
|
23
39
|
from omnibase_infra.models.handlers.model_contract_discovery_result import (
|
|
24
40
|
ModelContractDiscoveryResult,
|
|
25
41
|
)
|
|
26
42
|
from omnibase_infra.models.handlers.model_handler_descriptor import (
|
|
43
|
+
LiteralHandlerKind,
|
|
27
44
|
ModelHandlerDescriptor,
|
|
28
45
|
)
|
|
29
46
|
from omnibase_infra.models.handlers.model_handler_identifier import (
|
|
30
47
|
ModelHandlerIdentifier,
|
|
31
48
|
)
|
|
49
|
+
from omnibase_infra.models.handlers.model_handler_source_config import (
|
|
50
|
+
ModelHandlerSourceConfig,
|
|
51
|
+
)
|
|
32
52
|
|
|
33
53
|
__all__ = [
|
|
54
|
+
"LiteralHandlerKind",
|
|
55
|
+
"ModelBootstrapHandlerDescriptor",
|
|
34
56
|
"ModelContractDiscoveryResult",
|
|
35
57
|
"ModelHandlerDescriptor",
|
|
36
58
|
"ModelHandlerIdentifier",
|
|
59
|
+
"ModelHandlerSourceConfig",
|
|
37
60
|
]
|
|
61
|
+
|
|
62
|
+
# =============================================================================
|
|
63
|
+
# Forward Reference Resolution
|
|
64
|
+
# =============================================================================
|
|
65
|
+
# ModelContractDiscoveryResult uses TYPE_CHECKING to defer import of
|
|
66
|
+
# ModelHandlerValidationError to avoid circular imports:
|
|
67
|
+
# - models.errors imports ModelHandlerIdentifier from models.handlers
|
|
68
|
+
# - models.handlers cannot import ModelHandlerValidationError at module level
|
|
69
|
+
#
|
|
70
|
+
# The forward reference is resolved via model_rebuild() in runtime modules that
|
|
71
|
+
# import ModelHandlerValidationError (e.g., handler_contract_source.py,
|
|
72
|
+
# handler_bootstrap_source.py, registry_contract_source.py, handler_source_resolver.py).
|
|
73
|
+
# Each module calls model_rebuild() at module level after importing both the model
|
|
74
|
+
# and the forward-referenced type. This is safe because model_rebuild() is idempotent.
|
|
75
|
+
#
|
|
76
|
+
# Why NOT here at module level:
|
|
77
|
+
# - Circular import: models.handlers.__init__ -> models.errors.__init__
|
|
78
|
+
# -> model_handler_validation_error.py -> models.handlers (for identifier)
|
|
79
|
+
# - Runtime modules load after model packages, avoiding this cycle
|
|
80
|
+
# =============================================================================
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Bootstrap Handler Descriptor Model with Required handler_class.
|
|
4
|
+
|
|
5
|
+
This module provides ModelBootstrapHandlerDescriptor, a specialized handler
|
|
6
|
+
descriptor for bootstrap handlers that REQUIRES the handler_class field to be set.
|
|
7
|
+
|
|
8
|
+
Bootstrap handlers are hardcoded handlers that must always specify their
|
|
9
|
+
implementation class for dynamic import. Unlike contract-discovered handlers
|
|
10
|
+
where handler_class may be optional (inferred from convention), bootstrap
|
|
11
|
+
handlers have no contract file to derive the class from.
|
|
12
|
+
|
|
13
|
+
Part of OMN-1087: Implement HandlerBootstrapSource descriptor-based validation.
|
|
14
|
+
|
|
15
|
+
See Also:
|
|
16
|
+
- ModelHandlerDescriptor: Base descriptor with optional handler_class
|
|
17
|
+
- HandlerBootstrapSource: Source that uses this specialized descriptor
|
|
18
|
+
- BootstrapEffectDefinition: TypedDict for bootstrap handler definitions
|
|
19
|
+
|
|
20
|
+
.. versionadded:: 0.6.4
|
|
21
|
+
Created as part of OMN-1087 bootstrap handler validation.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from pydantic import ConfigDict, Field
|
|
27
|
+
|
|
28
|
+
from omnibase_infra.models.handlers.model_handler_descriptor import (
|
|
29
|
+
ModelHandlerDescriptor,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ModelBootstrapHandlerDescriptor(ModelHandlerDescriptor):
|
|
34
|
+
"""Handler descriptor for bootstrap handlers with required handler_class.
|
|
35
|
+
|
|
36
|
+
This specialized descriptor extends ModelHandlerDescriptor to enforce that
|
|
37
|
+
handler_class is always set. Bootstrap handlers are hardcoded and must
|
|
38
|
+
specify their implementation class since there is no contract file to
|
|
39
|
+
derive the class from.
|
|
40
|
+
|
|
41
|
+
The key difference from ModelHandlerDescriptor:
|
|
42
|
+
- handler_class: Required (str) instead of optional (str | None)
|
|
43
|
+
|
|
44
|
+
All other fields maintain the same constraints as the parent class.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
handler_id: Unique identifier for the handler (e.g., "proto.consul").
|
|
48
|
+
name: Human-readable name for the handler.
|
|
49
|
+
version: Semantic version (ModelSemVer). Accepts string, dict, or ModelSemVer.
|
|
50
|
+
handler_kind: Handler kind (compute, effect, reducer, orchestrator).
|
|
51
|
+
input_model: Fully qualified input model class path.
|
|
52
|
+
output_model: Fully qualified output model class path.
|
|
53
|
+
description: Optional description of the handler.
|
|
54
|
+
handler_class: REQUIRED fully qualified Python class path for dynamic import.
|
|
55
|
+
contract_path: Path to the source contract file (typically None for bootstrap).
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
Create a bootstrap handler descriptor:
|
|
59
|
+
|
|
60
|
+
>>> descriptor = ModelBootstrapHandlerDescriptor(
|
|
61
|
+
... handler_id="proto.consul",
|
|
62
|
+
... name="Consul Handler",
|
|
63
|
+
... version="1.0.0",
|
|
64
|
+
... handler_kind="effect",
|
|
65
|
+
... input_model="omnibase_infra.models.types.JsonDict",
|
|
66
|
+
... output_model="omnibase_core.models.dispatch.ModelHandlerOutput",
|
|
67
|
+
... handler_class="omnibase_infra.handlers.handler_consul.HandlerConsul",
|
|
68
|
+
... )
|
|
69
|
+
>>> descriptor.handler_class
|
|
70
|
+
'omnibase_infra.handlers.handler_consul.HandlerConsul'
|
|
71
|
+
|
|
72
|
+
Missing handler_class raises ValidationError:
|
|
73
|
+
|
|
74
|
+
>>> from pydantic import ValidationError
|
|
75
|
+
>>> try:
|
|
76
|
+
... ModelBootstrapHandlerDescriptor(
|
|
77
|
+
... handler_id="proto.consul",
|
|
78
|
+
... name="Consul Handler",
|
|
79
|
+
... version="1.0.0",
|
|
80
|
+
... handler_kind="effect",
|
|
81
|
+
... input_model="omnibase_infra.models.types.JsonDict",
|
|
82
|
+
... output_model="omnibase_core.models.dispatch.ModelHandlerOutput",
|
|
83
|
+
... # handler_class omitted - will fail
|
|
84
|
+
... )
|
|
85
|
+
... except ValidationError as e:
|
|
86
|
+
... print("Validation failed as expected")
|
|
87
|
+
Validation failed as expected
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
ValidationError: If handler_class is not provided or is None.
|
|
91
|
+
|
|
92
|
+
.. versionadded:: 0.6.4
|
|
93
|
+
Created as part of OMN-1087 bootstrap handler validation.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
model_config = ConfigDict(
|
|
97
|
+
frozen=True,
|
|
98
|
+
extra="forbid",
|
|
99
|
+
strict=True,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Override handler_class to be required (no default, not optional)
|
|
103
|
+
# The Field() definition must include the pattern constraint from parent
|
|
104
|
+
handler_class: str = Field(
|
|
105
|
+
...,
|
|
106
|
+
min_length=3,
|
|
107
|
+
pattern=r"^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)+$",
|
|
108
|
+
description=(
|
|
109
|
+
"REQUIRED: Fully qualified Python class path for dynamic handler import. "
|
|
110
|
+
"Bootstrap handlers must always specify this field since they have no "
|
|
111
|
+
"contract file to derive the class from. "
|
|
112
|
+
"Example: 'omnibase_infra.handlers.handler_consul.HandlerConsul'"
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def to_base_descriptor(self) -> ModelHandlerDescriptor:
|
|
117
|
+
"""Convert to base ModelHandlerDescriptor for API compatibility.
|
|
118
|
+
|
|
119
|
+
This method allows bootstrap descriptors to be used where the base
|
|
120
|
+
ModelHandlerDescriptor type is expected, while maintaining the
|
|
121
|
+
validation benefits of the bootstrap-specific model.
|
|
122
|
+
|
|
123
|
+
Implementation Notes:
|
|
124
|
+
Uses ``model_dump()`` without ``exclude_unset=True`` because:
|
|
125
|
+
|
|
126
|
+
1. **Field parity**: This child class has NO extra fields beyond the
|
|
127
|
+
parent. The only difference is ``handler_class`` type constraint
|
|
128
|
+
(required ``str`` vs optional ``str | None``).
|
|
129
|
+
|
|
130
|
+
2. **Type compatibility**: A ``str`` value from child is valid where
|
|
131
|
+
parent expects ``str | None``.
|
|
132
|
+
|
|
133
|
+
3. **Complete copy**: ``model_dump()`` ensures all fields are copied,
|
|
134
|
+
including those set to their default values.
|
|
135
|
+
|
|
136
|
+
Using ``exclude_unset=True`` would risk excluding fields that have
|
|
137
|
+
defaults but were explicitly set to those defaults during construction.
|
|
138
|
+
|
|
139
|
+
If future versions add child-specific fields not in parent, this
|
|
140
|
+
method MUST be updated to use ``exclude={'new_field'}`` or refactored.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
ModelHandlerDescriptor instance with all fields copied.
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
>>> bootstrap_desc = ModelBootstrapHandlerDescriptor(
|
|
147
|
+
... handler_id="proto.consul",
|
|
148
|
+
... name="Consul Handler",
|
|
149
|
+
... version="1.0.0",
|
|
150
|
+
... handler_kind="effect",
|
|
151
|
+
... input_model="omnibase_infra.models.types.JsonDict",
|
|
152
|
+
... output_model="omnibase_core.models.dispatch.ModelHandlerOutput",
|
|
153
|
+
... handler_class="omnibase_infra.handlers.handler_consul.HandlerConsul",
|
|
154
|
+
... )
|
|
155
|
+
>>> base_desc = bootstrap_desc.to_base_descriptor()
|
|
156
|
+
>>> isinstance(base_desc, ModelHandlerDescriptor)
|
|
157
|
+
True
|
|
158
|
+
"""
|
|
159
|
+
return ModelHandlerDescriptor(**self.model_dump())
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
__all__ = ["ModelBootstrapHandlerDescriptor"]
|
|
@@ -72,9 +72,11 @@ class ModelContractDiscoveryResult(BaseModel):
|
|
|
72
72
|
)
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
75
|
+
# Forward Reference Resolution:
|
|
76
|
+
# This model uses TYPE_CHECKING to defer import of ModelHandlerValidationError.
|
|
77
|
+
# model_rebuild() is called in runtime modules that import ModelHandlerValidationError
|
|
78
|
+
# (e.g., handler_contract_source.py, handler_bootstrap_source.py, registry_contract_source.py).
|
|
79
|
+
# Each module calls model_rebuild() at module level after importing both the model
|
|
80
|
+
# and the forward-referenced type. This is safe because model_rebuild() is idempotent.
|
|
79
81
|
|
|
80
82
|
__all__ = ["ModelContractDiscoveryResult"]
|
|
@@ -99,6 +99,7 @@ class ModelHandlerDescriptor(BaseModel):
|
|
|
99
99
|
input_model: Fully qualified input model class path.
|
|
100
100
|
output_model: Fully qualified output model class path.
|
|
101
101
|
description: Optional description of the handler.
|
|
102
|
+
handler_class: Fully qualified Python class path for dynamic handler import.
|
|
102
103
|
contract_path: Path to the source contract file.
|
|
103
104
|
|
|
104
105
|
Example:
|
|
@@ -176,10 +177,24 @@ class ModelHandlerDescriptor(BaseModel):
|
|
|
176
177
|
default=None,
|
|
177
178
|
description="Optional description of the handler",
|
|
178
179
|
)
|
|
180
|
+
handler_class: str | None = Field(
|
|
181
|
+
default=None,
|
|
182
|
+
min_length=3,
|
|
183
|
+
pattern=r"^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)+$",
|
|
184
|
+
description="Fully qualified Python class path for dynamic handler import (e.g., 'omnibase_infra.handlers.handler_consul.HandlerConsul')",
|
|
185
|
+
)
|
|
179
186
|
contract_path: str | None = Field(
|
|
180
187
|
default=None,
|
|
181
188
|
description="Path to the source contract file",
|
|
182
189
|
)
|
|
190
|
+
contract_config: dict[str, JsonType] | None = Field(
|
|
191
|
+
default=None,
|
|
192
|
+
description=(
|
|
193
|
+
"Parsed configuration from the handler contract file. "
|
|
194
|
+
"Contains extracted values like security settings, tags, and handler metadata. "
|
|
195
|
+
"Populated during handler discovery when contract_path is set."
|
|
196
|
+
),
|
|
197
|
+
)
|
|
183
198
|
|
|
184
199
|
|
|
185
200
|
__all__ = ["LiteralHandlerKind", "ModelHandlerDescriptor"]
|