{"record":{"id":"0b6ffc415588d916","repo":"astrid-runtime/astrid","slug":"principal-revocation-principal-read-back-epoch","errorCode":null,"errorMessage":"principal revocation {principal} read back epoch {durable}, expected at least {epoch}","messagePattern":"principal revocation (.+?) read back epoch (.+?), expected at least (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-gateway/src/revocations.rs","lineNumber":400,"sourceCode":"    let digest = blake3::hash(&bytes).to_hex().to_string();\n    let entries = raw\n        .into_iter()\n        .map(|(alias, epoch)| {\n            PrincipalId::new(&alias)\n                .map(|principal| (principal, epoch))\n                .map_err(|error| anyhow::anyhow!(\"invalid principal {alias:?}: {error}\"))\n        })\n        .collect::<anyhow::Result<Vec<_>>>()?;\n    for (principal, epoch) in &entries {\n        record_principal_max(store, principal, *epoch).await?;\n    }\n    let (principals, _) = load_from_store(store).await?;\n    for (principal, epoch) in &entries {\n        let durable = principals.get(principal).copied().ok_or_else(|| {\n            anyhow::anyhow!(\"principal revocation {principal} missing after migration\")\n        })?;\n        if durable < *epoch {\n            anyhow::bail!(\n                \"principal revocation {principal} read back epoch {durable}, expected at least {epoch}\"\n            );\n        }\n    }\n    let receipt = LegacyMigrationReceipt {\n        schema: 1,\n        digest,\n        principal_count: entries.len(),\n    };\n    let encoded = serde_json::to_vec(&receipt).context(\"encode revocation migration receipt\")?;\n    let existing = store\n        .get(REVOCATION_NAMESPACE, MIGRATION_RECEIPT_KEY)\n        .await\n        .map_err(|error| anyhow::anyhow!(\"read revocation migration receipt: {error}\"))?;\n    if let Some(existing) = existing {\n        if existing != encoded {\n            anyhow::bail!(\"gateway revocation migration receipt conflicts\");\n        }","sourceCodeStart":382,"sourceCodeEnd":418,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/revocations.rs#L382-L418","documentation":"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.","triggerScenarios":"Thrown at crates/astrid-gateway/src/revocations.rs:400 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","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"],"exampleFix":null,"handlingStrategy":"retry","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"}