astrid-runtime/astrid · error
gateway revocation migration receipt raced; retry startup
Error message
gateway revocation migration receipt raced; retry startup
What it means
Race guard in migrate_legacy_file: the compare_and_swap that installs the revocation migration receipt failed because another writer inserted a different receipt concurrently. The digest-bearing receipt encodes the migrated entry set; a mismatch means two different migration attempts raced, the on-disk state is ambiguous, and startup must be retried rather than proceeding on possibly-divergent revocation data.
Solutions
- Retry startup — the winner's receipt should now be readable and migration complete
- Verify the receipt and hydrated epochs are present before continuing
- Ensure only one instance performs startup migration, or rely on the CAS fence
Defensive patterns
Strategy: retry
When it happens
Trigger: Thrown at crates/astrid-gateway/src/revocations.rs:424 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/97a67c29232dd1b7.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-gateway/src/revocations.rs:424
schema: 1,
digest,
principal_count: entries.len(),
};
let encoded = serde_json::to_vec(&receipt).context("encode revocation migration receipt")?;
let existing = store
.get(REVOCATION_NAMESPACE, MIGRATION_RECEIPT_KEY)
.await
.map_err(|error| anyhow::anyhow!("read revocation migration receipt: {error}"))?;
if let Some(existing) = existing {
if existing != encoded {
anyhow::bail!("gateway revocation migration receipt conflicts");
}
} else if !store
.compare_and_swap(REVOCATION_NAMESPACE, MIGRATION_RECEIPT_KEY, None, encoded)
.await
.map_err(|error| anyhow::anyhow!("write revocation migration receipt: {error}"))?
{
anyhow::bail!("gateway revocation migration receipt raced; retry startup");
}
let _ = load_from_store(store).await?;
match std::fs::remove_file(&path) {
Ok(()) => {},
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {},
Err(error) => return Err(anyhow::anyhow!("retire {}: {error}", path.display())),
}
Ok(true)
}
/// Spawn the audit-event watcher. Subscribes to the kernel's audit
/// topic and updates the revocation map whenever a successful
/// `AgentDelete` admin op lands. Detached: terminates when the bus
/// is dropped (i.e. daemon shutdown), so no explicit join is needed.
///
/// `bus` is the kernel's shared event bus; `revoked_at` is the same
/// `Arc<RwLock<…>>` held by [`crate::state::GatewayState`] so writes
/// here become visible to every in-flight verify call.View on GitHub (pinned to affd8760f4)