omnibase_infra 0.2.5__py3-none-any.whl → 0.2.7__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 (139) hide show
  1. omnibase_infra/constants_topic_patterns.py +26 -0
  2. omnibase_infra/enums/__init__.py +3 -0
  3. omnibase_infra/enums/enum_consumer_group_purpose.py +92 -0
  4. omnibase_infra/enums/enum_handler_source_mode.py +16 -2
  5. omnibase_infra/errors/__init__.py +4 -0
  6. omnibase_infra/errors/error_binding_resolution.py +128 -0
  7. omnibase_infra/event_bus/configs/kafka_event_bus_config.yaml +0 -2
  8. omnibase_infra/event_bus/event_bus_inmemory.py +64 -10
  9. omnibase_infra/event_bus/event_bus_kafka.py +105 -47
  10. omnibase_infra/event_bus/mixin_kafka_broadcast.py +3 -7
  11. omnibase_infra/event_bus/mixin_kafka_dlq.py +12 -6
  12. omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +0 -81
  13. omnibase_infra/event_bus/testing/__init__.py +26 -0
  14. omnibase_infra/event_bus/testing/adapter_protocol_event_publisher_inmemory.py +418 -0
  15. omnibase_infra/event_bus/testing/model_publisher_metrics.py +64 -0
  16. omnibase_infra/handlers/handler_consul.py +2 -0
  17. omnibase_infra/handlers/mixins/__init__.py +5 -0
  18. omnibase_infra/handlers/mixins/mixin_consul_service.py +274 -10
  19. omnibase_infra/handlers/mixins/mixin_consul_topic_index.py +585 -0
  20. omnibase_infra/handlers/models/model_filesystem_config.py +4 -4
  21. omnibase_infra/migrations/001_create_event_ledger.sql +166 -0
  22. omnibase_infra/migrations/001_drop_event_ledger.sql +18 -0
  23. omnibase_infra/mixins/mixin_node_introspection.py +189 -19
  24. omnibase_infra/models/__init__.py +8 -0
  25. omnibase_infra/models/bindings/__init__.py +59 -0
  26. omnibase_infra/models/bindings/constants.py +144 -0
  27. omnibase_infra/models/bindings/model_binding_resolution_result.py +103 -0
  28. omnibase_infra/models/bindings/model_operation_binding.py +44 -0
  29. omnibase_infra/models/bindings/model_operation_bindings_subcontract.py +152 -0
  30. omnibase_infra/models/bindings/model_parsed_binding.py +52 -0
  31. omnibase_infra/models/discovery/model_introspection_config.py +25 -17
  32. omnibase_infra/models/dispatch/__init__.py +8 -0
  33. omnibase_infra/models/dispatch/model_debug_trace_snapshot.py +114 -0
  34. omnibase_infra/models/dispatch/model_materialized_dispatch.py +141 -0
  35. omnibase_infra/models/handlers/model_handler_source_config.py +1 -1
  36. omnibase_infra/models/model_node_identity.py +126 -0
  37. omnibase_infra/models/projection/model_snapshot_topic_config.py +3 -2
  38. omnibase_infra/models/registration/__init__.py +9 -0
  39. omnibase_infra/models/registration/model_event_bus_topic_entry.py +59 -0
  40. omnibase_infra/models/registration/model_node_event_bus_config.py +99 -0
  41. omnibase_infra/models/registration/model_node_introspection_event.py +11 -0
  42. omnibase_infra/models/runtime/__init__.py +9 -0
  43. omnibase_infra/models/validation/model_coverage_metrics.py +2 -2
  44. omnibase_infra/nodes/__init__.py +9 -0
  45. omnibase_infra/nodes/contract_registry_reducer/__init__.py +29 -0
  46. omnibase_infra/nodes/contract_registry_reducer/contract.yaml +255 -0
  47. omnibase_infra/nodes/contract_registry_reducer/models/__init__.py +38 -0
  48. omnibase_infra/nodes/contract_registry_reducer/models/model_contract_registry_state.py +266 -0
  49. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_cleanup_topic_references.py +55 -0
  50. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_deactivate_contract.py +58 -0
  51. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_mark_stale.py +49 -0
  52. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_heartbeat.py +71 -0
  53. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_topic.py +66 -0
  54. omnibase_infra/nodes/contract_registry_reducer/models/model_payload_upsert_contract.py +92 -0
  55. omnibase_infra/nodes/contract_registry_reducer/node.py +121 -0
  56. omnibase_infra/nodes/contract_registry_reducer/reducer.py +784 -0
  57. omnibase_infra/nodes/contract_registry_reducer/registry/__init__.py +9 -0
  58. omnibase_infra/nodes/contract_registry_reducer/registry/registry_infra_contract_registry_reducer.py +101 -0
  59. omnibase_infra/nodes/handlers/consul/contract.yaml +85 -0
  60. omnibase_infra/nodes/handlers/db/contract.yaml +72 -0
  61. omnibase_infra/nodes/handlers/graph/contract.yaml +127 -0
  62. omnibase_infra/nodes/handlers/http/contract.yaml +74 -0
  63. omnibase_infra/nodes/handlers/intent/contract.yaml +66 -0
  64. omnibase_infra/nodes/handlers/mcp/contract.yaml +69 -0
  65. omnibase_infra/nodes/handlers/vault/contract.yaml +91 -0
  66. omnibase_infra/nodes/node_ledger_projection_compute/__init__.py +50 -0
  67. omnibase_infra/nodes/node_ledger_projection_compute/contract.yaml +104 -0
  68. omnibase_infra/nodes/node_ledger_projection_compute/node.py +284 -0
  69. omnibase_infra/nodes/node_ledger_projection_compute/registry/__init__.py +29 -0
  70. omnibase_infra/nodes/node_ledger_projection_compute/registry/registry_infra_ledger_projection.py +118 -0
  71. omnibase_infra/nodes/node_ledger_write_effect/__init__.py +82 -0
  72. omnibase_infra/nodes/node_ledger_write_effect/contract.yaml +200 -0
  73. omnibase_infra/nodes/node_ledger_write_effect/handlers/__init__.py +22 -0
  74. omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_append.py +372 -0
  75. omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_query.py +597 -0
  76. omnibase_infra/nodes/node_ledger_write_effect/models/__init__.py +31 -0
  77. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_append_result.py +54 -0
  78. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_entry.py +92 -0
  79. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query.py +53 -0
  80. omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query_result.py +41 -0
  81. omnibase_infra/nodes/node_ledger_write_effect/node.py +89 -0
  82. omnibase_infra/nodes/node_ledger_write_effect/protocols/__init__.py +13 -0
  83. omnibase_infra/nodes/node_ledger_write_effect/protocols/protocol_ledger_persistence.py +127 -0
  84. omnibase_infra/nodes/node_ledger_write_effect/registry/__init__.py +9 -0
  85. omnibase_infra/nodes/node_ledger_write_effect/registry/registry_infra_ledger_write.py +121 -0
  86. omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +7 -5
  87. omnibase_infra/nodes/reducers/models/__init__.py +7 -2
  88. omnibase_infra/nodes/reducers/models/model_payload_consul_register.py +11 -0
  89. omnibase_infra/nodes/reducers/models/model_payload_ledger_append.py +133 -0
  90. omnibase_infra/nodes/reducers/registration_reducer.py +1 -0
  91. omnibase_infra/protocols/__init__.py +3 -0
  92. omnibase_infra/protocols/protocol_dispatch_engine.py +152 -0
  93. omnibase_infra/runtime/__init__.py +60 -0
  94. omnibase_infra/runtime/binding_resolver.py +753 -0
  95. omnibase_infra/runtime/constants_security.py +70 -0
  96. omnibase_infra/runtime/contract_loaders/__init__.py +9 -0
  97. omnibase_infra/runtime/contract_loaders/operation_bindings_loader.py +789 -0
  98. omnibase_infra/runtime/emit_daemon/__init__.py +97 -0
  99. omnibase_infra/runtime/emit_daemon/cli.py +844 -0
  100. omnibase_infra/runtime/emit_daemon/client.py +811 -0
  101. omnibase_infra/runtime/emit_daemon/config.py +535 -0
  102. omnibase_infra/runtime/emit_daemon/daemon.py +812 -0
  103. omnibase_infra/runtime/emit_daemon/event_registry.py +477 -0
  104. omnibase_infra/runtime/emit_daemon/model_daemon_request.py +139 -0
  105. omnibase_infra/runtime/emit_daemon/model_daemon_response.py +191 -0
  106. omnibase_infra/runtime/emit_daemon/queue.py +618 -0
  107. omnibase_infra/runtime/event_bus_subcontract_wiring.py +466 -0
  108. omnibase_infra/runtime/handler_source_resolver.py +43 -2
  109. omnibase_infra/runtime/kafka_contract_source.py +984 -0
  110. omnibase_infra/runtime/models/__init__.py +13 -0
  111. omnibase_infra/runtime/models/model_contract_load_result.py +224 -0
  112. omnibase_infra/runtime/models/model_runtime_contract_config.py +268 -0
  113. omnibase_infra/runtime/models/model_runtime_scheduler_config.py +4 -3
  114. omnibase_infra/runtime/models/model_security_config.py +109 -0
  115. omnibase_infra/runtime/publisher_topic_scoped.py +294 -0
  116. omnibase_infra/runtime/runtime_contract_config_loader.py +406 -0
  117. omnibase_infra/runtime/service_kernel.py +76 -6
  118. omnibase_infra/runtime/service_message_dispatch_engine.py +558 -15
  119. omnibase_infra/runtime/service_runtime_host_process.py +770 -20
  120. omnibase_infra/runtime/transition_notification_publisher.py +3 -2
  121. omnibase_infra/runtime/util_wiring.py +206 -62
  122. omnibase_infra/services/mcp/service_mcp_tool_sync.py +27 -9
  123. omnibase_infra/services/session/config_consumer.py +25 -8
  124. omnibase_infra/services/session/config_store.py +2 -2
  125. omnibase_infra/services/session/consumer.py +1 -1
  126. omnibase_infra/topics/__init__.py +45 -0
  127. omnibase_infra/topics/platform_topic_suffixes.py +140 -0
  128. omnibase_infra/topics/util_topic_composition.py +95 -0
  129. omnibase_infra/types/typed_dict/__init__.py +9 -1
  130. omnibase_infra/types/typed_dict/typed_dict_envelope_build_params.py +115 -0
  131. omnibase_infra/utils/__init__.py +9 -0
  132. omnibase_infra/utils/util_consumer_group.py +232 -0
  133. omnibase_infra/validation/infra_validators.py +18 -1
  134. omnibase_infra/validation/validation_exemptions.yaml +192 -0
  135. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/METADATA +3 -3
  136. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/RECORD +139 -52
  137. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/entry_points.txt +1 -0
  138. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/WHEEL +0 -0
  139. {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,114 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Debug trace snapshot model.
4
+
5
+ This module defines the serializable trace metadata snapshot that replaces
6
+ live envelope references in the dispatch contract.
7
+
8
+ Design Rationale:
9
+ The dispatch boundary is a **serialization boundary**. Anything crossing
10
+ this boundary must be:
11
+ - Loggable (observability)
12
+ - Replayable (debugging)
13
+ - Transportable (Kafka, distributed dispatch)
14
+ - Inspectable offline (audit pipelines)
15
+
16
+ Live Python object references violate all of these requirements.
17
+ This snapshot model provides the trace metadata handlers need for
18
+ debugging without creating dependencies on internal envelope types.
19
+
20
+ Warning:
21
+ This model exists solely for debugging and observability.
22
+ It must **never** be used for business logic.
23
+
24
+ The data in this snapshot is:
25
+ - Non-authoritative (may not reflect the complete envelope state)
26
+ - Metadata-only (does not include payload content)
27
+ - Immutable (cannot be used to modify the original envelope)
28
+
29
+ .. versionadded:: 0.2.8
30
+ Added as part of OMN-1518 - Strict JSON-safe dispatch contract.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ from pydantic import BaseModel, ConfigDict, Field
36
+
37
+
38
+ class ModelDebugTraceSnapshot(BaseModel):
39
+ """Serializable, non-authoritative trace snapshot.
40
+
41
+ This model captures trace metadata from the original envelope for
42
+ debugging and observability purposes. It replaces live envelope
43
+ references to maintain transport-safe dispatch contracts.
44
+
45
+ All fields are optional because:
46
+ 1. Not all envelopes have all metadata fields
47
+ 2. Extraction may fail gracefully without blocking dispatch
48
+ 3. Debug data should never cause handler failures
49
+
50
+ Attributes:
51
+ event_type: The event type identifier (e.g., "UserCreated").
52
+ correlation_id: Correlation ID for distributed tracing (serialized UUID).
53
+ trace_id: Trace ID for span correlation (serialized UUID).
54
+ causation_id: Causation ID linking to parent event (serialized UUID).
55
+ topic: The Kafka/event topic this message was received on.
56
+ timestamp: When the event was created (ISO 8601 format).
57
+ partition_key: The partition key for Kafka routing.
58
+
59
+ Example:
60
+ >>> snapshot = ModelDebugTraceSnapshot(
61
+ ... correlation_id="550e8400-e29b-41d4-a716-446655440000",
62
+ ... trace_id="660e8400-e29b-41d4-a716-446655440001",
63
+ ... topic="dev.user.events.v1",
64
+ ... timestamp="2025-01-27T12:00:00Z",
65
+ ... )
66
+ >>> snapshot.model_dump()
67
+ {'event_type': None, 'correlation_id': '550e8400-...', ...}
68
+
69
+ Warning:
70
+ This snapshot is **non-authoritative**. It exists solely for
71
+ debugging and observability. Do not use it for business logic.
72
+
73
+ .. versionadded:: 0.2.8
74
+ """
75
+
76
+ model_config = ConfigDict(
77
+ frozen=True,
78
+ extra="forbid",
79
+ )
80
+
81
+ event_type: str | None = Field(
82
+ default=None,
83
+ description="Event type identifier (e.g., 'UserCreated').",
84
+ )
85
+
86
+ correlation_id: str | None = Field(
87
+ default=None,
88
+ description="Correlation ID for distributed tracing (serialized UUID).",
89
+ )
90
+
91
+ trace_id: str | None = Field(
92
+ default=None,
93
+ description="Trace ID for span correlation (serialized UUID).",
94
+ )
95
+
96
+ causation_id: str | None = Field(
97
+ default=None,
98
+ description="Causation ID linking to parent event (serialized UUID).",
99
+ )
100
+
101
+ topic: str | None = Field(
102
+ default=None,
103
+ description="Event topic this message was received on.",
104
+ )
105
+
106
+ timestamp: str | None = Field(
107
+ default=None,
108
+ description="Event creation timestamp (ISO 8601 format).",
109
+ )
110
+
111
+ partition_key: str | None = Field(
112
+ default=None,
113
+ description="Partition key for Kafka routing.",
114
+ )
@@ -0,0 +1,141 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Materialized dispatch message model.
4
+
5
+ This module defines the canonical runtime contract for dispatched messages.
6
+ All handlers receive a materialized dict that conforms to this shape.
7
+
8
+ Design Rationale:
9
+ The dispatch boundary is a **serialization boundary**. This model enforces
10
+ that all data crossing the dispatch layer is transport-safe:
11
+ - JSON-serializable (can be logged, replayed, transported to Kafka)
12
+ - Deterministic (same input produces same serialized output)
13
+ - Inspectable (can be examined offline without Python runtime)
14
+
15
+ Handlers that need rich Pydantic models should hydrate them locally:
16
+ ``ModelFoo.model_validate(dispatch["payload"])``
17
+
18
+ This separation keeps the runtime decoupled from handler internals and
19
+ enables distributed dispatch, event replay, and observability tooling.
20
+
21
+ .. versionadded:: 0.2.6
22
+ Added as part of OMN-1518 - Declarative operation bindings.
23
+
24
+ .. versionchanged:: 0.2.8
25
+ Changed to strict JSON-safe contract:
26
+ - Removed arbitrary_types_allowed
27
+ - Changed payload/bindings to JsonType
28
+ - Renamed __debug_original_envelope to __debug_trace (serialized snapshot)
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ from pydantic import BaseModel, ConfigDict, Field
34
+
35
+ from omnibase_core.types import JsonType
36
+
37
+
38
+ class ModelMaterializedDispatch(BaseModel):
39
+ """Canonical dispatch message shape (strictly JSON-safe).
40
+
41
+ This is the runtime contract for all handlers. After materialization,
42
+ every handler receives a dict that conforms to this structure:
43
+
44
+ - ``payload``: The event payload as a JSON-safe dict (required)
45
+ - ``__bindings``: Resolved binding parameters (always present, may be empty)
46
+ - ``__debug_trace``: Serialized trace metadata snapshot (debug only)
47
+
48
+ The double-underscore prefix on ``__bindings`` and ``__debug_trace``
49
+ signals that these are infrastructure-level fields, not business data.
50
+
51
+ Transport Safety:
52
+ All fields are JSON-serializable. This enables:
53
+ - Event replay from logs or Kafka
54
+ - Distributed dispatch across processes
55
+ - Observability tooling (logging, tracing, dashboards)
56
+ - Offline inspection and debugging
57
+
58
+ Handler Hydration:
59
+ Handlers that need typed Pydantic models should hydrate locally:
60
+
61
+ >>> payload = dispatch["payload"]
62
+ >>> event = UserCreatedEvent.model_validate(payload)
63
+
64
+ This keeps the dispatch boundary clean and transport-safe.
65
+
66
+ Warning:
67
+ ``__debug_trace`` is provided ONLY for debugging and observability.
68
+ It is a serialized snapshot of trace metadata, NOT the live envelope.
69
+
70
+ **DO NOT**:
71
+ - Use ``__debug_trace`` for business logic
72
+ - Assume ``__debug_trace`` reflects complete envelope state
73
+ - Depend on specific fields being present
74
+
75
+ Example:
76
+ >>> materialized = {
77
+ ... "payload": {"user_id": "123", "action": "login"},
78
+ ... "__bindings": {"user_id": "123", "timestamp": "2025-01-27T12:00:00Z"},
79
+ ... "__debug_trace": {
80
+ ... "correlation_id": "550e8400-e29b-41d4-a716-446655440000",
81
+ ... "topic": "dev.user.events.v1",
82
+ ... },
83
+ ... }
84
+ >>> validated = ModelMaterializedDispatch.model_validate(materialized)
85
+ >>> validated.payload
86
+ {'user_id': '123', 'action': 'login'}
87
+
88
+ Attributes:
89
+ payload: The event payload as a JSON-safe dict. Handlers that need
90
+ typed models should call ``model_validate()`` on this dict.
91
+ Primitives are wrapped under ``{"_raw": value}`` to maintain
92
+ dict structure (this is a last-resort escape hatch).
93
+ bindings: Resolved binding parameters from contract.yaml operation_bindings.
94
+ Always present (empty dict if no bindings configured). All values
95
+ are JSON-safe (UUIDs and datetimes are serialized to strings).
96
+ debug_trace: Serialized trace metadata snapshot for debugging.
97
+ Contains correlation_id, trace_id, topic, etc. as strings.
98
+ This is NOT authoritative data and should NOT be used for
99
+ business logic. Excluded from repr() to prevent log bloat.
100
+
101
+ .. versionadded:: 0.2.6
102
+
103
+ .. versionchanged:: 0.2.8
104
+ Changed to strict JSON-safe contract. See module docstring.
105
+ """
106
+
107
+ model_config = ConfigDict(
108
+ frozen=True,
109
+ populate_by_name=True,
110
+ extra="forbid",
111
+ # NOTE: arbitrary_types_allowed is intentionally NOT set (defaults to False).
112
+ # This enforces that all data crossing the dispatch boundary is JSON-safe.
113
+ # See module docstring for design rationale.
114
+ )
115
+
116
+ payload: JsonType = Field(
117
+ ...,
118
+ description=(
119
+ "Event payload as JSON-safe dict. "
120
+ "Handlers should hydrate typed models via model_validate()."
121
+ ),
122
+ )
123
+
124
+ bindings: dict[str, JsonType] = Field(
125
+ default_factory=dict,
126
+ alias="__bindings",
127
+ description=(
128
+ "Resolved binding parameters. Always present, may be empty dict. "
129
+ "Values are JSON-safe (UUIDs/datetimes serialized to strings)."
130
+ ),
131
+ )
132
+
133
+ debug_trace: dict[str, str | None] | None = Field(
134
+ default=None,
135
+ alias="__debug_trace",
136
+ description=(
137
+ "Serialized trace metadata snapshot for debugging only. "
138
+ "NOT authoritative. Do NOT use for business logic."
139
+ ),
140
+ repr=False, # Prevent log bloat when stringifying model
141
+ )
@@ -26,7 +26,7 @@ See Also:
26
26
 
27
27
  from __future__ import annotations
28
28
 
29
- from datetime import UTC, datetime, timezone
29
+ from datetime import UTC, datetime
30
30
 
31
31
  from pydantic import BaseModel, ConfigDict, Field, field_validator
32
32
 
@@ -0,0 +1,126 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Node Identity Model for ONEX Infrastructure.
4
+
5
+ This module provides a typed Pydantic model for uniquely identifying ONEX nodes
6
+ within the infrastructure. The identity encapsulates the environment, service,
7
+ node name, and version - the four dimensions required to uniquely identify
8
+ a node instance.
9
+
10
+ The model is immutable (frozen) to ensure identity stability throughout a node's
11
+ lifecycle. Once created, a node identity cannot be modified.
12
+
13
+ .. versionadded:: 0.2.6
14
+ Created as part of OMN-1602 typed node identity for introspection.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, field_validator
20
+
21
+
22
+ class ModelNodeIdentity(BaseModel):
23
+ """Typed identity for ONEX infrastructure nodes.
24
+
25
+ This model uniquely identifies a node within the ONEX infrastructure using
26
+ four dimensions: environment, service, node name, and version. The same
27
+ node identity can be used with different purposes (e.g., introspection,
28
+ registration, heartbeat) - purpose is passed separately to operations.
29
+
30
+ All fields are required and must be non-empty strings without whitespace-only
31
+ values. The model is frozen to ensure identity immutability.
32
+
33
+ Attributes:
34
+ env: Environment identifier (e.g., "dev", "staging", "prod").
35
+ Determines which infrastructure deployment the node belongs to.
36
+ service: Service name from the node's contract (e.g., "omniintelligence",
37
+ "omnibridge"). Groups related nodes under a common service boundary.
38
+ node_name: Node name from the contract (e.g., "claude_hook_event_effect",
39
+ "registration_orchestrator"). Uniquely identifies the node within
40
+ its service.
41
+ version: Version string for the node (e.g., "v1", "v2.0.0").
42
+ Enables version-aware routing and registration. While any non-empty
43
+ string is accepted, semver-style prefixed with 'v' is recommended
44
+ for consistency (e.g., 'v1', 'v1.0.0', 'v2.1.3').
45
+
46
+ Example:
47
+ >>> identity = ModelNodeIdentity(
48
+ ... env="dev",
49
+ ... service="omniintelligence",
50
+ ... node_name="claude_hook_event_effect",
51
+ ... version="v1",
52
+ ... )
53
+ >>> identity.env
54
+ 'dev'
55
+ >>> identity.service
56
+ 'omniintelligence'
57
+
58
+ The model is immutable - attempting to modify raises an error:
59
+
60
+ >>> identity.env = "prod" # doctest: +SKIP
61
+ Traceback (most recent call last):
62
+ ...
63
+ pydantic_core._pydantic_core.ValidationError: ...
64
+
65
+ Empty or whitespace-only values are rejected:
66
+
67
+ >>> ModelNodeIdentity(env="", service="svc", node_name="node", version="v1")
68
+ Traceback (most recent call last):
69
+ ...
70
+ pydantic_core._pydantic_core.ValidationError: ...
71
+
72
+ Note:
73
+ The `purpose` field is intentionally NOT included in this model.
74
+ Purpose (e.g., "introspection", "registration") is passed separately
75
+ to operations because the same node identity can be used for multiple
76
+ purposes.
77
+
78
+ .. versionadded:: 0.2.6
79
+ Created as part of OMN-1602.
80
+ """
81
+
82
+ model_config = ConfigDict(
83
+ frozen=True,
84
+ extra="forbid",
85
+ strict=True,
86
+ )
87
+
88
+ env: str = Field(
89
+ description="Environment identifier (e.g., 'dev', 'staging', 'prod')",
90
+ )
91
+ service: str = Field(
92
+ description="Service name from the node's contract (e.g., 'omniintelligence')",
93
+ )
94
+ node_name: str = Field( # pattern-ok: canonical identifier, not a foreign key reference
95
+ description="Node name from the contract (e.g., 'claude_hook_event_effect')",
96
+ )
97
+ version: str = Field(
98
+ description="Version string for the node (e.g., 'v1', 'v2.0.0')",
99
+ )
100
+
101
+ @field_validator("env", "service", "node_name", "version", mode="after")
102
+ @classmethod
103
+ def _validate_non_empty(cls, v: str, info: ValidationInfo) -> str:
104
+ """Validate that string fields are non-empty and not whitespace-only.
105
+
106
+ Args:
107
+ v: The string value to validate.
108
+ info: Pydantic validation context containing field name.
109
+
110
+ Returns:
111
+ The validated string value.
112
+
113
+ Raises:
114
+ ValueError: If the value is empty or contains only whitespace.
115
+ """
116
+ field_name = info.field_name
117
+ if not v:
118
+ msg = f"'{field_name}' must not be empty"
119
+ raise ValueError(msg)
120
+ if not v.strip():
121
+ msg = f"'{field_name}' must not contain only whitespace"
122
+ raise ValueError(msg)
123
+ return v
124
+
125
+
126
+ __all__: list[str] = ["ModelNodeIdentity"]
@@ -58,6 +58,7 @@ import yaml
58
58
  from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
59
59
 
60
60
  from omnibase_infra.enums import EnumInfraTransportType
61
+ from omnibase_infra.topics import SUFFIX_REGISTRATION_SNAPSHOTS
61
62
 
62
63
  if TYPE_CHECKING:
63
64
  from omnibase_infra.errors.error_infra import ProtocolConfigurationError
@@ -154,7 +155,7 @@ class ModelSnapshotTopicConfig(BaseModel):
154
155
 
155
156
  # Topic identity
156
157
  topic: str = Field(
157
- default="onex.registration.snapshots",
158
+ default=SUFFIX_REGISTRATION_SNAPSHOTS,
158
159
  min_length=1,
159
160
  max_length=255,
160
161
  description="Full Kafka topic name for registration snapshots",
@@ -442,7 +443,7 @@ class ModelSnapshotTopicConfig(BaseModel):
442
443
  Default configuration instance with environment overrides
443
444
  """
444
445
  base_config = cls(
445
- topic="onex.registration.snapshots",
446
+ topic=SUFFIX_REGISTRATION_SNAPSHOTS,
446
447
  partition_count=12,
447
448
  replication_factor=3,
448
449
  cleanup_policy="compact",
@@ -18,12 +18,18 @@ from omnibase_infra.models.registration.events import (
18
18
  ModelNodeRegistrationInitiated,
19
19
  ModelNodeRegistrationRejected,
20
20
  )
21
+ from omnibase_infra.models.registration.model_event_bus_topic_entry import (
22
+ ModelEventBusTopicEntry,
23
+ )
21
24
  from omnibase_infra.models.registration.model_introspection_metrics import (
22
25
  ModelIntrospectionMetrics,
23
26
  )
24
27
  from omnibase_infra.models.registration.model_node_capabilities import (
25
28
  ModelNodeCapabilities,
26
29
  )
30
+ from omnibase_infra.models.registration.model_node_event_bus_config import (
31
+ ModelNodeEventBusConfig,
32
+ )
27
33
  from omnibase_infra.models.registration.model_node_heartbeat_event import (
28
34
  ModelNodeHeartbeatEvent,
29
35
  )
@@ -39,6 +45,9 @@ from omnibase_infra.models.registration.model_node_registration_record import (
39
45
  )
40
46
 
41
47
  __all__ = [
48
+ # Event bus configuration
49
+ "ModelEventBusTopicEntry",
50
+ "ModelNodeEventBusConfig",
42
51
  # Metrics
43
52
  "ModelIntrospectionMetrics",
44
53
  # Decision events (C1 Orchestrator output)
@@ -0,0 +1,59 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Event Bus Topic Entry Model.
4
+
5
+ This module provides the model for a single topic entry in the event bus
6
+ configuration, containing the environment-qualified topic string and
7
+ optional tooling metadata.
8
+
9
+ Key Design Decisions:
10
+ 1. Topics stored as environment-qualified strings (e.g., "dev.onex.evt.intent-classified.v1")
11
+ 2. Metadata fields (event_type, message_category, description) are tooling-only
12
+ 3. Routing uses ONLY the topic string - never metadata fields
13
+ 4. Model is frozen (immutable) with extra="forbid" for safety
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from pydantic import BaseModel, ConfigDict, Field
19
+
20
+
21
+ class ModelEventBusTopicEntry(BaseModel):
22
+ """Single topic entry with optional metadata.
23
+
24
+ IMPORTANT: Routing depends ONLY on the `topic` string.
25
+ Metadata fields (event_type, message_category, description) are
26
+ tooling-facing only and are never used for routing decisions.
27
+
28
+ Attributes:
29
+ topic: Environment-qualified topic string (e.g., "dev.onex.evt...").
30
+ This is the ONLY field used for routing.
31
+ event_type: Optional event model name. Tooling metadata only.
32
+ message_category: Message category (EVENT, COMMAND, INTENT).
33
+ Tooling metadata only. Defaults to "EVENT".
34
+ description: Optional human-readable description. Tooling metadata only.
35
+ """
36
+
37
+ model_config = ConfigDict(frozen=True, extra="forbid")
38
+
39
+ topic: str = Field(
40
+ ...,
41
+ description="Environment-qualified topic string (e.g., 'dev.onex.evt.intent-classified.v1'). "
42
+ "This is the ONLY field used for routing.",
43
+ )
44
+ event_type: str | None = Field(
45
+ default=None,
46
+ description="Optional event model name. Tooling metadata only - never used for routing.",
47
+ )
48
+ message_category: str = Field(
49
+ default="EVENT",
50
+ description="Message category (EVENT, COMMAND, INTENT). "
51
+ "Tooling metadata only - never used for routing.",
52
+ )
53
+ description: str | None = Field(
54
+ default=None,
55
+ description="Optional human-readable description. Tooling metadata only.",
56
+ )
57
+
58
+
59
+ __all__ = ["ModelEventBusTopicEntry"]
@@ -0,0 +1,99 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2025 OmniNode Team
3
+ """Node Event Bus Configuration Model.
4
+
5
+ This module provides the model for a node's resolved event bus configuration,
6
+ containing lists of topics the node subscribes to and publishes to.
7
+
8
+ Key Design Decisions:
9
+ 1. Topics stored as environment-qualified strings (e.g., "dev.onex.evt.intent-classified.v1")
10
+ 2. Metadata fields are tooling-only; routing uses ONLY topic strings
11
+ 3. Property methods extract topic strings only for routing lookups
12
+ 4. Model is frozen (immutable) with extra="forbid" for safety
13
+
14
+ Example:
15
+ >>> from omnibase_infra.models.registration import (
16
+ ... ModelEventBusTopicEntry,
17
+ ... ModelNodeEventBusConfig,
18
+ ... )
19
+ >>> entry = ModelEventBusTopicEntry(
20
+ ... topic="dev.onex.evt.intent-classified.v1",
21
+ ... event_type="ModelIntentClassified",
22
+ ... )
23
+ >>> config = ModelNodeEventBusConfig(subscribe_topics=[entry])
24
+ >>> config.subscribe_topic_strings
25
+ ['dev.onex.evt.intent-classified.v1']
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ from pydantic import BaseModel, ConfigDict, Field
31
+
32
+ from omnibase_infra.models.registration.model_event_bus_topic_entry import (
33
+ ModelEventBusTopicEntry,
34
+ )
35
+
36
+
37
+ class ModelNodeEventBusConfig(BaseModel):
38
+ """Resolved event bus configuration for registry storage.
39
+
40
+ This model holds the resolved, environment-qualified topic strings
41
+ that a node subscribes to and publishes to. It is designed for
42
+ storage in the registry to enable dynamic topic-based routing.
43
+
44
+ The property methods (subscribe_topic_strings, publish_topic_strings)
45
+ extract only the topic strings for routing lookups, ignoring all
46
+ metadata fields.
47
+
48
+ Attributes:
49
+ subscribe_topics: List of topics the node subscribes to.
50
+ publish_topics: List of topics the node publishes to.
51
+
52
+ Example:
53
+ >>> config = ModelNodeEventBusConfig(
54
+ ... subscribe_topics=[
55
+ ... ModelEventBusTopicEntry(topic="dev.onex.evt.input.v1"),
56
+ ... ],
57
+ ... publish_topics=[
58
+ ... ModelEventBusTopicEntry(topic="dev.onex.evt.output.v1"),
59
+ ... ],
60
+ ... )
61
+ >>> config.subscribe_topic_strings
62
+ ['dev.onex.evt.input.v1']
63
+ >>> config.publish_topic_strings
64
+ ['dev.onex.evt.output.v1']
65
+ """
66
+
67
+ model_config = ConfigDict(frozen=True, extra="forbid")
68
+
69
+ subscribe_topics: list[ModelEventBusTopicEntry] = Field(
70
+ default_factory=list,
71
+ description="List of topics the node subscribes to.",
72
+ )
73
+ publish_topics: list[ModelEventBusTopicEntry] = Field(
74
+ default_factory=list,
75
+ description="List of topics the node publishes to.",
76
+ )
77
+
78
+ @property
79
+ def subscribe_topic_strings(self) -> list[str]:
80
+ """Extract topic strings only, for routing lookups.
81
+
82
+ Returns:
83
+ List of environment-qualified topic strings from subscribe_topics.
84
+ Metadata fields are ignored.
85
+ """
86
+ return [entry.topic for entry in self.subscribe_topics]
87
+
88
+ @property
89
+ def publish_topic_strings(self) -> list[str]:
90
+ """Extract topic strings only, for routing lookups.
91
+
92
+ Returns:
93
+ List of environment-qualified topic strings from publish_topics.
94
+ Metadata fields are ignored.
95
+ """
96
+ return [entry.topic for entry in self.publish_topics]
97
+
98
+
99
+ __all__ = ["ModelNodeEventBusConfig"]
@@ -26,6 +26,9 @@ from omnibase_infra.models.discovery.model_introspection_performance_metrics imp
26
26
  from omnibase_infra.models.registration.model_node_capabilities import (
27
27
  ModelNodeCapabilities,
28
28
  )
29
+ from omnibase_infra.models.registration.model_node_event_bus_config import (
30
+ ModelNodeEventBusConfig,
31
+ )
29
32
  from omnibase_infra.models.registration.model_node_metadata import ModelNodeMetadata
30
33
  from omnibase_infra.utils import (
31
34
  validate_endpoint_urls_dict,
@@ -63,6 +66,8 @@ class ModelNodeIntrospectionEvent(BaseModel):
63
66
  deployment_id: Deployment/release identifier.
64
67
  epoch: Registration epoch for ordering.
65
68
  performance_metrics: Optional metrics from introspection operation.
69
+ event_bus: Resolved event bus topic configuration for registry-driven routing.
70
+ If None, node is NOT included in dynamic topic routing lookups.
66
71
 
67
72
  Example:
68
73
  >>> from uuid import uuid4
@@ -179,6 +184,12 @@ class ModelNodeIntrospectionEvent(BaseModel):
179
184
  default=None,
180
185
  description="Performance metrics from introspection operation",
181
186
  )
187
+ event_bus: ModelNodeEventBusConfig | None = Field(
188
+ default=None,
189
+ description="Resolved event bus topic configuration. "
190
+ "Contains environment-qualified topic strings for registry-driven routing. "
191
+ "If None, node is NOT included in dynamic topic routing lookups.",
192
+ )
182
193
 
183
194
 
184
195
  __all__ = ["ModelNodeIntrospectionEvent"]
@@ -27,7 +27,15 @@ from omnibase_infra.models.runtime.model_plugin_load_summary import (
27
27
  ModelPluginLoadSummary,
28
28
  )
29
29
 
30
+ # ModelContractLoadResult and ModelRuntimeContractConfig are exported from
31
+ # omnibase_infra.runtime.models (canonical location for runtime loader models)
32
+ from omnibase_infra.runtime.models import (
33
+ ModelContractLoadResult,
34
+ ModelRuntimeContractConfig,
35
+ )
36
+
30
37
  __all__ = [
38
+ "ModelContractLoadResult",
31
39
  "ModelContractSecurityConfig",
32
40
  "ModelDiscoveryError",
33
41
  "ModelDiscoveryResult",
@@ -37,4 +45,5 @@ __all__ = [
37
45
  "ModelLoadedHandler",
38
46
  "ModelPluginLoadContext",
39
47
  "ModelPluginLoadSummary",
48
+ "ModelRuntimeContractConfig",
40
49
  ]
@@ -13,7 +13,7 @@ and IDE support.
13
13
 
14
14
  from __future__ import annotations
15
15
 
16
- from collections.abc import Mapping
16
+ from collections.abc import Collection, Mapping
17
17
  from uuid import uuid4
18
18
 
19
19
  from pydantic import BaseModel, ConfigDict, Field
@@ -90,7 +90,7 @@ class ModelCoverageMetrics(BaseModel):
90
90
  cls,
91
91
  total: int,
92
92
  registered: int,
93
- unmapped: list[str] | set[str],
93
+ unmapped: Collection[str],
94
94
  ) -> ModelCoverageMetrics:
95
95
  """Create coverage metrics from raw counts.
96
96