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.
- omnibase_infra/constants_topic_patterns.py +26 -0
- omnibase_infra/enums/__init__.py +3 -0
- omnibase_infra/enums/enum_consumer_group_purpose.py +92 -0
- omnibase_infra/enums/enum_handler_source_mode.py +16 -2
- omnibase_infra/errors/__init__.py +4 -0
- omnibase_infra/errors/error_binding_resolution.py +128 -0
- omnibase_infra/event_bus/configs/kafka_event_bus_config.yaml +0 -2
- omnibase_infra/event_bus/event_bus_inmemory.py +64 -10
- omnibase_infra/event_bus/event_bus_kafka.py +105 -47
- omnibase_infra/event_bus/mixin_kafka_broadcast.py +3 -7
- omnibase_infra/event_bus/mixin_kafka_dlq.py +12 -6
- omnibase_infra/event_bus/models/config/model_kafka_event_bus_config.py +0 -81
- omnibase_infra/event_bus/testing/__init__.py +26 -0
- omnibase_infra/event_bus/testing/adapter_protocol_event_publisher_inmemory.py +418 -0
- omnibase_infra/event_bus/testing/model_publisher_metrics.py +64 -0
- omnibase_infra/handlers/handler_consul.py +2 -0
- omnibase_infra/handlers/mixins/__init__.py +5 -0
- omnibase_infra/handlers/mixins/mixin_consul_service.py +274 -10
- omnibase_infra/handlers/mixins/mixin_consul_topic_index.py +585 -0
- omnibase_infra/handlers/models/model_filesystem_config.py +4 -4
- omnibase_infra/migrations/001_create_event_ledger.sql +166 -0
- omnibase_infra/migrations/001_drop_event_ledger.sql +18 -0
- omnibase_infra/mixins/mixin_node_introspection.py +189 -19
- omnibase_infra/models/__init__.py +8 -0
- omnibase_infra/models/bindings/__init__.py +59 -0
- omnibase_infra/models/bindings/constants.py +144 -0
- omnibase_infra/models/bindings/model_binding_resolution_result.py +103 -0
- omnibase_infra/models/bindings/model_operation_binding.py +44 -0
- omnibase_infra/models/bindings/model_operation_bindings_subcontract.py +152 -0
- omnibase_infra/models/bindings/model_parsed_binding.py +52 -0
- omnibase_infra/models/discovery/model_introspection_config.py +25 -17
- omnibase_infra/models/dispatch/__init__.py +8 -0
- omnibase_infra/models/dispatch/model_debug_trace_snapshot.py +114 -0
- omnibase_infra/models/dispatch/model_materialized_dispatch.py +141 -0
- omnibase_infra/models/handlers/model_handler_source_config.py +1 -1
- omnibase_infra/models/model_node_identity.py +126 -0
- omnibase_infra/models/projection/model_snapshot_topic_config.py +3 -2
- omnibase_infra/models/registration/__init__.py +9 -0
- omnibase_infra/models/registration/model_event_bus_topic_entry.py +59 -0
- omnibase_infra/models/registration/model_node_event_bus_config.py +99 -0
- omnibase_infra/models/registration/model_node_introspection_event.py +11 -0
- omnibase_infra/models/runtime/__init__.py +9 -0
- omnibase_infra/models/validation/model_coverage_metrics.py +2 -2
- omnibase_infra/nodes/__init__.py +9 -0
- omnibase_infra/nodes/contract_registry_reducer/__init__.py +29 -0
- omnibase_infra/nodes/contract_registry_reducer/contract.yaml +255 -0
- omnibase_infra/nodes/contract_registry_reducer/models/__init__.py +38 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_contract_registry_state.py +266 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_cleanup_topic_references.py +55 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_deactivate_contract.py +58 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_mark_stale.py +49 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_heartbeat.py +71 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_update_topic.py +66 -0
- omnibase_infra/nodes/contract_registry_reducer/models/model_payload_upsert_contract.py +92 -0
- omnibase_infra/nodes/contract_registry_reducer/node.py +121 -0
- omnibase_infra/nodes/contract_registry_reducer/reducer.py +784 -0
- omnibase_infra/nodes/contract_registry_reducer/registry/__init__.py +9 -0
- omnibase_infra/nodes/contract_registry_reducer/registry/registry_infra_contract_registry_reducer.py +101 -0
- omnibase_infra/nodes/handlers/consul/contract.yaml +85 -0
- omnibase_infra/nodes/handlers/db/contract.yaml +72 -0
- omnibase_infra/nodes/handlers/graph/contract.yaml +127 -0
- omnibase_infra/nodes/handlers/http/contract.yaml +74 -0
- omnibase_infra/nodes/handlers/intent/contract.yaml +66 -0
- omnibase_infra/nodes/handlers/mcp/contract.yaml +69 -0
- omnibase_infra/nodes/handlers/vault/contract.yaml +91 -0
- omnibase_infra/nodes/node_ledger_projection_compute/__init__.py +50 -0
- omnibase_infra/nodes/node_ledger_projection_compute/contract.yaml +104 -0
- omnibase_infra/nodes/node_ledger_projection_compute/node.py +284 -0
- omnibase_infra/nodes/node_ledger_projection_compute/registry/__init__.py +29 -0
- omnibase_infra/nodes/node_ledger_projection_compute/registry/registry_infra_ledger_projection.py +118 -0
- omnibase_infra/nodes/node_ledger_write_effect/__init__.py +82 -0
- omnibase_infra/nodes/node_ledger_write_effect/contract.yaml +200 -0
- omnibase_infra/nodes/node_ledger_write_effect/handlers/__init__.py +22 -0
- omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_append.py +372 -0
- omnibase_infra/nodes/node_ledger_write_effect/handlers/handler_ledger_query.py +597 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/__init__.py +31 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_append_result.py +54 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_entry.py +92 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query.py +53 -0
- omnibase_infra/nodes/node_ledger_write_effect/models/model_ledger_query_result.py +41 -0
- omnibase_infra/nodes/node_ledger_write_effect/node.py +89 -0
- omnibase_infra/nodes/node_ledger_write_effect/protocols/__init__.py +13 -0
- omnibase_infra/nodes/node_ledger_write_effect/protocols/protocol_ledger_persistence.py +127 -0
- omnibase_infra/nodes/node_ledger_write_effect/registry/__init__.py +9 -0
- omnibase_infra/nodes/node_ledger_write_effect/registry/registry_infra_ledger_write.py +121 -0
- omnibase_infra/nodes/node_registration_orchestrator/registry/registry_infra_node_registration_orchestrator.py +7 -5
- omnibase_infra/nodes/reducers/models/__init__.py +7 -2
- omnibase_infra/nodes/reducers/models/model_payload_consul_register.py +11 -0
- omnibase_infra/nodes/reducers/models/model_payload_ledger_append.py +133 -0
- omnibase_infra/nodes/reducers/registration_reducer.py +1 -0
- omnibase_infra/protocols/__init__.py +3 -0
- omnibase_infra/protocols/protocol_dispatch_engine.py +152 -0
- omnibase_infra/runtime/__init__.py +60 -0
- omnibase_infra/runtime/binding_resolver.py +753 -0
- omnibase_infra/runtime/constants_security.py +70 -0
- omnibase_infra/runtime/contract_loaders/__init__.py +9 -0
- omnibase_infra/runtime/contract_loaders/operation_bindings_loader.py +789 -0
- omnibase_infra/runtime/emit_daemon/__init__.py +97 -0
- omnibase_infra/runtime/emit_daemon/cli.py +844 -0
- omnibase_infra/runtime/emit_daemon/client.py +811 -0
- omnibase_infra/runtime/emit_daemon/config.py +535 -0
- omnibase_infra/runtime/emit_daemon/daemon.py +812 -0
- omnibase_infra/runtime/emit_daemon/event_registry.py +477 -0
- omnibase_infra/runtime/emit_daemon/model_daemon_request.py +139 -0
- omnibase_infra/runtime/emit_daemon/model_daemon_response.py +191 -0
- omnibase_infra/runtime/emit_daemon/queue.py +618 -0
- omnibase_infra/runtime/event_bus_subcontract_wiring.py +466 -0
- omnibase_infra/runtime/handler_source_resolver.py +43 -2
- omnibase_infra/runtime/kafka_contract_source.py +984 -0
- omnibase_infra/runtime/models/__init__.py +13 -0
- omnibase_infra/runtime/models/model_contract_load_result.py +224 -0
- omnibase_infra/runtime/models/model_runtime_contract_config.py +268 -0
- omnibase_infra/runtime/models/model_runtime_scheduler_config.py +4 -3
- omnibase_infra/runtime/models/model_security_config.py +109 -0
- omnibase_infra/runtime/publisher_topic_scoped.py +294 -0
- omnibase_infra/runtime/runtime_contract_config_loader.py +406 -0
- omnibase_infra/runtime/service_kernel.py +76 -6
- omnibase_infra/runtime/service_message_dispatch_engine.py +558 -15
- omnibase_infra/runtime/service_runtime_host_process.py +770 -20
- omnibase_infra/runtime/transition_notification_publisher.py +3 -2
- omnibase_infra/runtime/util_wiring.py +206 -62
- omnibase_infra/services/mcp/service_mcp_tool_sync.py +27 -9
- omnibase_infra/services/session/config_consumer.py +25 -8
- omnibase_infra/services/session/config_store.py +2 -2
- omnibase_infra/services/session/consumer.py +1 -1
- omnibase_infra/topics/__init__.py +45 -0
- omnibase_infra/topics/platform_topic_suffixes.py +140 -0
- omnibase_infra/topics/util_topic_composition.py +95 -0
- omnibase_infra/types/typed_dict/__init__.py +9 -1
- omnibase_infra/types/typed_dict/typed_dict_envelope_build_params.py +115 -0
- omnibase_infra/utils/__init__.py +9 -0
- omnibase_infra/utils/util_consumer_group.py +232 -0
- omnibase_infra/validation/infra_validators.py +18 -1
- omnibase_infra/validation/validation_exemptions.yaml +192 -0
- {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/METADATA +3 -3
- {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/RECORD +139 -52
- {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/entry_points.txt +1 -0
- {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/WHEEL +0 -0
- {omnibase_infra-0.2.5.dist-info → omnibase_infra-0.2.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -8,10 +8,16 @@ for HandlerConsul, extracted to reduce class complexity.
|
|
|
8
8
|
Operations:
|
|
9
9
|
- consul.register: Register service with Consul agent
|
|
10
10
|
- consul.deregister: Deregister service from Consul agent
|
|
11
|
+
|
|
12
|
+
Event Bus Integration:
|
|
13
|
+
When the payload contains an 'event_bus_config' field, this mixin will:
|
|
14
|
+
1. Store the event bus configuration in Consul KV
|
|
15
|
+
2. Update the topic -> node_id reverse index for routing
|
|
11
16
|
"""
|
|
12
17
|
|
|
13
18
|
from __future__ import annotations
|
|
14
19
|
|
|
20
|
+
import logging
|
|
15
21
|
from collections.abc import Callable
|
|
16
22
|
from typing import TYPE_CHECKING, Protocol, TypeVar
|
|
17
23
|
from uuid import UUID
|
|
@@ -19,10 +25,12 @@ from uuid import UUID
|
|
|
19
25
|
T = TypeVar("T")
|
|
20
26
|
|
|
21
27
|
from omnibase_core.models.dispatch import ModelHandlerOutput
|
|
22
|
-
from omnibase_infra.
|
|
28
|
+
from omnibase_infra.constants_topic_patterns import TOPIC_NAME_PATTERN
|
|
29
|
+
from omnibase_infra.enums import EnumInfraTransportType, EnumMessageCategory
|
|
23
30
|
from omnibase_infra.errors import (
|
|
24
31
|
InfraConsulError,
|
|
25
32
|
ModelInfraErrorContext,
|
|
33
|
+
ProtocolConfigurationError,
|
|
26
34
|
RuntimeHostError,
|
|
27
35
|
)
|
|
28
36
|
from omnibase_infra.handlers.models.consul import (
|
|
@@ -33,10 +41,16 @@ from omnibase_infra.handlers.models.consul import (
|
|
|
33
41
|
from omnibase_infra.handlers.models.model_consul_handler_response import (
|
|
34
42
|
ModelConsulHandlerResponse,
|
|
35
43
|
)
|
|
44
|
+
from omnibase_infra.models.registration import (
|
|
45
|
+
ModelEventBusTopicEntry,
|
|
46
|
+
ModelNodeEventBusConfig,
|
|
47
|
+
)
|
|
36
48
|
|
|
37
49
|
if TYPE_CHECKING:
|
|
38
50
|
import consul as consul_lib
|
|
39
51
|
|
|
52
|
+
logger = logging.getLogger(__name__)
|
|
53
|
+
|
|
40
54
|
|
|
41
55
|
class ProtocolConsulServiceDependencies(Protocol):
|
|
42
56
|
"""Protocol defining required dependencies for service operations.
|
|
@@ -65,6 +79,24 @@ class ProtocolConsulServiceDependencies(Protocol):
|
|
|
65
79
|
"""Build standardized response."""
|
|
66
80
|
...
|
|
67
81
|
|
|
82
|
+
async def _store_node_event_bus(
|
|
83
|
+
self,
|
|
84
|
+
node_id: str,
|
|
85
|
+
event_bus: ModelNodeEventBusConfig,
|
|
86
|
+
correlation_id: UUID,
|
|
87
|
+
) -> None:
|
|
88
|
+
"""Store event_bus config in Consul KV - provided by MixinConsulTopicIndex."""
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
async def _update_topic_index(
|
|
92
|
+
self,
|
|
93
|
+
node_id: str,
|
|
94
|
+
event_bus: ModelNodeEventBusConfig,
|
|
95
|
+
correlation_id: UUID,
|
|
96
|
+
) -> None:
|
|
97
|
+
"""Update topic index - provided by MixinConsulTopicIndex."""
|
|
98
|
+
...
|
|
99
|
+
|
|
68
100
|
|
|
69
101
|
class MixinConsulService:
|
|
70
102
|
"""Mixin providing Consul service registration operations.
|
|
@@ -102,6 +134,180 @@ class MixinConsulService:
|
|
|
102
134
|
"""Build standardized response - provided by host class."""
|
|
103
135
|
raise NotImplementedError("Must be provided by implementing class") # type: ignore[return-value]
|
|
104
136
|
|
|
137
|
+
async def _store_node_event_bus(
|
|
138
|
+
self,
|
|
139
|
+
node_id: str,
|
|
140
|
+
event_bus: ModelNodeEventBusConfig,
|
|
141
|
+
correlation_id: UUID,
|
|
142
|
+
) -> None:
|
|
143
|
+
"""Store event_bus config - provided by MixinConsulTopicIndex."""
|
|
144
|
+
raise NotImplementedError("Must be provided by implementing class")
|
|
145
|
+
|
|
146
|
+
async def _update_topic_index(
|
|
147
|
+
self,
|
|
148
|
+
node_id: str,
|
|
149
|
+
event_bus: ModelNodeEventBusConfig,
|
|
150
|
+
correlation_id: UUID,
|
|
151
|
+
) -> None:
|
|
152
|
+
"""Update topic index - provided by MixinConsulTopicIndex."""
|
|
153
|
+
raise NotImplementedError("Must be provided by implementing class")
|
|
154
|
+
|
|
155
|
+
def _validate_topic_entry(
|
|
156
|
+
self,
|
|
157
|
+
entry: dict[str, object],
|
|
158
|
+
location: str,
|
|
159
|
+
correlation_id: UUID,
|
|
160
|
+
) -> tuple[str, str]:
|
|
161
|
+
"""Validate a single topic entry and return sanitized values.
|
|
162
|
+
|
|
163
|
+
Validates:
|
|
164
|
+
1. Topic is a non-empty string after stripping whitespace
|
|
165
|
+
2. Topic format matches TOPIC_NAME_PATTERN (alphanumeric, dots, underscores, hyphens)
|
|
166
|
+
3. message_category is a valid EnumMessageCategory value
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
entry: The full topic entry dict containing 'topic' and optional 'message_category'.
|
|
170
|
+
location: Location string for error messages (e.g., "subscribe_topics[0]").
|
|
171
|
+
correlation_id: Correlation ID for tracing.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Tuple of (stripped_topic, validated_message_category).
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
ProtocolConfigurationError: If validation fails.
|
|
178
|
+
"""
|
|
179
|
+
raw_topic = entry.get("topic")
|
|
180
|
+
|
|
181
|
+
# Validate topic is a non-empty string BEFORE any coercion
|
|
182
|
+
if not isinstance(raw_topic, str) or not raw_topic.strip():
|
|
183
|
+
ctx = ModelInfraErrorContext.with_correlation(
|
|
184
|
+
correlation_id=correlation_id,
|
|
185
|
+
transport_type=EnumInfraTransportType.CONSUL,
|
|
186
|
+
operation="parse_event_bus_config",
|
|
187
|
+
)
|
|
188
|
+
raise ProtocolConfigurationError(
|
|
189
|
+
f"Invalid or missing 'topic' in {location}: "
|
|
190
|
+
f"expected non-empty string, got {type(raw_topic).__name__}",
|
|
191
|
+
context=ctx,
|
|
192
|
+
parameter=f"{location}.topic",
|
|
193
|
+
value=str(raw_topic) if raw_topic is not None else "None",
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
stripped_topic = raw_topic.strip()
|
|
197
|
+
|
|
198
|
+
# Validate topic format (fail fast before storage/indexing)
|
|
199
|
+
if not TOPIC_NAME_PATTERN.match(stripped_topic):
|
|
200
|
+
ctx = ModelInfraErrorContext.with_correlation(
|
|
201
|
+
correlation_id=correlation_id,
|
|
202
|
+
transport_type=EnumInfraTransportType.CONSUL,
|
|
203
|
+
operation="parse_event_bus_config",
|
|
204
|
+
)
|
|
205
|
+
raise ProtocolConfigurationError(
|
|
206
|
+
f"Topic '{stripped_topic}' in {location} "
|
|
207
|
+
"contains invalid characters. Only alphanumeric characters, periods (.), "
|
|
208
|
+
"underscores (_), and hyphens (-) are allowed.",
|
|
209
|
+
context=ctx,
|
|
210
|
+
parameter=f"{location}.topic",
|
|
211
|
+
value=stripped_topic,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Validate message_category if provided
|
|
215
|
+
raw_category = entry.get("message_category", "EVENT")
|
|
216
|
+
if isinstance(raw_category, str):
|
|
217
|
+
category_upper = raw_category.upper()
|
|
218
|
+
else:
|
|
219
|
+
category_upper = str(raw_category).upper()
|
|
220
|
+
|
|
221
|
+
# Valid categories: EVENT, COMMAND, INTENT (case-insensitive)
|
|
222
|
+
valid_categories = {
|
|
223
|
+
cat.value.upper(): cat.value.upper() for cat in EnumMessageCategory
|
|
224
|
+
}
|
|
225
|
+
if category_upper not in valid_categories:
|
|
226
|
+
ctx = ModelInfraErrorContext.with_correlation(
|
|
227
|
+
correlation_id=correlation_id,
|
|
228
|
+
transport_type=EnumInfraTransportType.CONSUL,
|
|
229
|
+
operation="parse_event_bus_config",
|
|
230
|
+
)
|
|
231
|
+
raise ProtocolConfigurationError(
|
|
232
|
+
f"Invalid 'message_category' in {location}: "
|
|
233
|
+
f"'{raw_category}'. Valid values are: {', '.join(sorted(valid_categories.keys()))}",
|
|
234
|
+
context=ctx,
|
|
235
|
+
parameter=f"{location}.message_category",
|
|
236
|
+
value=str(raw_category),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
return stripped_topic, category_upper
|
|
240
|
+
|
|
241
|
+
def _parse_event_bus_config(
|
|
242
|
+
self,
|
|
243
|
+
event_bus_data: dict[str, object],
|
|
244
|
+
correlation_id: UUID,
|
|
245
|
+
) -> ModelNodeEventBusConfig:
|
|
246
|
+
"""Parse event_bus_config from payload dict to typed model.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
event_bus_data: Raw event_bus_config dict from payload.
|
|
250
|
+
correlation_id: Correlation ID for tracing.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Parsed ModelNodeEventBusConfig instance.
|
|
254
|
+
|
|
255
|
+
Raises:
|
|
256
|
+
ProtocolConfigurationError: If any topic entry has an invalid topic,
|
|
257
|
+
invalid format, or invalid message_category.
|
|
258
|
+
"""
|
|
259
|
+
subscribe_topics: list[ModelEventBusTopicEntry] = []
|
|
260
|
+
publish_topics: list[ModelEventBusTopicEntry] = []
|
|
261
|
+
|
|
262
|
+
raw_subscribe = event_bus_data.get("subscribe_topics")
|
|
263
|
+
if isinstance(raw_subscribe, list):
|
|
264
|
+
for idx, entry in enumerate(raw_subscribe):
|
|
265
|
+
if isinstance(entry, dict):
|
|
266
|
+
stripped_topic, message_category = self._validate_topic_entry(
|
|
267
|
+
entry=entry,
|
|
268
|
+
location=f"subscribe_topics[{idx}]",
|
|
269
|
+
correlation_id=correlation_id,
|
|
270
|
+
)
|
|
271
|
+
subscribe_topics.append(
|
|
272
|
+
ModelEventBusTopicEntry(
|
|
273
|
+
topic=stripped_topic,
|
|
274
|
+
event_type=entry.get("event_type")
|
|
275
|
+
if isinstance(entry.get("event_type"), str)
|
|
276
|
+
else None,
|
|
277
|
+
message_category=message_category,
|
|
278
|
+
description=entry.get("description")
|
|
279
|
+
if isinstance(entry.get("description"), str)
|
|
280
|
+
else None,
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
raw_publish = event_bus_data.get("publish_topics")
|
|
285
|
+
if isinstance(raw_publish, list):
|
|
286
|
+
for idx, entry in enumerate(raw_publish):
|
|
287
|
+
if isinstance(entry, dict):
|
|
288
|
+
stripped_topic, message_category = self._validate_topic_entry(
|
|
289
|
+
entry=entry,
|
|
290
|
+
location=f"publish_topics[{idx}]",
|
|
291
|
+
correlation_id=correlation_id,
|
|
292
|
+
)
|
|
293
|
+
publish_topics.append(
|
|
294
|
+
ModelEventBusTopicEntry(
|
|
295
|
+
topic=stripped_topic,
|
|
296
|
+
event_type=entry.get("event_type")
|
|
297
|
+
if isinstance(entry.get("event_type"), str)
|
|
298
|
+
else None,
|
|
299
|
+
message_category=message_category,
|
|
300
|
+
description=entry.get("description")
|
|
301
|
+
if isinstance(entry.get("description"), str)
|
|
302
|
+
else None,
|
|
303
|
+
)
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
return ModelNodeEventBusConfig(
|
|
307
|
+
subscribe_topics=subscribe_topics,
|
|
308
|
+
publish_topics=publish_topics,
|
|
309
|
+
)
|
|
310
|
+
|
|
105
311
|
async def _register_service(
|
|
106
312
|
self,
|
|
107
313
|
payload: dict[str, object],
|
|
@@ -118,23 +324,35 @@ class MixinConsulService:
|
|
|
118
324
|
- port: Optional service port
|
|
119
325
|
- tags: Optional list of tags
|
|
120
326
|
- check: Optional health check configuration dict
|
|
327
|
+
- node_id: Optional node ID for event bus registration
|
|
328
|
+
- event_bus_config: Optional event bus configuration dict containing:
|
|
329
|
+
- subscribe_topics: List of topic entries to subscribe to
|
|
330
|
+
- publish_topics: List of topic entries to publish to
|
|
121
331
|
correlation_id: Correlation ID for tracing
|
|
122
332
|
input_envelope_id: Input envelope ID for causality tracking
|
|
123
333
|
|
|
124
334
|
Returns:
|
|
125
335
|
ModelHandlerOutput wrapping the registration result with correlation tracking
|
|
336
|
+
|
|
337
|
+
Event Bus Integration:
|
|
338
|
+
When event_bus_config is provided along with node_id, this method will:
|
|
339
|
+
1. Store the event bus configuration in Consul KV at onex/nodes/{node_id}/event_bus/
|
|
340
|
+
2. Update the topic -> node_id reverse index at onex/topics/{topic}/subscribers
|
|
126
341
|
"""
|
|
127
342
|
name = payload.get("name")
|
|
128
343
|
if not isinstance(name, str) or not name:
|
|
129
|
-
ctx = ModelInfraErrorContext(
|
|
344
|
+
ctx = ModelInfraErrorContext.with_correlation(
|
|
345
|
+
correlation_id=correlation_id,
|
|
130
346
|
transport_type=EnumInfraTransportType.CONSUL,
|
|
131
347
|
operation="consul.register",
|
|
132
348
|
target_name="consul_handler",
|
|
133
|
-
correlation_id=correlation_id,
|
|
134
349
|
)
|
|
135
|
-
raise
|
|
136
|
-
"Missing or invalid 'name' in payload"
|
|
350
|
+
raise ProtocolConfigurationError(
|
|
351
|
+
"Missing or invalid 'name' in payload: "
|
|
352
|
+
f"expected non-empty string, got {type(name).__name__}",
|
|
137
353
|
context=ctx,
|
|
354
|
+
parameter="name",
|
|
355
|
+
value=str(name) if name is not None else "None",
|
|
138
356
|
)
|
|
139
357
|
|
|
140
358
|
service_id = payload.get("service_id")
|
|
@@ -198,6 +416,49 @@ class MixinConsulService:
|
|
|
198
416
|
correlation_id,
|
|
199
417
|
)
|
|
200
418
|
|
|
419
|
+
# Handle event bus configuration if provided
|
|
420
|
+
event_bus_data = payload.get("event_bus_config")
|
|
421
|
+
node_id = payload.get("node_id")
|
|
422
|
+
|
|
423
|
+
# Fail fast: if event_bus_config is present, node_id is REQUIRED
|
|
424
|
+
if isinstance(event_bus_data, dict):
|
|
425
|
+
if not isinstance(node_id, str) or not node_id.strip():
|
|
426
|
+
ctx = ModelInfraErrorContext.with_correlation(
|
|
427
|
+
correlation_id=correlation_id,
|
|
428
|
+
transport_type=EnumInfraTransportType.CONSUL,
|
|
429
|
+
operation="consul.register",
|
|
430
|
+
)
|
|
431
|
+
raise ProtocolConfigurationError(
|
|
432
|
+
"event_bus_config requires a valid 'node_id': "
|
|
433
|
+
f"expected non-empty string, got {type(node_id).__name__}",
|
|
434
|
+
context=ctx,
|
|
435
|
+
parameter="node_id",
|
|
436
|
+
value=str(node_id) if node_id is not None else "None",
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
logger.info(
|
|
440
|
+
"Processing event_bus_config for node %s",
|
|
441
|
+
node_id,
|
|
442
|
+
extra={"correlation_id": str(correlation_id), "node_id": node_id},
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
# Parse the event bus config
|
|
446
|
+
event_bus = self._parse_event_bus_config(event_bus_data, correlation_id)
|
|
447
|
+
|
|
448
|
+
# Update topic index FIRST (uses old topics from previous registration)
|
|
449
|
+
# This computes delta and updates reverse index
|
|
450
|
+
await self._update_topic_index(node_id, event_bus, correlation_id)
|
|
451
|
+
|
|
452
|
+
# Store the new event bus config AFTER index update
|
|
453
|
+
# Order matters: _update_topic_index reads old topics before we overwrite
|
|
454
|
+
await self._store_node_event_bus(node_id, event_bus, correlation_id)
|
|
455
|
+
|
|
456
|
+
logger.info(
|
|
457
|
+
"Completed event_bus registration for node %s",
|
|
458
|
+
node_id,
|
|
459
|
+
extra={"correlation_id": str(correlation_id), "node_id": node_id},
|
|
460
|
+
)
|
|
461
|
+
|
|
201
462
|
typed_payload = ModelConsulRegisterPayload(
|
|
202
463
|
registered=True,
|
|
203
464
|
name=name,
|
|
@@ -223,16 +484,19 @@ class MixinConsulService:
|
|
|
223
484
|
ModelHandlerOutput wrapping the deregistration result with correlation tracking
|
|
224
485
|
"""
|
|
225
486
|
service_id = payload.get("service_id")
|
|
226
|
-
if not isinstance(service_id, str) or not service_id:
|
|
227
|
-
ctx = ModelInfraErrorContext(
|
|
487
|
+
if not isinstance(service_id, str) or not service_id.strip():
|
|
488
|
+
ctx = ModelInfraErrorContext.with_correlation(
|
|
489
|
+
correlation_id=correlation_id,
|
|
228
490
|
transport_type=EnumInfraTransportType.CONSUL,
|
|
229
491
|
operation="consul.deregister",
|
|
230
492
|
target_name="consul_handler",
|
|
231
|
-
correlation_id=correlation_id,
|
|
232
493
|
)
|
|
233
|
-
raise
|
|
234
|
-
"Missing or invalid 'service_id' in payload"
|
|
494
|
+
raise ProtocolConfigurationError(
|
|
495
|
+
"Missing or invalid 'service_id' in payload: "
|
|
496
|
+
f"expected non-empty string, got {type(service_id).__name__}",
|
|
235
497
|
context=ctx,
|
|
498
|
+
parameter="service_id",
|
|
499
|
+
value=str(service_id) if service_id is not None else "None",
|
|
236
500
|
)
|
|
237
501
|
|
|
238
502
|
if self._client is None:
|