docs(systems): finish detach consolidation — federation.md §6.3b, database.md, stale comments (detach spec §8)
This commit is contained in:
@@ -34,7 +34,7 @@ IDs: Snowflake text, permissions: bigint decimal strings
|
||||
| showActivity | integer NOT NULL | 1 | Rich presence visibility |
|
||||
| federationRegistryUpdatedAt | integer | 0 | LWW timestamp for federation registry sync |
|
||||
| federationHealPending | integer | 0 | Instance-epoch self-healing: set when a replicated identity is flagged for re-heal after a peer reset |
|
||||
| federationHomeOrphaned | integer | 0 | Instance-epoch self-healing (now live, Phase 2): **set** to 1 by `quarantineOrphanedAccounts` on every real account whose home instance was factory-reset (freeze); **read** by the login flow (rejected before password verify — `auth.md` §4) and the `GET /api/federation/reset-events` admin surface. Reversible via admin Keep/Remove |
|
||||
| federationHomeOrphaned | integer | 0 | Instance-epoch self-healing: **1 = DETACHED / sovereign local account** (its home instance was reset/lost), not "frozen." **Set** to 1 by `quarantineOrphanedAccounts` on every real account from a reset home incarnation (flag-only detach — no rename, no login block). **Read** by: the login flow (self-heal path permanently disabled for detached rows; local-password login still works — `auth.md` §4), `users.ts` (unlocks local profile edit + local change-password), the S2S binding guards (`findFederatedUser` tier-2, `profile_update`, identity-delete all exclude detached rows — `federation.md`), and the `GET /api/federation/reset-events` admin surface. Cleared only by `tombstoneUser` (deletion). Detach spec §3/§4 |
|
||||
| createdAt | integer NOT NULL | | Epoch ms |
|
||||
|
||||
### spaces
|
||||
|
||||
@@ -361,14 +361,15 @@ Detection (`markPeerReset`) only snapshots + journals + notifies; it destroys no
|
||||
|
||||
**Live co-member update.** Around each stub, the heal loop now broadcasts a sanitized `user_updated` so survivors' clients flip the partner to "Deleted User" without a reload — aligning this path with the other three deletion callers (admin/self/identity-delete). For each stub it captures `collectDeletionBroadcastTargets(stub.id).targetUserIds` **before** `tombstoneUser` (which deletes the DM/friend/space rows that set is derived from), then re-reads the tombstoned row and calls `connectionManager.sendToUser(targetId, { type: 'user_updated', user: sanitizeUser(deletedRow) })` for each target.
|
||||
|
||||
**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)`:
|
||||
**Real federated accounts — detach (design §6.3b, revised by the 2026-07-02 orphaned-account-detach spec).** 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`.
|
||||
- For every flagged real account (`federation_heal_pending = 1`, non-stub, `isDeleted = 0`, `homeInstanceMatch`): set `federation_home_orphaned = 1` and clear `federation_heal_pending`. **That is all** — this is a flag-only **detach**, not a freeze.
|
||||
- **`federation_home_orphaned = 1` means "DETACHED / sovereign local account,"** not "frozen." The account was cut loose from its (now-reset) home instance and operates as a purely local account from here on: it logs in with its **local password** (`auth.ts` no longer blocks a detached account before password verify — only the self-heal branch is permanently disabled for it, see `auth.md` §4), and it gains local profile edit + local change-password (`users.ts`). There is **no login freeze**.
|
||||
- **No rename.** The username is preserved — first-come-first-served on this instance. There is **no `!orphaned:{uid}@{domain}` handle-freeing** and **no space-owner special case**: all real accounts are treated uniformly and owners simply keep managing their spaces.
|
||||
- **What closes the post-re-peer hijack** is NOT a login freeze. It is the combination of (a) the login self-heal being **permanently disabled** for detached accounts (`auth.ts` returns 401 in the failed-local-password branch without contacting the home domain — a new incarnation can never re-hash its way in) and (b) the S2S identity-binding guards, which **exclude detached rows** on every domain-keyed surface: `findFederatedUser` tier-2 (`federation_home_orphaned = 0` predicate, ~line 3522), the S2S `profile_update` handler (accept-and-skip, ~line 6162), and the S2S `DELETE /api/federation/identity` guard (idempotent 200, no deletion, ~line 2357). See "S2S Identity Deletion" above and design §4.3.
|
||||
- Content (space messages, memberships, reactions) and usernames are preserved in all cases. No `user_updated` broadcast is emitted (nothing visible changes). 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.
|
||||
**Login self-heal epoch guard (design §6.3a).** The federated password self-heal (`auth.ts` §4) 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. This closes the *pre*-re-peer hijack (a reset home accepting a new same-name user's password during the undetected-reset window). The *post*-re-peer window is closed by detach: once an account is detached, its self-heal path is permanently disabled and the S2S binding guards exclude it (above), so the new incarnation has zero influence over it. Full three-way in `auth.md` §4.
|
||||
|
||||
**Reset-events admin surface (`GET /api/federation/reset-events`).** Admin-only. Returns the durable `federation_reset_events` journal (each event carrying a nullable `acknowledgedAt`) joined with each origin's current detached 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[]`). The endpoint returns **all** events (including acknowledged ones, for audit); the client filters to `acknowledgedAt === null`. Disposition actions — one-click Re-peer (`/peers/:id/reset` → `/peer/initiate`), full-purge Remove (`DELETE /api/admin/users/:id`, owns-spaces → transfer first) for genuinely-abandoned detached accounts, and a non-destructive **Dismiss** (`POST /api/federation/reset-events/acknowledge` with `{ origin }`, idempotent — sets `acknowledged_at`) that hides the card without touching the accounts (detached accounts keep working locally). See `admin.md` "FederationPanel" and `client-federation.md` §8.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user