diff --git a/cmd/wallet/main.go b/cmd/wallet/main.go index f2314d3..2fa33ca 100644 --- a/cmd/wallet/main.go +++ b/cmd/wallet/main.go @@ -51,7 +51,7 @@ const shutdownGrace = 5 * time.Second // Version is the wallet binary's release tag. Kept in sync with the // app_version field in manifest.json — `manifest_test.go` cross-checks // they agree, so a release without bumping both fails CI. -const Version = "0.4.0" +const Version = "0.4.1" func main() { ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) diff --git a/manifest.json b/manifest.json index 301099d..ba2aa6e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,6 +1,6 @@ { "id": "io.pilot.wallet", - "app_version": "0.4.0", + "app_version": "0.4.1", "manifest_version": 2, "binary": { "runtime": "go", @@ -47,6 +47,7 @@ {"cap": "fs.read", "target": "$HOME/.pilot/apps/io.pilot.wallet.previous/*"}, {"cap": "fs.read", "target": "$PILOT_APPSTORE_BACKUP_ROOT/io.pilot.wallet/*/*"}, {"cap": "audit.log","target": "*"}, + {"cap": "identity.verify", "target": "*"}, {"cap": "net.dial", "target": "*.pilot", "if": {"kind": "rate", "params": {"per": "min", "limit": 100}}}, {"cap": "net.dial", "target": "*.base.org", diff --git a/manifest_test.go b/manifest_test.go index 4b1250a..132176a 100644 --- a/manifest_test.go +++ b/manifest_test.go @@ -151,6 +151,34 @@ func TestManifestDeclaresEIP3009Grant(t *testing.T) { } } +// capsKnownToStateDeletingPilotctl is app-store v1.0.2's KnownCaps: the +// manifest validator in pilotctl v1.13.0-v1.13.9, whose `appstore upgrade` +// (run hourly by the updater as `upgrade --all`) deletes the app's dir, and +// with it identity-evm.json, before the new version ever runs. pilotctl +// v1.13.10 (app-store v1.0.3) carries the state forward and added +// identity.verify. +var capsKnownToStateDeletingPilotctl = map[string]bool{ + "fs.read": true, "fs.write": true, "fs.append": true, "fs.delete": true, + "net.dial": true, "net.call": true, "ipc.call": true, "key.sign": true, + "audit.log": true, "proc.exec": true, +} + +// TestManifestRefusedByPilotctlThatDeletesState: the manifest must declare a +// capability those pilotctl versions do not know, so their upgrade fails +// validation before it touches the install and the existing key stays on disk. +// The wallet does not use identity.verify; it is there for this. Drop it only +// once no node runs pilotctl v1.13.0-v1.13.9. +func TestManifestRefusedByPilotctlThatDeletesState(t *testing.T) { + raw, _ := os.ReadFile(manifestPath(t)) + m, _ := manifest.Parse(raw) + for _, g := range m.Grants { + if !capsKnownToStateDeletingPilotctl[g.Cap] { + return + } + } + t.Error("every grant's cap is known to pilotctl v1.13.0-v1.13.9: their upgrade would install this version and delete the wallet's key; keep the identity.verify grant") +} + // TestShippedManifestSpendCapsParse round-trips the real wallet // manifest through ParseSpendCapsFromManifest and asserts both // declared caps come through. Without this, a manifest edit that