astrid-runtime/astrid · error

gateway revocation storage is unavailable while a legacy…

Error message

gateway revocation storage is unavailable while a legacy revocation file exists

What it means

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.

Solutions

  1. Restore connectivity to the control KV store, then restart
  2. Complete the legacy-file migration so the KV is authoritative and the file can be retired
  3. If running standalone tests without KV, remove the legacy file first — no-KV mode is intentionally non-durable
Defensive patterns

Strategy: fallback

When it happens

Trigger: Thrown at crates/astrid-gateway/src/state.rs:385 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at crates/astrid-gateway/src/state.rs:385

        }))
    }

    /// Hydrate principal and device revocation epochs from the authoritative
    /// kernel control KV before the HTTP listener is exposed. A legacy JSON
    /// index is imported exactly once when present and then retired after KV
    /// receipt/read-back verification. Any list/get error or malformed value
    /// aborts startup before an unfenced listener can be exposed. Standalone
    /// route tests without a KV remain usable only when no legacy file exists;
    /// that no-KV mode is intentionally non-durable across restart.
    ///
    /// # Panics
    ///
    /// Panics if either in-memory revocation lock is poisoned, indicating an
    /// earlier panic while mutating gateway security state.
    pub async fn hydrate_revocations(&self) -> anyhow::Result<()> {
        let Some(store) = self.storage_kv.as_deref() else {
            if crate::revocations::legacy_file_exists()? {
                anyhow::bail!(
                    "gateway revocation storage is unavailable while a legacy revocation file exists"
                );
            }
            return Ok(());
        };
        let _ = crate::revocations::migrate_legacy_file(store).await?;
        let (principals, devices) = crate::revocations::load_from_store(store).await?;
        *self
            .revoked_at
            .write()
            .expect("revocation map poisoned during startup hydration") = principals;
        *self
            .revoked_key_ids
            .write()
            .expect("device revocation map poisoned during startup hydration") = devices;
        Ok(())
    }

View on GitHub (pinned to affd8760f4)