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
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2025 OmniNode Team
|
|
3
|
+
"""Registry API Routes.
|
|
4
|
+
|
|
5
|
+
FastAPI route handlers for the Registry API. Routes are defined as an
|
|
6
|
+
APIRouter for easy mounting into the main FastAPI application.
|
|
7
|
+
|
|
8
|
+
Endpoint Summary:
|
|
9
|
+
GET /registry/discovery - Full dashboard payload
|
|
10
|
+
GET /registry/nodes - Node list with pagination
|
|
11
|
+
GET /registry/nodes/{id} - Single node detail
|
|
12
|
+
GET /registry/instances - Live Consul instances
|
|
13
|
+
GET /registry/widgets/mapping - Widget mapping configuration
|
|
14
|
+
GET /registry/health - Service health check
|
|
15
|
+
|
|
16
|
+
Related Tickets:
|
|
17
|
+
- OMN-1278: Contract-Driven Dashboard - Registry Discovery
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from typing import TYPE_CHECKING, Annotated
|
|
23
|
+
from uuid import UUID, uuid4
|
|
24
|
+
|
|
25
|
+
from fastapi import APIRouter, Depends, Header, HTTPException, Query, Request, status
|
|
26
|
+
|
|
27
|
+
from omnibase_infra.enums import EnumRegistrationState
|
|
28
|
+
from omnibase_infra.services.registry_api.models import (
|
|
29
|
+
ModelRegistryDiscoveryResponse,
|
|
30
|
+
ModelRegistryHealthResponse,
|
|
31
|
+
ModelRegistryNodeView,
|
|
32
|
+
ModelResponseListInstances,
|
|
33
|
+
ModelResponseListNodes,
|
|
34
|
+
ModelWidgetMapping,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from omnibase_infra.services.registry_api.service import ServiceRegistryDiscovery
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_correlation_id(
|
|
42
|
+
x_correlation_id: Annotated[
|
|
43
|
+
str | None,
|
|
44
|
+
Header(
|
|
45
|
+
alias="X-Correlation-ID",
|
|
46
|
+
description="Correlation ID for distributed tracing. Must be a valid UUID if provided.",
|
|
47
|
+
),
|
|
48
|
+
] = None,
|
|
49
|
+
) -> UUID:
|
|
50
|
+
"""FastAPI dependency to extract and validate correlation ID from HTTP header.
|
|
51
|
+
|
|
52
|
+
Extracts the X-Correlation-ID header value and validates it as a UUID.
|
|
53
|
+
If no header is provided, generates a new UUID for the request.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
x_correlation_id: Optional correlation ID string from X-Correlation-ID header.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Parsed UUID from header or newly generated UUID if not provided.
|
|
60
|
+
|
|
61
|
+
Raises:
|
|
62
|
+
HTTPException: 400 Bad Request if correlation ID is provided but not a valid UUID.
|
|
63
|
+
|
|
64
|
+
Example:
|
|
65
|
+
Valid header: X-Correlation-ID: 550e8400-e29b-41d4-a716-446655440000
|
|
66
|
+
Invalid header: X-Correlation-ID: not-a-uuid (returns 400)
|
|
67
|
+
Missing header: Generates new UUID automatically
|
|
68
|
+
"""
|
|
69
|
+
if x_correlation_id is None:
|
|
70
|
+
return uuid4()
|
|
71
|
+
try:
|
|
72
|
+
return UUID(x_correlation_id)
|
|
73
|
+
except ValueError:
|
|
74
|
+
raise HTTPException(
|
|
75
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
76
|
+
detail=f"Invalid X-Correlation-ID header format: '{x_correlation_id}'. Must be a valid UUID (e.g., '550e8400-e29b-41d4-a716-446655440000').",
|
|
77
|
+
) from None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Create router with prefix
|
|
81
|
+
router = APIRouter(
|
|
82
|
+
prefix="/registry",
|
|
83
|
+
tags=["registry"],
|
|
84
|
+
responses={
|
|
85
|
+
400: {"description": "Bad request (e.g., invalid correlation ID format)"},
|
|
86
|
+
500: {"description": "Internal server error"},
|
|
87
|
+
503: {"description": "Service unavailable"},
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_service(request: Request) -> ServiceRegistryDiscovery:
|
|
93
|
+
"""Dependency to get the registry discovery service from app state.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
request: FastAPI request object.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
ServiceRegistryDiscovery instance from app state.
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
HTTPException: If service is not configured in app state.
|
|
103
|
+
"""
|
|
104
|
+
service: ServiceRegistryDiscovery | None = getattr(
|
|
105
|
+
request.app.state, "registry_service", None
|
|
106
|
+
)
|
|
107
|
+
if service is None:
|
|
108
|
+
raise HTTPException(
|
|
109
|
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
110
|
+
detail="Registry service not configured",
|
|
111
|
+
)
|
|
112
|
+
return service
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@router.get(
|
|
116
|
+
"/discovery",
|
|
117
|
+
response_model=ModelRegistryDiscoveryResponse,
|
|
118
|
+
summary="Full Dashboard Payload",
|
|
119
|
+
description=(
|
|
120
|
+
"Returns the complete dashboard payload including nodes, live instances, "
|
|
121
|
+
"and summary statistics. This is the primary endpoint for dashboard "
|
|
122
|
+
"consumption, providing all needed data in a single request."
|
|
123
|
+
),
|
|
124
|
+
responses={
|
|
125
|
+
400: {"description": "Bad request (e.g., invalid correlation ID format)"},
|
|
126
|
+
200: {
|
|
127
|
+
"description": "Successful response with full discovery data",
|
|
128
|
+
"content": {
|
|
129
|
+
"application/json": {
|
|
130
|
+
"example": {
|
|
131
|
+
"timestamp": "2025-01-21T10:00:00Z",
|
|
132
|
+
"warnings": [],
|
|
133
|
+
"summary": {
|
|
134
|
+
"total_nodes": 10,
|
|
135
|
+
"active_nodes": 8,
|
|
136
|
+
"healthy_instances": 5,
|
|
137
|
+
"unhealthy_instances": 2,
|
|
138
|
+
"by_node_type": {"EFFECT": 5, "COMPUTE": 3, "REDUCER": 2},
|
|
139
|
+
"by_state": {"active": 8, "pending_registration": 2},
|
|
140
|
+
},
|
|
141
|
+
"nodes": [],
|
|
142
|
+
"live_instances": [],
|
|
143
|
+
"pagination": {
|
|
144
|
+
"total": 10,
|
|
145
|
+
"limit": 100,
|
|
146
|
+
"offset": 0,
|
|
147
|
+
"has_more": False,
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
)
|
|
155
|
+
async def get_discovery(
|
|
156
|
+
service: Annotated[ServiceRegistryDiscovery, Depends(get_service)],
|
|
157
|
+
correlation_id: Annotated[UUID, Depends(get_correlation_id)],
|
|
158
|
+
limit: Annotated[
|
|
159
|
+
int,
|
|
160
|
+
Query(ge=1, le=1000, description="Maximum number of nodes to return"),
|
|
161
|
+
] = 100,
|
|
162
|
+
offset: Annotated[
|
|
163
|
+
int,
|
|
164
|
+
Query(ge=0, description="Number of nodes to skip for pagination"),
|
|
165
|
+
] = 0,
|
|
166
|
+
) -> ModelRegistryDiscoveryResponse:
|
|
167
|
+
"""Get full dashboard payload with nodes, instances, and summary."""
|
|
168
|
+
response = await service.get_discovery(
|
|
169
|
+
limit=limit,
|
|
170
|
+
offset=offset,
|
|
171
|
+
correlation_id=correlation_id,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return response
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@router.get(
|
|
178
|
+
"/nodes",
|
|
179
|
+
response_model=ModelResponseListNodes,
|
|
180
|
+
summary="List Registered Nodes",
|
|
181
|
+
description=(
|
|
182
|
+
"Returns a paginated list of registered nodes from the PostgreSQL "
|
|
183
|
+
"projection store. Supports filtering by state and node type."
|
|
184
|
+
),
|
|
185
|
+
responses={
|
|
186
|
+
400: {"description": "Bad request (e.g., invalid correlation ID format)"},
|
|
187
|
+
200: {
|
|
188
|
+
"description": "Successful response with node list",
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
)
|
|
192
|
+
async def list_nodes(
|
|
193
|
+
service: Annotated[ServiceRegistryDiscovery, Depends(get_service)],
|
|
194
|
+
correlation_id: Annotated[UUID, Depends(get_correlation_id)],
|
|
195
|
+
limit: Annotated[
|
|
196
|
+
int,
|
|
197
|
+
Query(ge=1, le=1000, description="Maximum number of nodes to return"),
|
|
198
|
+
] = 100,
|
|
199
|
+
offset: Annotated[
|
|
200
|
+
int,
|
|
201
|
+
Query(ge=0, description="Number of nodes to skip for pagination"),
|
|
202
|
+
] = 0,
|
|
203
|
+
state: Annotated[
|
|
204
|
+
str | None,
|
|
205
|
+
Query(
|
|
206
|
+
description="Filter by registration state (e.g., 'active', 'pending_registration')"
|
|
207
|
+
),
|
|
208
|
+
] = None,
|
|
209
|
+
node_type: Annotated[
|
|
210
|
+
str | None,
|
|
211
|
+
Query(
|
|
212
|
+
description="Filter by node type (effect, compute, reducer, orchestrator)"
|
|
213
|
+
),
|
|
214
|
+
] = None,
|
|
215
|
+
) -> ModelResponseListNodes:
|
|
216
|
+
"""List registered nodes with pagination and optional filtering."""
|
|
217
|
+
# Parse state filter
|
|
218
|
+
state_filter: EnumRegistrationState | None = None
|
|
219
|
+
if state is not None:
|
|
220
|
+
try:
|
|
221
|
+
state_filter = EnumRegistrationState(state)
|
|
222
|
+
except ValueError:
|
|
223
|
+
raise HTTPException(
|
|
224
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
225
|
+
detail=f"Invalid state value: {state}. Valid values: {[s.value for s in EnumRegistrationState]}",
|
|
226
|
+
) from None
|
|
227
|
+
|
|
228
|
+
nodes, pagination, warnings = await service.list_nodes(
|
|
229
|
+
limit=limit,
|
|
230
|
+
offset=offset,
|
|
231
|
+
state=state_filter,
|
|
232
|
+
node_type=node_type,
|
|
233
|
+
correlation_id=correlation_id,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
return ModelResponseListNodes(
|
|
237
|
+
nodes=nodes,
|
|
238
|
+
pagination=pagination,
|
|
239
|
+
warnings=warnings,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@router.get(
|
|
244
|
+
"/nodes/{node_id}",
|
|
245
|
+
response_model=ModelRegistryNodeView,
|
|
246
|
+
summary="Get Node Details",
|
|
247
|
+
description="Returns detailed information for a single registered node by ID.",
|
|
248
|
+
responses={
|
|
249
|
+
400: {"description": "Bad request (e.g., invalid correlation ID format)"},
|
|
250
|
+
200: {"description": "Successful response with node details"},
|
|
251
|
+
404: {"description": "Node not found"},
|
|
252
|
+
},
|
|
253
|
+
)
|
|
254
|
+
async def get_node(
|
|
255
|
+
node_id: UUID,
|
|
256
|
+
service: Annotated[ServiceRegistryDiscovery, Depends(get_service)],
|
|
257
|
+
correlation_id: Annotated[UUID, Depends(get_correlation_id)],
|
|
258
|
+
) -> ModelRegistryNodeView:
|
|
259
|
+
"""Get a single node by ID."""
|
|
260
|
+
|
|
261
|
+
node, warnings = await service.get_node(
|
|
262
|
+
node_id=node_id,
|
|
263
|
+
correlation_id=correlation_id,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
if node is None:
|
|
267
|
+
# Check if it was a service error or genuinely not found
|
|
268
|
+
if warnings:
|
|
269
|
+
raise HTTPException(
|
|
270
|
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
271
|
+
detail=f"Service error: {warnings[0].message}",
|
|
272
|
+
)
|
|
273
|
+
raise HTTPException(
|
|
274
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
275
|
+
detail=f"Node not found: {node_id}",
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
return node
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
@router.get(
|
|
282
|
+
"/instances",
|
|
283
|
+
response_model=ModelResponseListInstances,
|
|
284
|
+
summary="List Live Consul Instances",
|
|
285
|
+
description=(
|
|
286
|
+
"Returns a list of live service instances from Consul. "
|
|
287
|
+
"Includes health status and metadata for each instance."
|
|
288
|
+
),
|
|
289
|
+
responses={
|
|
290
|
+
400: {"description": "Bad request (e.g., invalid correlation ID format)"},
|
|
291
|
+
200: {"description": "Successful response with instance list"},
|
|
292
|
+
},
|
|
293
|
+
)
|
|
294
|
+
async def list_instances(
|
|
295
|
+
service: Annotated[ServiceRegistryDiscovery, Depends(get_service)],
|
|
296
|
+
correlation_id: Annotated[UUID, Depends(get_correlation_id)],
|
|
297
|
+
service_name: Annotated[
|
|
298
|
+
str | None,
|
|
299
|
+
Query(description="Filter by service name"),
|
|
300
|
+
] = None,
|
|
301
|
+
include_unhealthy: Annotated[
|
|
302
|
+
bool,
|
|
303
|
+
Query(description="Include unhealthy instances in results"),
|
|
304
|
+
] = False,
|
|
305
|
+
) -> ModelResponseListInstances:
|
|
306
|
+
"""List live Consul service instances."""
|
|
307
|
+
instances, warnings = await service.list_instances(
|
|
308
|
+
service_name=service_name,
|
|
309
|
+
include_unhealthy=include_unhealthy,
|
|
310
|
+
correlation_id=correlation_id,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
return ModelResponseListInstances(
|
|
314
|
+
instances=instances,
|
|
315
|
+
warnings=warnings,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@router.get(
|
|
320
|
+
"/widgets/mapping",
|
|
321
|
+
response_model=ModelWidgetMapping,
|
|
322
|
+
summary="Widget Mapping Configuration",
|
|
323
|
+
description=(
|
|
324
|
+
"Returns the capability-to-widget mapping configuration. "
|
|
325
|
+
"Used by dashboards to determine which widget type to render "
|
|
326
|
+
"for each node capability."
|
|
327
|
+
),
|
|
328
|
+
responses={
|
|
329
|
+
200: {"description": "Successful response with widget mapping"},
|
|
330
|
+
503: {"description": "Configuration not available"},
|
|
331
|
+
},
|
|
332
|
+
)
|
|
333
|
+
async def get_widget_mapping(
|
|
334
|
+
service: Annotated[ServiceRegistryDiscovery, Depends(get_service)],
|
|
335
|
+
) -> ModelWidgetMapping:
|
|
336
|
+
"""Get widget mapping configuration."""
|
|
337
|
+
mapping, warnings = service.get_widget_mapping()
|
|
338
|
+
|
|
339
|
+
if mapping is None:
|
|
340
|
+
raise HTTPException(
|
|
341
|
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
342
|
+
detail=f"Widget mapping not available: {warnings[0].message if warnings else 'Unknown error'}",
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
return mapping
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@router.get(
|
|
349
|
+
"/health",
|
|
350
|
+
response_model=ModelRegistryHealthResponse,
|
|
351
|
+
summary="Service Health Check",
|
|
352
|
+
description=(
|
|
353
|
+
"Performs a health check on all backend components (PostgreSQL, Consul, config) "
|
|
354
|
+
"and returns the overall service health status."
|
|
355
|
+
),
|
|
356
|
+
responses={
|
|
357
|
+
400: {"description": "Bad request (e.g., invalid correlation ID format)"},
|
|
358
|
+
200: {"description": "Health check response (may indicate degraded/unhealthy)"},
|
|
359
|
+
},
|
|
360
|
+
)
|
|
361
|
+
async def health_check(
|
|
362
|
+
service: Annotated[ServiceRegistryDiscovery, Depends(get_service)],
|
|
363
|
+
correlation_id: Annotated[UUID, Depends(get_correlation_id)],
|
|
364
|
+
) -> ModelRegistryHealthResponse:
|
|
365
|
+
"""Perform health check on all backend components."""
|
|
366
|
+
response = await service.health_check(correlation_id=correlation_id)
|
|
367
|
+
|
|
368
|
+
return response
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
__all__ = ["router", "get_service", "get_correlation_id"]
|