Contact restore
A degraded contact is a cid present in contacts but missing from peer_ads — typically after a breaking-change migration carried the contact but had to drop its address document. Contact restore re-runs the key exchange between two mutually known addresses: the same machinery as the invite legs (identity bundle, box to an ephemeral key, gates before any write), but the trust anchor is "a signed request from an address already in my contacts" instead of an out-of-band invite token.
Traced from a2a_messaging.mm (begin_contact_restore, handle_request_contact_restore, handle_submit_restore_response, handle_complete_restore, flush_deferred, restore_degraded_contacts).
Why the pieces are shaped this way
- Leg 0 is unboxed (just
$rid, an ephemeral pubkey, and a scheme id): there is nothing secret to carry yet, and the framework signs every envelope, so the responder authenticates the requester from the envelope alone. - Silent no-op for strangers: a request from an address not in
contactsreturns success with no actions — whether an address is known never leaks. - The flush is host-driven, not automatic: firing
flush_deferredon the$contact_restorednotify means the encrypted sends can never race the restore legs' bare sends on the wire. - Retry budget: the host sweep re-fires on its GC cadence, up to
restore_max_attempts(30) per contact; a peer that upgraded and came back online answers on the first post-upgrade attempt. Each re-mint supersedes the previous ephemeral key, so a stale leg-1 reply fails both the$ridcheck and the unbox. - Observability:
list_degraded_contactsandlist_deferred_queuesare the readonly views the host sweep keys off.
This flow is what makes contacts survive breaking changes — see the migration contract in Versioning.