feat(federation): server-side acknowledge for reset events (acknowledged_at + admin endpoint) (detach spec §4.6)

This commit is contained in:
Jannis Braun
2026-07-02 18:59:33 +02:00
parent 7e1e32de69
commit 42ad5e141d
10 changed files with 3938 additions and 3 deletions
+5 -2
View File
@@ -327,6 +327,7 @@ POST /federation/peer/initiate (admin) { remoteOrigin }
POST /federation/peer/accept (public, IP rate-limited 10/min) { sourceOrigin, challenge, hmacSecret, instanceName?, instanceId?, approvalToken? } → { accepted:true, instanceName, instanceId } (200) | queued (202 + { approvalToken }) | 409 { accepted:false, code:'PEER_EXISTS_RESET_REQUIRED', instanceName, instanceId }
GET /federation/peers (admin) → { peers[] } (no secrets; each peer carries needsAttentionReason)
GET /federation/reset-events (admin) → FederationResetEventsResponse
POST /federation/reset-events/acknowledge (admin) { origin } → { success } (200) | 400 missing origin | 404 unknown origin
DELETE /federation/peers/:id (admin) → { success } + outbox cleanup
POST /federation/relay (HMAC-signed S2S) FederationRelayRequest (+ sourceInstanceId?) → { accepted[], rejected[] }
POST /federation/sync (HMAC-signed S2S) { sinceTimestamp, limit?, dmChannelId?, federatedId?, contextType? } → { events[], hasMore, checkpoint }
@@ -345,7 +346,7 @@ POST /federation/epoch (HMAC-signed S2S, HMAC-signed response) {}
```typescript
type FederationOrphanedAccount = {
id: string;
username: string; // '!orphaned:{uid}@domain' for freed handles; real for space owners
username: string; // preserved original handle (detach spec); legacy rows may carry '!orphaned:{uid}@domain'
displayName: string | null;
avatarColor: string | null;
ownedSpaces: { id: string; name: string }[];
@@ -354,13 +355,15 @@ type FederationOrphanedAccount = {
};
type FederationResetEvent = {
origin: string; deadEpoch: string; newEpoch: string | null;
detectedAt: number; resolvedAt: number | null;
detectedAt: number; resolvedAt: number | null; acknowledgedAt: number | null;
stubCount: number; orphanedAccountCount: number;
orphanedAccounts: FederationOrphanedAccount[];
};
type FederationResetEventsResponse = { events: FederationResetEvent[] };
```
**`POST /api/federation/reset-events/acknowledge`** — admin-only, no S2S. Body `{ origin }`: `400` if missing, `404` if no journal row for that origin, else stamps `acknowledged_at = Date.now()` **only if currently null** (idempotent — a second call keeps the original timestamp) and returns `{ success: true }`. Lets the admin banner be dismissed server-side (Task 7) instead of client-only state; purely informational, detached accounts stay detached (detach spec §4.6).
Disposition actions reuse existing endpoints (no new mutating routes): one-click Re-peer = `POST /peers/:id/reset``POST /peer/initiate`; full-purge Remove = `DELETE /api/admin/users/:id` (owns-spaces → transfer first). **`needsAttentionReason`** (`'auth_failures' | 'peer_reset_detected' | 'repeer_incomplete' | null`) is now included on each `GET /federation/peers` peer object so the client can raise the persistent Reset-cleanup banner only for reset-detected peers and surface an "incomplete Re-peer" warning for `repeer_incomplete`. See `federation.md` "Instance Epoch" and `client-federation.md` §8.
**`POST /api/federation/users/lookup`** — HMAC-authenticated S2S endpoint. Resolves a username on this instance to its canonical `(homeUserId, profile snapshot)`. Used by the cross-instance friend-add flow on the sender's home server before queuing a `friend_request_create` event. Responds to native, non-deleted users only; ignores `discoverable`. Returns `{ found: false, code: 'user_not_found' }` for stubs, tombstoned users, or unknown handles. See `federation.md` §1 "S2S User Lookup" for the full contract.
+1
View File
@@ -426,6 +426,7 @@ Instance-epoch self-healing ledger. One row per origin recording a detected fede
| resolvedAt | integer | | Epoch ms healing completed; `NULL` while in progress |
| stubCount | integer NOT NULL | 0 | Count of replicated identity stubs affected by the reset |
| orphanedAccountCount | integer NOT NULL | 0 | Count of accounts that could not be re-linked to the new epoch |
| acknowledgedAt | integer | | Epoch ms the admin dismissed this reset event from the banner (`POST /api/federation/reset-events/acknowledge`, idempotent); `NULL` while unacknowledged. Purely informational — detach spec §4.6 |
### peer_approval_requests
Queue of peering requests pending admin review when `autoAcceptPeering` is `false`. Holds **both directions**: inbound rows (remote asked to peer with us) and outbound rows (a local user-initiated `ensurePeered` call gated on this side; see [federation.md → Outbound Peering Gate](federation.md#outbound-peering-gate)). UNIQUE on `(origin, direction)` so the same origin may have at most one row per direction simultaneously. Rows expire after 30 days via janitor cleanup.