{"record":{"id":"4c4b52f59736b2e9","repo":"astrid-runtime/astrid","slug":"gateway-revocation-storage-is-unavailable-while-a","errorCode":null,"errorMessage":"gateway revocation storage is unavailable while a legacy revocation file exists","messagePattern":"gateway revocation storage is unavailable while a legacy revocation file exists","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-gateway/src/state.rs","lineNumber":385,"sourceCode":"        }))\n    }\n\n    /// Hydrate principal and device revocation epochs from the authoritative\n    /// kernel control KV before the HTTP listener is exposed. A legacy JSON\n    /// index is imported exactly once when present and then retired after KV\n    /// receipt/read-back verification. Any list/get error or malformed value\n    /// aborts startup before an unfenced listener can be exposed. Standalone\n    /// route tests without a KV remain usable only when no legacy file exists;\n    /// that no-KV mode is intentionally non-durable across restart.\n    ///\n    /// # Panics\n    ///\n    /// Panics if either in-memory revocation lock is poisoned, indicating an\n    /// earlier panic while mutating gateway security state.\n    pub async fn hydrate_revocations(&self) -> anyhow::Result<()> {\n        let Some(store) = self.storage_kv.as_deref() else {\n            if crate::revocations::legacy_file_exists()? {\n                anyhow::bail!(\n                    \"gateway revocation storage is unavailable while a legacy revocation file exists\"\n                );\n            }\n            return Ok(());\n        };\n        let _ = crate::revocations::migrate_legacy_file(store).await?;\n        let (principals, devices) = crate::revocations::load_from_store(store).await?;\n        *self\n            .revoked_at\n            .write()\n            .expect(\"revocation map poisoned during startup hydration\") = principals;\n        *self\n            .revoked_key_ids\n            .write()\n            .expect(\"device revocation map poisoned during startup hydration\") = devices;\n        Ok(())\n    }\n","sourceCodeStart":367,"sourceCodeEnd":403,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/state.rs#L367-L403","documentation":"Fail-closed startup guard in hydrate_revocations: revocation storage (control KV) is unavailable while a legacy JSON revocation file still exists, so revocations could be silently lost after restart; startup aborts before exposing an unfenced listener.","triggerScenarios":"Thrown at crates/astrid-gateway/src/state.rs:385 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Restore connectivity to the control KV store, then restart","Complete the legacy-file migration so the KV is authoritative and the file can be retired","If running standalone tests without KV, remove the legacy file first — no-KV mode is intentionally non-durable"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"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"}