{"record":{"id":"dd06b2f425a0eb1f","repo":"astrid-runtime/astrid","slug":"msg","errorCode":null,"errorMessage":"{msg}","messagePattern":"\\{msg\\}","errorType":"http","errorClass":"GatewayError::Internal","httpStatus":500,"severity":"error","filePath":"crates/astrid-gateway/src/routes/capsules.rs","lineNumber":524,"sourceCode":"    params((\"id\" = String, Path, description = \"Capsule id\")),\n    responses(\n        (status = 200, body = CapsuleTopicsResponse, description = \"Declared topics. Empty until kernel-side topic enumeration ships.\"),\n        (status = 401, body = ErrorBody),\n    )\n)]\npub async fn list_capsule_topics(\n    State(_state): State<Arc<GatewayState>>,\n    Path(_id): Path<String>,\n    req: Request<axum::body::Body>,\n) -> GatewayResult<Json<CapsuleTopicsResponse>> {\n    caller_from(&req)?;\n    Ok(Json(CapsuleTopicsResponse { topics: vec![] }))\n}\n\n// ── helpers (kernel client error mapping) ────────────────────────\n\nfn internal(msg: String) -> GatewayError {\n    GatewayError::Internal(anyhow::anyhow!(msg))\n}\n\nfn hidden_capsule_detail_denial(\n    caller: &astrid_core::PrincipalId,\n    capsule_id: &str,\n    reason: &str,\n) -> GatewayResult<Json<CapsuleDetail>> {\n    tracing::warn!(\n        security_event = true,\n        principal = %caller,\n        capsule = %capsule_id,\n        reason = %reason,\n        \"capsule detail visibility probe denied; returning hidden not-found\"\n    );\n    Err(GatewayError::NotFound)\n}\n\n/// Map a non-success GitHub HTTP status to a gateway error. A `404` is a","sourceCodeStart":506,"sourceCodeEnd":542,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/routes/capsules.rs#L506-L542","documentation":"internal(msg) is a helper in capsules.rs that wraps kernel-client error messages into GatewayError::Internal (500). Every capsule endpoint (list, install, get, stage, resolve_github_source, permission_cards) funnels kernel-client failures through it, so this error means the kernel/daemon side of a capsule operation failed and its message is passed through verbatim.","triggerScenarios":"Any capsules endpoint call whose kernel client request fails — list/install/stage/get/permissions or GitHub source resolution — producing a kernel client error string mapped by internal().","commonSituations":"Kernel daemon down or unreachable during capsule operations; kernel rejecting a capsule id or source (message embedded in {msg}); network errors while resolving GitHub capsule sources; staging failures for capsule archives.","solutions":["Read the embedded {msg} — it carries the kernel-client cause; treat it as the real error.","Verify the kernel daemon is running and reachable from the gateway.","Retry if the failure was transient (network blip during GitHub resolution or staging).","If the message indicates a capsule-level rejection (not found, invalid source), correct the capsule id/source and re-request."],"exampleFix":"// before: opaque 500\ncurl /api/capsules  # 500 \"kernel client: connection refused\"\n// after: ensure kernel reachable\n$ systemctl status astrid-daemon && retry capsule request","handlingStrategy":"try-catch","validationCode":"// probe kernel before capsule operations\nconst h = await fetch('/api/health');\nif (!h.ok || h.json().kernel !== 'up') throw new Error('kernel unavailable; capsule ops disabled');","typeGuard":null,"tryCatchPattern":"try {\n  const list = await fetch('/api/capsules');\n} catch (e) {\n  if (e.status === 500 && /kernel/.test(e.message)) {\n    // daemon-side failure: check kernel health, then retry once\n  }\n  throw e;\n}","preventionTips":["Health-check the kernel daemon before exposing capsule endpoints.","Retry transient kernel-client errors with backoff in the helper itself.","Surface the embedded kernel message to operators via structured logs.","Alert on capsule endpoints' 500 rates as a proxy for gateway-kernel link health."],"tags":["rust","capsules","daemon","kernel-client"],"backgroundTag":"internal-invariant-violation","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}