Files
amethyst/commons
Vitor PamplonaandClaude Opus 5 409339b375 feat(concord): re-mint invite links on Refounding so stranded recovery fires
Closes the liveness half of A2. Recovery's whole premise is that the
community keeps re-minting its bundle at the SAME addressable coordinate, so
the link a stranded member already holds starts pointing at the new epoch.
Nothing did: `ConcordInviteBundle.mintLink` generates a fresh KeyPair and
token per call, and no client persisted `linkSignerPrivKey`. Every mint was a
new coordinate, so `recover` could only ever return `already_current` — the
mechanism was dead code, and an owner evicted by a rogue admin had no way
back.

The kind-33301 bundle is addressable and authored by the link signer, so
re-signing at that coordinate with the same token replaces what is there and
every holder of that link keeps working. Exposes that as
`ConcordActions.remintBundleAt`, persists the link signer + token in amy's
store at mint time, and has `concord refound` refresh every link it minted
for the new epoch.

Re-minting every live link is safe precisely because the security half is
already in: `refound` bans the removed members on the way out, and `recover`
reads the banlist of the epoch being LEFT, so a removed member's own recovery
is refused even though their link now resolves. That gate stops being
belt-and-braces here and becomes load-bearing — which is what the audit
predicted for any client that re-mints (Armada does).

Verified end to end against a loopback geode, both directions:

    bob joins by link, holds no role, never posts   (unfindable by a rotation)
    alice refound --remove <stranger>  → recipients=1, invites_refreshed=1
    bob rekey    → no_blob_for_us      (genuinely stranded)
    bob recover  → recovered, epoch 0 → 1   ← first time this has ever fired
    bob reads the community at the new epoch

    alice refound --remove bob         → epoch 1 → 2, invites_refreshed=1
    bob recover  → refused, reason "banned", still at epoch 1

Still open for the shipping client: Amethyst persists no link signer, so
A2 liveness remains open on Android. Doing it there means deciding where the
secret lives in the kind-13302 list, which Armada also reads — a wire-schema
call, not a code one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 19:06:39 -04:00
..