From 14a96efa584c86bc1bda8ae7346fd4f50335b186 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Wed, 22 Apr 2026 00:06:17 +0200 Subject: [PATCH] feat(federation): scaffold peer-activation recovery module Empty stubs for onPeerActivated, resetOutboxBackoff, syncPeerMutationLog, and startupBootstrapSync. Functions are filled in by subsequent tasks following TDD cycles. --- .../src/utils/federationPeerActivation.ts | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 packages/server/src/utils/federationPeerActivation.ts diff --git a/packages/server/src/utils/federationPeerActivation.ts b/packages/server/src/utils/federationPeerActivation.ts new file mode 100644 index 00000000..d4ffef18 --- /dev/null +++ b/packages/server/src/utils/federationPeerActivation.ts @@ -0,0 +1,81 @@ +import { getDb } from '../db/index.js'; +import * as schema from '../db/schema.js'; +import { and, eq } from 'drizzle-orm'; +import { isFederationRelayEnabled } from './federationOutbox.js'; +import { buildFederationHeaders, getOurOrigin } from './federationAuth.js'; +import type { FederationRelayEvent } from '@backspace/shared'; + +export type PeerActivationReason = + | 'initiate_accepted' + | 'accept_rejected_override' + | 'accept_awaiting_approval' + | 'accept_pending' + | 'accept_new' + | 'approval_handshake' + | 'health_check_recovery' + | 'ensure_peered' + | 'startup_bootstrap'; + +// Dedup: concurrent activations for the same peerId share one promise. +const inFlightActivation = new Map>(); + +/** + * Called whenever federation_peers.status transitions to 'active' for any reason. + * Two independent invariants — both run unconditionally: + * 1. Reset outbox backoff (nextRetryAt = now, attempts = 0) for this peer. + * 2. Pull-sync mutation log from peer's /api/federation/sync since lastSyncedAt. + * + * Call sites (must remain exhaustive — grep `onPeerActivated(` to audit): + * - routes/federation.ts /peer/initiate activation + * - routes/federation.ts /peer/accept existing-rejected override + * - routes/federation.ts /peer/accept existing-awaiting_approval + * - routes/federation.ts /peer/accept existing-pending + * - routes/federation.ts /peer/accept new-peer + * - routes/federation.ts /approval-requests/:id/approve + * - utils/federationWorker.ts health check recovery + * - utils/federationPeering.ts ensurePeered/performHandshake + * - utils/federationWorker.ts startup bootstrap (via startupBootstrapSync) + * + * Deduplicated by peerId — concurrent calls share one promise. + */ +export async function onPeerActivated( + peerId: string, + reason: PeerActivationReason, +): Promise { + // Stub — implemented in Task 4. + void peerId; + void reason; +} + +/** + * Reset all outbox backoff state for a peer (nextRetryAt = now, attempts = 0). + * Unconditional across all entries of the peer — see spec §Invariant 1. + */ +export function resetOutboxBackoff(peerId: string): void { + // Stub — implemented in Task 2. + void peerId; +} + +/** + * Pull-sync mutation log from the peer's /api/federation/sync endpoint. + * Runs three contextType passes (dm, friend, profile), paginating each. + * Updates peer.lastSyncedAt to Date.now() on success; leaves it untouched + * on transient failure so the next activation retries. + */ +export async function syncPeerMutationLog( + peerId: string, + reason: PeerActivationReason, +): Promise { + // Stub — implemented in Task 3. + void peerId; + void reason; +} + +/** + * Startup bootstrap — scan for freshly-peered rows (status='active', lastSyncedAt=0) + * and run onPeerActivated for each. Replaces runInitialSyncForNewPeers. + * Invoked from startFederationWorkers. + */ +export async function startupBootstrapSync(): Promise { + // Stub — implemented in Task 5. +}