astrid-runtime/astrid · error · GatewayError::Internal

gateway is not wired to a live event bus; model registry una

Error message

gateway is not wired to a live event bus; model registry unavailable

What it means

`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.

Source

Thrown at crates/astrid-gateway/src/routes/models.rs:198

///
/// `corr_id` is the set-path correlation filter (see
/// [`reply_satisfies_corr_id`]). When `Some`, the reply-draining loop SKIPS
/// any reply whose `corr_id` is present and differs from ours — that reply
/// belongs to another concurrent same-principal SET — and keeps receiving
/// (within the same overall timeout budget) until a matching / un-correlated
/// reply arrives or the budget is spent. When `None` (the GET paths) the
/// first trusted-source reply on the scoped route is taken.
async fn registry_round_trip(
    state: &GatewayState,
    principal_id: &PrincipalId,
    _workspace: &WorkspaceContext,
    request_topic: &'static str,
    response_topic: &'static str,
    payload: serde_json::Value,
    corr_id: Option<&str>,
) -> GatewayResult<serde_json::Value> {
    let Some(bus) = state.event_bus.clone() else {
        return Err(GatewayError::Internal(anyhow::anyhow!(
            "gateway is not wired to a live event bus; model registry unavailable"
        )));
    };

    let principal = principal_id.to_string();
    ensure_registry_request_subscribed(state, principal_id, request_topic).await?;
    let expected_source_ids = provider_source_ids(state, principal_id, request_topic).await?;

    // Subscribe FIRST, then publish. Reverse order would race a fast
    // registry reply — the reply could land before subscribe returns and
    // we'd miss it. The route is scoped to `Some(Some(principal))`: the
    // outer `Some` marks the route as scoped, the inner `Some(principal)`
    // is the security boundary — a reply stamped with any other principal
    // is dropped at enqueue and never enters this route's budget. A fresh
    // per-call UUID isolates this connection's route from any concurrent
    // model request.
    let mut reply_rx = bus.subscribe_topic_routed_scoped(
        Uuid::new_v4(),

View on GitHub (pinned to affd8760f4)

Solutions

  1. Check gateway startup config and logs for event-bus initialization; fix the wiring so `state.event_bus` is populated.
  2. If intentionally running without a bus, do not expose the model registry routes.
  3. Verify the bus connection (broker/socket) is created before routes are served.
  4. In tests, provide a real or mock event bus in the app state instead of `None`.

Example fix

// before
let Some(bus) = state.event_bus.clone() else {
    return Err(GatewayError::Internal(anyhow::anyhow!(
        "gateway is not wired to a live event bus; model registry unavailable"
    )));
};
// after
let Some(bus) = state.event_bus.clone() else {
    tracing::error!("event bus missing from app state; check startup wiring");
    return Err(GatewayError::ServiceUnavailable(
        "model registry unavailable: event bus not configured".into(),
    ));
};
Defensive patterns

Strategy: validation

Validate before calling

pub fn registry_available(state: &AppState) -> bool {
    state.event_bus.is_some()
}
// gate the routes at router-build time
if !registry_available(&state) { skip_model_routes(); }

Type guard

fn has_event_bus(state: &AppState) -> bool {
    matches!(state.event_bus, Some(_))
}

Try / catch

match list_models(&state, caller).await {
    Err(e) if e.message().contains("not wired to a live event bus") => {
        tracing::error!("event bus missing; model registry disabled");
        StatusCode::SERVICE_UNAVAILABLE
    }
    r => r,
}

Prevention

When it happens

Trigger: 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.

Common situations: 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`.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/90cfd88e7d4f2d2c. Report an issue: GitHub.