feat(server): reclassify undeliverable targeted-peer as no_recipient failure (#18)

sendFederatedCallStart now treats a 200-with-undeliverable-messageId as
a peer-failure instead of unconditional success. Feeds the existing
failures[] array and terminal-determination machinery from #16.
New sendFederatedCallStartForTest export mirrors the existing
handleDm*ForTest pattern. TDD — three tests cover single-peer terminal
no_recipient, group-DM mixed delivered+undeliverable non-terminal, and
the happy-path (empty undeliverable → no event).

Also hardens sendCallRelay's response parse: validates undeliverable
is an Array and entries are well-shaped, logs protocol drift at warn/debug
rather than silently falling back to old-peer semantics.
This commit is contained in:
Jannis Braun
2026-04-24 21:11:11 +02:00
parent 7d2137b6d4
commit 26a4925032
3 changed files with 287 additions and 5 deletions
+16 -1
View File
@@ -1925,10 +1925,24 @@ async function sendFederatedCallStart(
}
// ─── Targeted relay: fan out in parallel, await results ────────────────────
// Each peer's result has THREE possible classifications:
// ok=true, messageId NOT in undeliverable → delivered
// ok=true, messageId IN undeliverable → new: no_recipient failure (#18)
// ok=false → existing failure reasons
const targetedResults = await Promise.all(
Array.from(targetedPeers.keys()).map(async peerOrigin => {
const result = await sendCallRelay(peerOrigin, [buildRelayEvent()]);
const relayEvent = buildRelayEvent();
const result = await sendCallRelay(peerOrigin, [relayEvent]);
if (result.ok) {
if (result.undeliverable.includes(relayEvent.messageId)) {
console.warn(`[federation] dm_call_start to ${peerOrigin}: remote had no recipient`);
return {
origin: peerOrigin,
ok: false as const,
reason: 'no_recipient' as const satisfies DmCallUndeliverableReason,
error: 'remote reported no_recipient',
};
}
return { origin: peerOrigin, ok: true as const };
}
const reason = mapCallReasonToEventReason(result.reason);
@@ -2483,3 +2497,4 @@ export function registerCallRelayHooks(): void {
export const handleDmCallAcceptForTest = handleDmCallAccept;
export const handleDmCallRejectForTest = handleDmCallReject;
export const handleDmCallEndForTest = handleDmCallEnd;
export const sendFederatedCallStartForTest = sendFederatedCallStart;