feat(federation): pass explicit intent at every ensurePeered call site

- social.ts friend-add: user_action, with 409 peer_pending_local_admin
  when gate fires
- /peer/ensure: user_action, surfaces peeringStatus: 'admin_required'
- sendCallRelay (typing warm-up + call relay): system intent
- federationWorker resolvePendingPeers: system intent (defensive — gate
  is unreachable from here since pending rows already exist)
- CallRelayFailureReason: peer_admin_required added (mapped to
  peer_transient_failure on the user-facing event surface, since system
  intent should never legitimately surface admin_required)
- Test files: thread intent arg through racePeering and ensurePeered
  calls (positional shift from racePeering signature change)
- outboundGate.test.ts: tighten noUncheckedIndexedAccess access via
  non-null assertions after toHaveLength()
- docs/systems/social.md: peer_pending_local_admin error code documented
This commit is contained in:
Jannis Braun
2026-04-26 21:37:19 +02:00
parent ef80e3b416
commit 4d4dc383d7
10 changed files with 81 additions and 50 deletions
+19 -5
View File
@@ -798,14 +798,26 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
}
const { ensurePeered } = await import('../utils/federationPeering.js');
const result = await ensurePeered(remoteOrigin);
// NOTE: /peer/ensure is currently only invoked from friend-add client paths
// (see packages/web/src/stores/instanceStore.ts ensurePeered references).
// The hardcoded reason here is correct TODAY but will become wrong when
// DM-to-stranger or space-join grow into the gate. When that happens,
// surface the reason and target through the request body instead. Do NOT
// silently leave the hardcoding in place when adding a new caller.
const result = await ensurePeered(remoteOrigin, {
kind: 'user_action',
userId: request.userId,
reason: 'friend_add',
target: remoteOrigin,
});
// NOTE: The internal EnsurePeeredResult status names differ from the client-facing
// peeringStatus values. The mapping:
// 'active' → 'active' (peer is live)
// 'rejected' → 'rejected' (permanently blocked)
// 'pending' → 'awaiting_approval' (queued on remote, waiting for admin)
// 'failed' → 'pending' (transient error, will retry automatically)
// 'active' → 'active' (peer is live)
// 'rejected' → 'rejected' (permanently blocked)
// 'pending' → 'awaiting_approval' (queued on remote, waiting for admin)
// 'failed' → 'pending' (transient error, will retry automatically)
// 'admin_required' → 'admin_required' (local outbound gate fired — our admin must approve)
// The internal 'pending' means "we got a 202 from the remote — admin hasn't acted yet",
// while 'failed' means "network/timeout — the outbox worker will retry next tick".
// The client sees 'awaiting_approval' (actionable info) vs 'pending' (transient, will resolve).
@@ -818,6 +830,8 @@ export async function federationRoutes(app: FastifyInstance): Promise<void> {
return reply.code(200).send({ peeringStatus: 'awaiting_approval', error: result.error });
case 'failed':
return reply.code(200).send({ peeringStatus: 'pending', error: result.error });
case 'admin_required':
return reply.code(200).send({ peeringStatus: 'admin_required' });
default:
return reply.code(200).send({ peeringStatus: 'pending', error: 'Unknown peering result' });
}
+13 -1
View File
@@ -178,7 +178,12 @@ async function handleFederatedFriendRequest(
}
// 2. ensurePeered — block until 'active', or surface peer status as error
const peering = await ensurePeered(peerOrigin);
const peering = await ensurePeered(peerOrigin, {
kind: 'user_action',
userId: sender.id,
reason: 'friend_add',
target: `${baseName}@${targetDomain}`,
});
if (peering.status === 'rejected') {
return reply.code(403).send({ error: 'peer_rejected', statusCode: 403, domain: targetDomain });
}
@@ -195,6 +200,13 @@ async function handleFederatedFriendRequest(
}
return reply.code(409).send({ error: 'peer_pending', statusCode: 409, domain: targetDomain });
}
if (peering.status === 'admin_required') {
return reply.code(409).send({
error: 'peer_pending_local_admin',
statusCode: 409,
domain: targetDomain,
});
}
// peering.status === 'active' — continue
// 3. Lookup