{"record":{"id":"90cfd88e7d4f2d2c","repo":"astrid-runtime/astrid","slug":"gateway-is-not-wired-to-a-live-event-bus-model-re","errorCode":null,"errorMessage":"gateway is not wired to a live event bus; model registry unavailable","messagePattern":"gateway is not wired to a live event bus; model registry unavailable","errorType":"http","errorClass":"GatewayError::Internal","httpStatus":500,"severity":"error","filePath":"crates/astrid-gateway/src/routes/models.rs","lineNumber":198,"sourceCode":"///\n/// `corr_id` is the set-path correlation filter (see\n/// [`reply_satisfies_corr_id`]). When `Some`, the reply-draining loop SKIPS\n/// any reply whose `corr_id` is present and differs from ours — that reply\n/// belongs to another concurrent same-principal SET — and keeps receiving\n/// (within the same overall timeout budget) until a matching / un-correlated\n/// reply arrives or the budget is spent. When `None` (the GET paths) the\n/// first trusted-source reply on the scoped route is taken.\nasync fn registry_round_trip(\n    state: &GatewayState,\n    principal_id: &PrincipalId,\n    _workspace: &WorkspaceContext,\n    request_topic: &'static str,\n    response_topic: &'static str,\n    payload: serde_json::Value,\n    corr_id: Option<&str>,\n) -> GatewayResult<serde_json::Value> {\n    let Some(bus) = state.event_bus.clone() else {\n        return Err(GatewayError::Internal(anyhow::anyhow!(\n            \"gateway is not wired to a live event bus; model registry unavailable\"\n        )));\n    };\n\n    let principal = principal_id.to_string();\n    ensure_registry_request_subscribed(state, principal_id, request_topic).await?;\n    let expected_source_ids = provider_source_ids(state, principal_id, request_topic).await?;\n\n    // Subscribe FIRST, then publish. Reverse order would race a fast\n    // registry reply — the reply could land before subscribe returns and\n    // we'd miss it. The route is scoped to `Some(Some(principal))`: the\n    // outer `Some` marks the route as scoped, the inner `Some(principal)`\n    // is the security boundary — a reply stamped with any other principal\n    // is dropped at enqueue and never enters this route's budget. A fresh\n    // per-call UUID isolates this connection's route from any concurrent\n    // model request.\n    let mut reply_rx = bus.subscribe_topic_routed_scoped(\n        Uuid::new_v4(),","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/routes/models.rs#L180-L216","documentation":"`registry_round_trip` requires the gateway `AppState` to have a live event bus (`state.event_bus`). If the bus is `None`, the function immediately returns `GatewayError::Internal` with the message 'gateway is not wired to a live event bus; model registry unavailable'. This is a wiring/deployment problem: model list/get/set endpoints cannot function without an event bus.","triggerScenarios":"Calling any model registry endpoint (list models, get/set active model) on a gateway instance constructed without an event bus — e.g. a test/standalone build or a config path where the bus was never attached to state.","commonSituations":"Gateway started with event-bus wiring disabled or misconfigured; running a slim/CLI mode that never constructs the bus; tests using a stub state without `event_bus`; initialization order bug where the bus failed to start and was left `None`.","solutions":["Check gateway startup config and logs for event-bus initialization; fix the wiring so `state.event_bus` is populated.","If intentionally running without a bus, do not expose the model registry routes.","Verify the bus connection (broker/socket) is created before routes are served.","In tests, provide a real or mock event bus in the app state instead of `None`."],"exampleFix":"// before\nlet Some(bus) = state.event_bus.clone() else {\n    return Err(GatewayError::Internal(anyhow::anyhow!(\n        \"gateway is not wired to a live event bus; model registry unavailable\"\n    )));\n};\n// after\nlet Some(bus) = state.event_bus.clone() else {\n    tracing::error!(\"event bus missing from app state; check startup wiring\");\n    return Err(GatewayError::ServiceUnavailable(\n        \"model registry unavailable: event bus not configured\".into(),\n    ));\n};","handlingStrategy":"validation","validationCode":"pub fn registry_available(state: &AppState) -> bool {\n    state.event_bus.is_some()\n}\n// gate the routes at router-build time\nif !registry_available(&state) { skip_model_routes(); }","typeGuard":"fn has_event_bus(state: &AppState) -> bool {\n    matches!(state.event_bus, Some(_))\n}","tryCatchPattern":"match list_models(&state, caller).await {\n    Err(e) if e.message().contains(\"not wired to a live event bus\") => {\n        tracing::error!(\"event bus missing; model registry disabled\");\n        StatusCode::SERVICE_UNAVAILABLE\n    }\n    r => r,\n}","preventionTips":["Fail gateway startup fast if the event bus cannot be initialized rather than leaving it None.","Only mount model registry routes when the bus is present.","Add a boot-time assertion/log that event_bus was wired.","In tests, always construct AppState with a bus (real or mock)."],"tags":["configuration","event-bus","registry","gateway"],"backgroundTag":"missing-configuration","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}