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

  1. Check the KV store's health and CAS semantics; retry startup once it is healthy
  2. Investigate why the written epoch was lost (eviction, race, wrong namespace)
  3. 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)