{"record":{"id":"131970d4cd3341fb","repo":"astrid-runtime/astrid","slug":"legacy-principal-alias-retains-unsupported-name","errorCode":null,"errorMessage":"legacy principal {alias} retains unsupported {name} state; no authoritative migration API exists: {}","messagePattern":"legacy principal (.+?) retains unsupported (.+?) state; no authoritative migration API exists: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs","lineNumber":519,"sourceCode":") -> io::Result<()> {\n    for (alias, uid) in directory.bindings() {\n        if alias != PrincipalId::default() {\n            handle_non_default_audit_source(\n                home,\n                &alias,\n                snapshots.get(&format!(\"principal:{uid}:audit\")),\n            )?;\n        }\n        for (name, path) in [\n            (\"kv\", home.principal_home(&alias).kv_dir()),\n            (\"tokens\", home.principal_home(&alias).tokens_dir()),\n        ] {\n            if !path_exists(&path)? {\n                continue;\n            }\n            let snapshot = snapshot_path(&path)?;\n            if snapshot.entries != SourceCount::ZERO {\n                return Err(io::Error::new(\n                    io::ErrorKind::Unsupported,\n                    format!(\n                        \"legacy principal {alias} retains unsupported {name} state; no authoritative migration API exists: {}\",\n                        path.display()\n                    ),\n                ));\n            }\n        }\n    }\n    Ok(())\n}\n\nfn validate_component_name(name: &str) -> io::Result<()> {\n    match name {\n        \"system:state-db\"\n        | \"system:cow\"\n        | \"system:invites\"\n        | \"system:pair-tokens\"","sourceCodeStart":501,"sourceCodeEnd":537,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs#L501-L537","documentation":"Before migrating a legacy layout, the library scans each non-default principal alias's legacy `kv` and `tokens` directories. If such a directory exists and its snapshot shows a non-zero entry count, migration is refused with `io::ErrorKind::Unsupported` because the library has no authoritative, lossless migration path for that legacy state. This is a deliberate safety barrier: migrating would silently discard data.","triggerScenarios":"Calling `migrate_legacy_layout` (or the tested paths `empty_non_default_audit_is_retired_instead_of_refusing_cutover` / `non_empty_non_default_audit_is_quarantined_with_bytes_preserved` trigger it via the same check) when a legacy principal alias still holds entries in `kv_dir()` or `tokens_dir()` whose `snapshot_path` reports `entries != SourceCount::ZERO`.","commonSituations":"Upgrading an old astrid home where a secondary principal still has kv pairs or auth tokens stored in the legacy layout; switching to a version that enforces the migration barrier before the legacy data was drained; automation creating tokens in the legacy directory between upgrade steps.","solutions":["Drain/export the legacy kv and tokens state for the affected alias using the legacy APIs so the snapshot becomes empty, then re-run migration.","If the data is intentionally abandoned, remove the legacy `kv`/`tokens` directories for that alias (after confirming nothing needs them) so the path check is skipped.","Check whether a service is still writing tokens/kv entries into the legacy layout and stop it before migrating.","Consult release notes for a supported migration tool for the legacy state instead of hand-migrating."],"exampleFix":"// before: legacy tokens dir still populated\n//   homes/<alias>/tokens/  (3 entries) -> Unsupported error\n// after: drain legacy state first\n$ astrid legacy export-tokens --alias work > tokens.json\n$ astrid legacy clear --alias work\n$ astrid migrate","handlingStrategy":"validation","validationCode":"// Before migrating, ensure legacy kv/tokens dirs are absent or empty\nfn legacy_sources_drained(home: &Path, alias: &str) -> io::Result<()> {\n    for sub in [\"kv\", \"tokens\"] {\n        let dir = home.join(alias).join(sub);\n        if dir.exists() && std::fs::read_dir(&dir)?.next().is_some() {\n            return Err(io::Error::new(io::ErrorKind::Unsupported, format!(\"{dir:?} still has entries\")));\n        }\n    }\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"match migrate_legacy_layout(&home) {\n    Err(e) if e.kind() == io::ErrorKind::Unsupported => {\n        // inspect e.to_string() for which alias/source retains state; drain it and retry\n    }\n    other => other?,\n}","preventionTips":["Stop all processes writing to the legacy layout before migrating","Export/drain legacy kv and token state as part of the upgrade runbook","Run migration during a maintenance window so nothing repopulates legacy dirs","Check for non-default aliases with remaining legacy data before upgrading"],"tags":["migration","unsupported-operation","data-loss-prevention","rust"],"backgroundTag":"unsupported-operation","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"}