docs(federation): document instance-epoch self-healing (Phase 2)
This commit is contained in:
@@ -357,7 +357,18 @@ Detection (`markPeerReset`) only snapshots + journals + notifies; it destroys no
|
||||
|
||||
**Soft-tombstone (pure stubs only).** For every user that is `federation_heal_pending = 1` AND `password_hash = '!federation-replicated'` (pure S2S stub sentinel) AND matches the origin (`homeInstanceMatch`), calls `tombstoneUser(uid, { purgeContent: false })`. The `purgeContent: false` is **non-negotiable** — the default (`true`) irreversibly deletes this box's reactions and authored space messages, violating the invariant that a remote's reset never destroys our non-re-syncable content. The soft tombstone clears exactly the relationship rows that cause the bug (`friends`, `friend_requests`, `dm_members`, …) so stale friendships/DMs clear and re-adds work. Flags are then cleared **keyed by the stub id list** (not by re-querying the sentinel — `tombstoneUser` has already randomized `password_hash`).
|
||||
|
||||
**Real federated accounts left intact.** A flagged user that is NOT a stub (`password_hash != '!federation-replicated'`) carries real, non-re-syncable local content. It is **never** auto-tombstoned — it stays `federation_heal_pending = 1` and fully intact for the Phase 2 quarantine/admin surface (design §6.3).
|
||||
**Real federated accounts — quarantine (design §6.3b).** A flagged user that is NOT a stub (`password_hash != '!federation-replicated'`) carries real, non-re-syncable local content and is **never** auto-tombstoned. In the genuine-reset branch, after the stub soft-tombstone loop, `healResetIncarnation` calls `quarantineOrphanedAccounts(origin)`:
|
||||
|
||||
- For every flagged real account (`federation_heal_pending = 1`, non-stub, `isDeleted = 0`, `homeInstanceMatch`): set `federation_home_orphaned = 1` (**freeze**) and clear `federation_heal_pending`.
|
||||
- **The freeze is universal** — applied even to space owners. It is the barrier that closes the *post*-re-peer hijack: once re-peered, the trusted baseline updates to the new epoch, so the login epoch guard reads "match" again and no longer blocks a same-name hijack; only the freeze does. Enforcement is in `auth.ts` (direct login rejects `federation_home_orphaned = 1` before password verify — see `auth.md` §4).
|
||||
- **The rename is conditional.** A non-owner is renamed `username → '!orphaned:{uid}@{domain}'` to **free the handle**, so a returning same-name user re-registers into a clean fresh account instead of colliding. This defends BOTH the login uniqueness check AND the registration tier-2 stub-resolution upgrade (`findFederatedUser`). A space owner is **not** renamed (renaming an `ownerId`-referenced account orphans the reference) — it stays frozen and is surfaced to the admin to resolve ownership by hand.
|
||||
- Content (space messages, memberships, reactions) is preserved in all cases. The returned count refreshes the journal's `orphaned_account_count`.
|
||||
|
||||
**Login self-heal epoch guard (design §6.3a).** The federated password self-heal (`auth.ts` §4) now gates re-hashing on the home instance's current epoch, read via the authenticated `fetchPeerEpoch(peer)` (HMAC-signed both ways): no baseline on record → allow (legacy); baseline differs from the fetched epoch → refuse; epoch can't be determined (`fetchPeerEpoch` null — 404/unreachable/bad-sig/desynced secret) → **fail closed/refuse**; match → allow. Closes the *pre*-re-peer hijack (a reset home accepting a new same-name user's password); the universal quarantine freeze closes the post-re-peer window. Full three-way in `auth.md` §4.
|
||||
|
||||
**Reset-events admin surface (`GET /api/federation/reset-events`).** Admin-only, read-only. Returns the durable `federation_reset_events` journal joined with each origin's current orphaned real accounts (`federation_home_orphaned = 1`, `homeInstanceMatch`), each with `ownedSpaces`, `spaceMemberCount`, and authored-`messageCount` for disposition. Response type `FederationResetEventsResponse` (`{ events: FederationResetEvent[] }`, each event carrying `orphanedAccounts: FederationOrphanedAccount[]`). Disposition actions reuse existing endpoints — one-click Re-peer (`/peers/:id/reset` → `/peer/initiate`) and full-purge Remove (`DELETE /api/admin/users/:id`, owns-spaces → transfer first). See `admin.md` "FederationPanel" and `client-federation.md` §8.
|
||||
|
||||
**`needsAttentionReason` on the peer API.** `GET /api/federation/peers` returns `needsAttentionReason: 'auth_failures' | 'peer_reset_detected' | null` per peer, so the admin UI distinguishes a reset-detected peer (persistent Reset-cleanup banner + one-click Re-peer) from a generic auth-failure peer (plain "Reset Peering").
|
||||
|
||||
### S2S Identity Deletion (`DELETE /api/federation/identity`)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user