astrid-runtime/astrid · error
principal revocation
Error message
principal revocation {principal} read back epoch {durable}, expected at least {epoch} What it means
Read-back verification in migrate_legacy_file: after writing each principal epoch into the control KV, the reloaded value came back lower than the epoch just written — the KV failed to durably persist the migration, so startup aborts before retiring the legacy file.
Solutions
- Check the KV store's health and CAS semantics; retry startup once it is healthy
- Investigate why the written epoch was lost (eviction, race, wrong namespace)
- Re-run migration after fixing the store; the legacy file is still the source of truth
Defensive patterns
Strategy: retry
When it happens
Trigger: Thrown at crates/astrid-gateway/src/revocations.rs:400 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/0b6ffc415588d916.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-gateway/src/revocations.rs:400
let digest = blake3::hash(&bytes).to_hex().to_string();
let entries = raw
.into_iter()
.map(|(alias, epoch)| {
PrincipalId::new(&alias)
.map(|principal| (principal, epoch))
.map_err(|error| anyhow::anyhow!("invalid principal {alias:?}: {error}"))
})
.collect::<anyhow::Result<Vec<_>>>()?;
for (principal, epoch) in &entries {
record_principal_max(store, principal, *epoch).await?;
}
let (principals, _) = load_from_store(store).await?;
for (principal, epoch) in &entries {
let durable = principals.get(principal).copied().ok_or_else(|| {
anyhow::anyhow!("principal revocation {principal} missing after migration")
})?;
if durable < *epoch {
anyhow::bail!(
"principal revocation {principal} read back epoch {durable}, expected at least {epoch}"
);
}
}
let receipt = LegacyMigrationReceipt {
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");
}View on GitHub (pinned to affd8760f4)