Defaulting
How a friendship is disabled when someone can't (or won't) settle, how it can come back, and how one side can drop it for good.
1. Why
Someone who can't pay back is better off telling their friend than quietly abandoning Tally. Defaulting does that openly, with an optional note. The same step absolves a friend's debt, deactivates a zero-tally friendship, and removes test or mistaken friends from the list.
2. The two steps
2.1 Default — the friendship is disabled
The friend page's danger action sends a signed friendship_default message. Its payload holds the note and the tally at that moment, from the author's side. The button is labelled Default on tally, Absolve tally or Deactivate friend connection, depending on which way the tally points. Both sides then set friendship_status to disabled (glossary):
- The records stay and are shown as Historic records, but they count in no total (the view sums accepted friends only).
- No new activity: handlers accept records, requests, trust changes and name changes from accepted friends only. A disabled friend is not peer-eligible, so no connection or sync is kept up.
- The friend is listed under Disabled connections on the friends page. Their page shows the default notice (who, when, the note), a status chip instead of the Send buttons, and no trust editing.
2.2 Delete history — final for this user
On a disabled friend's page, Delete history removes the friend record, contact, queued messages and every ledger entry between the two. A signed, self-addressed friendship_history_deleted entry records the deletion. It is never sent to the friend. Self-sync carries it to the user's other devices, which delete their copies too (bridge.js). Until the friend shows up again, any entry with them dated before the deletion is dropped wherever it comes from, whether self-sync, a replay or the friend (isDeletedHistoryEntry in ledger.ts), so the history can't creep back.
After this the user cannot restore the friendship. The friend still can, if they kept their copy.
3. Reactivation
Either side of a disabled friendship can suggest reactivation. The reactivation_request carries the requester's copy of the shared history, because the recipient may have deleted theirs. Earlier requests are left out of it, so histories never nest.
The outer signature only proves who sent the list. So every history entry is checked on its own (verifyReactivationHistory):
- It is between exactly these two users.
- Its values are plausible (Ledger value rules).
- It carries a valid authorship proof by its own author. That author may be either user: the recipient's own records come back signed by the recipient.
One bad entry rejects the whole suggestion, since a partial history would give a wrong tally. If the recipient had deleted the history, the friend comes back as disabled, with the tally rebuilt from the verified records and an incoming suggestion to Reactivate or Decline.
A reactivation_response answers it. On acceptance both sides return to accepted, and the records count again. A suggestion is open until a response with its request_id is in the ledger (getOpenReactivationRequests), like payment requests. Suggesting while the friend's own suggestion is open counts as accepting it.
A disabled friend's fresh friend_request, or an old friend_accept replayed by sync, is ignored. Reactivation is the only way back, so the old tally never revives unannounced.
4. Tests
tests/two-client-e2e/scenarios/friendship-default.cjs walks the whole flow through the UI. It covers the default with a note, the disabled views, history deletion, a suggestion with a tampered record being rejected, the real suggestion restoring the deleted side, and acceptance. tests/ledger-validation covers the new types' value rules.