feat(federation): detect peer reset on needs_attention peers (§4.1)

A reset peer can reach needs_attention via the auth-failure path (HTTP up,
401/403 from a new incarnation crossing AUTH_FAILURE_THRESHOLD) without ever
passing through unreachable, so the unreachable-only recovery probe never
observes its epoch change and no reset journal is created — leaving a later
manual Re-peer with nothing to heal.

Add detectResetOnNeedsAttentionPeers() to the 15-minute health-check tick:
probe needs_attention peers with a non-null baseline (excluding those already
peer_reset_detected) and call markPeerReset on an observed epoch mismatch.
Detection only — never recovers a needs_attention peer to active; baseline
(peer_instance_id) and hmac_secret untouched.
This commit is contained in:
Jannis Braun
2026-07-02 00:34:33 +02:00
parent 769ed64314
commit d8fec00905
4 changed files with 159 additions and 4 deletions
+12 -1
View File
@@ -12,7 +12,7 @@ import { connectionManager } from '../ws/handler.js';
import { generateThumbnail } from './thumbnail.js';
import type { FederationRelayRequest, FederationRelayResponse, FederationRelayEvent } from '@backspace/shared';
import { startupBootstrapSync, onPeerDeactivated } from './federationPeerActivation.js';
import { probePeerReachable, recoverOrDetectReset } from './federationRecovery.js';
import { probePeerReachable, recoverOrDetectReset, detectResetOnNeedsAttentionPeers } from './federationRecovery.js';
import { backfillReplicatedProfileAssets } from '../routes/federation.js';
import { invokePermanentFailureCallback } from './federationRollback.js';
import { refreshPeerEpochs, getInstanceId } from './federationEpoch.js';
@@ -1180,6 +1180,17 @@ async function processHealthCheckTick(): Promise<void> {
// relay/user activity. Best-effort — a failed fetch is a benign no-op retried
// next tick, so it never disturbs the rest of the health-check work.
await refreshPeerEpochs().catch(() => {});
// ── Reset detection for needs_attention peers (design §4.1) ─────────────────
// A reset peer can land in `needs_attention` via the auth-failure path (HTTP
// up, 401/403 from a new incarnation) WITHOUT ever passing through
// `unreachable`, so the unreachable-only recovery probe never observes its
// epoch change. Probe those peers here so a reset journal is created at
// detection time (otherwise a later manual Re-peer heals nothing). Detection
// ONLY — never flips a needs_attention peer to active. Best-effort: a failure
// is a benign no-op retried next tick and must not disturb the rest of the tick.
// No shared abort signal — probePeerReachable carries its own 10s timeout.
await detectResetOnNeedsAttentionPeers().catch(() => {});
}
// ─── Federated Call Health Sweep ────────────────────────────────────────────