Startup should attempt refresh-token recovery before FATALing on a stale identity
Symptom: after the control plane rotates its signing key, sam-node run dies at startup:
loaded identity fails role requirement "sam:role:node": biscuit: invalid signature
Root cause: Start() verifies the stored biscuit against the freshly synced mesh config (the node's own SyncMeshConfig pulls the new CP pubkey at startup) and hard-fails on mismatch — without attempting any recovery. Yet the store usually holds everything needed to heal: refresh_token, oidc_issuer, oidc_client_id (device flow saves them), and renewWithRefreshToken + enroll --jwt-path already exist. The renewal loop only helps before expiry/rotation and only after a successful start; the startup path has no recovery rung at all.
Request: on role-requirement failure at startup, attempt silent re-enrollment via the stored refresh token (refresh grant → /enroll with the new JWT, keeping the PeerID) before FATALing; fall back to the reset+device-flow guidance only when the refresh token is absent or rejected. We implemented this client-side in our wrapper (OIDC discovery → refresh grant → sam-node reset → run --join --jwt-path) and it heals a rotated identity with zero human steps — it would be better upstream where everyone gets it.
Startup should attempt refresh-token recovery before FATALing on a stale identity
Symptom: after the control plane rotates its signing key,
sam-node rundies at startup:Root cause:
Start()verifies the stored biscuit against the freshly synced mesh config (the node's ownSyncMeshConfigpulls the new CP pubkey at startup) and hard-fails on mismatch — without attempting any recovery. Yet the store usually holds everything needed to heal:refresh_token,oidc_issuer,oidc_client_id(device flow saves them), andrenewWithRefreshToken+enroll --jwt-pathalready exist. The renewal loop only helps before expiry/rotation and only after a successful start; the startup path has no recovery rung at all.Request: on role-requirement failure at startup, attempt silent re-enrollment via the stored refresh token (refresh grant →
/enrollwith the new JWT, keeping the PeerID) before FATALing; fall back to the reset+device-flow guidance only when the refresh token is absent or rejected. We implemented this client-side in our wrapper (OIDC discovery → refresh grant →sam-node reset→run --join --jwt-path) and it heals a rotated identity with zero human steps — it would be better upstream where everyone gets it.