Storage-backed capsule upgrade leaves a stale global authority receipt that the legacy barrier rejects
Summary
A storage-backed capsule install deliberately skips writing the global etc/capsule-authority/ receipt, publishing the receipt inside the durable package instead. But the legacy migration barrier still reads that global receipt as authoritative. After any storage-backed upgrade, the on-disk receipt names the previous version, and the barrier rejects the capsule:
Error: Failed to boot Kernel: legacy capsule migration failed: verify legacy capsule authority astrid-capsule-agents8 of my 19 capsules were in this state.
Mechanism
crates/astrid-capsule-install/src/local.rs:733 makes the global receipt conditional:
// Workspace/native installs still need a host receipt while their files
// are authoritative. A storage-backed install publishes the exact receipt
// inside the durable package atomically; its target directory is only a
// disposable cache, so emitting a legacy global receipt would make the
// migration barrier reject an otherwise valid post-cutover restart.
let authority_transaction = options
.storage
.is_none()
.then(|| AuthorityReceiptTransaction::stage(home, &target_dir, &installed_authority))
.transpose()?;The reasoning is explicit and internally coherent. The gap is that the capsule's meta.json and Capsule.toml under the native directory are updated (local.rs:847 writes meta unconditionally), so the native tree advances to the new version while the global receipt stays frozen at the old one.
When that install later has to pass through the legacy barrier — crates/astrid-capsule-install/src/storage/migration.rs:145 — it calls verify_installed_authority, which reads the stale global receipt and bails at crates/astrid-capsule-install/src/authority.rs:533:
if authority.capsule_id != manifest.package.name
|| authority.version != manifest.package.version
{
bail!(
"installed capsule identity/version differs from its authority receipt (approved {} {}, found {} {})",
authority.capsule_id, authority.version,
manifest.package.name, manifest.package.version
);
}So the comment's "otherwise valid post-cutover restart" holds only if the barrier never runs again. On an install that still has native capsule directories awaiting migration, it does.
The state I found (manifest version vs. receipt version):
| capsule | Capsule.toml |
etc/capsule-authority/ receipt |
|---|---|---|
| astrid-capsule-agents | 0.2.0 | 0.1.0 |
| astrid-capsule-context-engine | 0.2.0 | 0.1.0 |
| astrid-capsule-fs | 0.2.0 | 0.1.0 |
| astrid-capsule-memory | 0.2.0 | 0.1.0 |
| astrid-capsule-prompt-builder | 0.2.0 | 0.1.0 |
| astrid-capsule-react | 0.2.2 | 0.2.1 |
| astrid-capsule-router | 0.2.0 | 0.1.0 |
| astrid-capsule-system | 0.2.0 | 0.1.0 |
The astrid-capsule-agents receipt is dated 2026-07-24; its meta.json records updated_at: 2026-09-05T21:02:17Z. Six weeks of drift.
Reproduction Steps
- Install capsules so native directories exist under
home/<principal>/.local/capsules/with receipts inetc/capsule-authority/. - Upgrade one of them through the storage-backed path (
options.storage.is_some()), someta.jsonandCapsule.tomladvance but the global receipt does not. - Run
astrid starton a build where the legacy migration barrier still processes native capsules. - Boot aborts on the first capsule whose receipt version trails its manifest.
Deleting the stale receipts lets verify_installed_authority take its None branch (authority.rs:513), which re-snapshots them as LegacyMigration and proceeds — but that silently re-approves whatever is on disk, which is not a remedy anyone should have to reach for.
Expected Behavior
- Keep the global receipt in step with the native directory whenever that directory is updated, or delete it alongside the content it describes so it cannot go stale.
- Have the barrier prefer the durable package receipt when one exists, and treat the global receipt as a fallback only for capsules with no durable entry.
- Add a test covering "storage-backed upgrade, then legacy barrier runs" — the two code paths are individually correct and only fail in combination.
Environment
- OS: macOS 15.6 (Darwin 24.6.0), arm64
- Astrid: 2026.9.2, installed via
astrid update - Source: astrid-runtime/astrid @
73661c9b(Cargo version 0.10.4) - 19 native capsules under
home/default/.local/capsules/; 29 receipts underetc/capsule-authority/; 8 stale
Logs / Backtrace
Error: Failed to boot Kernel: legacy capsule migration failed: verify legacy capsule authority astrid-capsule-agentsThe underlying cause, recovered by hand (see the companion issue on flattened error chains):
etc/capsule-authority/832eed9eddef9775b3116be39d8ce184fa9fe264abb2d26df452121c7ee5f0c5.json
{ "schema_version": 1, "source": "legacy-migration",
"capsule_id": "astrid-capsule-agents", "version": "0.1.0", ... }
home/default/.local/capsules/astrid-capsule-agents/Capsule.toml
[package] name = "astrid-capsule-agents" version = "0.2.0"
home/default/.local/capsules/astrid-capsule-agents/meta.json
{ "version": "0.2.0", "updated_at": "2026-09-05T21:02:17.365614+00:00", ... }Source: astrid-runtime/astrid