@@ -549,7 +549,9 @@ tooLong : Text -> Int -> Bool
549549tooLong = \s maxLen -> count (chars s) > maxLen
550550
551551isValidHex : Text -> Bool
552−isValidHex = \s -> count (chars s) > 0 && mod (count (chars s)) 2 == 0 && all (chars s) (\c -> contains c "0123456789abcdef")
552+isValidHex = \s ->
553+ let cs = chars s in
554+ count cs > 0 && mod (count cs) 2 == 0 && all cs (\c -> contains c "0123456789abcdef")
553555
554556normalizeHex : Text -> Text
555557normalizeHex = \s -> bytesToHex (bytesFromHex s)
@@ -672,17 +674,17 @@ verifyFederationAuth = \authorization fromServer scope requestTimestamp -> do
672674withFederationAuth = \authorization t respond fromServer scope requestTimestamp precondition action -> do
673675 blocked <- *blockedPeers
674676 allowed <- *allowedPeers
675− let err = \e -> respond {ok: False {}, error: e, retryAfterMs: 0}
676− if elem blocked fromServer then yield (err "blocked")
677− else if count allowed > 0 && not (elem allowed fromServer) then yield (err "not_allowed")
678− else if not precondition then yield (err "invalid_request")
679− else if requestTimestamp > 0 && abs (requestTimestamp - t) > clockSkewTolerance then yield (err "clock_skew")
677+ let err = \e retry -> respond {ok: False {}, error: e, retryAfterMs: retry}
678+ if elem blocked fromServer then yield (err "blocked" 0)
679+ else if count allowed > 0 && not (elem allowed fromServer) then yield (err "not_allowed" 0)
680+ else if not precondition then yield (err "invalid_request" 0)
681+ else if requestTimestamp > 0 && abs (requestTimestamp - t) > clockSkewTolerance then yield (err "clock_skew" 0)
680682 else do
681683 verified <- verifyFederationAuth authorization fromServer scope requestTimestamp
682− if not verified then yield (err "unauthorized")
684+ if not verified then yield (err "unauthorized" 0)
683685 else do
684686 limited <- checkServerRateGate fromServer t
685− if limited then yield (respond {ok: False {}, error: "rate_limited", retryAfterMs: federationRateWindowMs})
687+ if limited then yield (err "rate_limited" federationRateWindowMs)
686688 else action
687689
688690-- ============================================
@@ -789,8 +791,9 @@ presenceForKey = \presence pubkey t ->
789791
790792-- Resolve a key's server for client lookup, masking remote identities behind "remote".
791793maskedServer = \pres pubkey t ->
792− if any (presenceForKey pres pubkey t) (\p -> isLocalServer p.server) then serverHostN
793− else if count (presenceForKey pres pubkey t) > 0 then "remote"
794+ let entries = presenceForKey pres pubkey t in
795+ if any entries (\p -> isLocalServer p.server) then serverHostN
796+ else if count entries > 0 then "remote"
794797 else ""
795798
796799hasMessage : MessageIdHex -> PubkeyHex -> [{messageId: MessageIdHex, toKey: PubkeyHex | a}] -> Bool
@@ -1069,8 +1072,7 @@ handlePoll = \cursor generation watchKeys authorization clientIp respond -> do
10691072 else case extractToken authorization of
10701073 Nothing {} -> yield (respond (emptyPollResp 0 (False {}) (False {}) (False {}) serverGen 0 cursorWasReset))
10711074 Just {value: token} ->
1072− let effectiveCursor = if generation != serverGen then 0 else cursor in
1073− pollOnce token effectiveCursor cursorWasReset (takeRelation maxWatchKeys watchKeys) clientIp serverGen respond t
1075+ pollOnce token (if generation != serverGen then 0 else cursor) cursorWasReset (takeRelation maxWatchKeys watchKeys) clientIp serverGen respond t
10741076
10751077pollOnce = \token cursor cursorWasReset watchKeys clientIp serverGen respond t -> do
10761078 setup <- atomic do
@@ -1079,9 +1081,9 @@ pollOnce = \token cursor cursorWasReset watchKeys clientIp serverGen respond t -
10791081 Nothing {} -> yield Nothing {}
10801082 Just {value: s} -> do
10811083 let clampedCursor = clamp 0 t cursor
1082− ackResult <- if clampedCursor > 0 then ackPolledMessages s.pubkey clampedCursor t else yield {pendingAcks: []}
1084+ pendingAcks <- if clampedCursor > 0 then ackPolledMessages s.pubkey clampedCursor t else yield []
10831085 startWakeup <- *pollWakeup
1084− yield (Just {value: {pubkey: s.pubkey, clampedCursor: clampedCursor, startWakeup: startWakeup, pendingAcks: ackResult.pendingAcks}})
1086+ yield (Just {value: {pubkey: s.pubkey, clampedCursor: clampedCursor, startWakeup: startWakeup, pendingAcks: pendingAcks}})
10851087 case setup of
10861088 Nothing {} -> yield (respond (emptyPollResp 0 (False {}) (False {}) (False {}) serverGen 0 cursorWasReset))
10871089 Just {value: s} -> do
@@ -1109,8 +1111,7 @@ pollOnce = \token cursor cursorWasReset watchKeys clientIp serverGen respond t -
11091111 myDel <- collectAndPrune senderDeliveries (\x -> x.senderKey == s.pubkey) (\x -> {messageId: x.messageId, toKey: x.toKey, deliveredAt: x.deliveredAt})
11101112 myCancels <- collectAndPrune cancelNotifications (\x -> x.toKey == s.pubkey) (\x -> {messageId: x.messageId, cancelledAt: x.cancelledAt})
11111113 yield {expirations: myExp, deliveries: myDel, cancellations: myCancels}
1112− let isHeartbeat = pollResult.authorized && count pollResult.events == 0
1113− yield (respond {events: pollResult.events, cursor: pollResult.cursor, authorized: pollResult.authorized, rateLimited: False {}, draining: False {}, generation: serverGen, presenceUpdates: presenceUpdates, heartbeat: isHeartbeat, retryAfterMs: 0, cursorReset: cursorWasReset, hasMore: pollResult.hasMore, cursorStale: False {}, messageExpirations: drains.expirations, messageDeliveries: drains.deliveries, droppedDeliveryReceipts: 0, droppedExpirationNotifs: 0, loadPct: 0, messageCancellations: drains.cancellations})
1114+ yield (respond {events: pollResult.events, cursor: pollResult.cursor, authorized: pollResult.authorized, rateLimited: False {}, draining: False {}, generation: serverGen, presenceUpdates: presenceUpdates, heartbeat: pollResult.authorized && count pollResult.events == 0, retryAfterMs: 0, cursorReset: cursorWasReset, hasMore: pollResult.hasMore, cursorStale: False {}, messageExpirations: drains.expirations, messageDeliveries: drains.deliveries, droppedDeliveryReceipts: 0, droppedExpirationNotifs: 0, loadPct: 0, messageCancellations: drains.cancellations})
11141115
11151116doPoll = \s t -> do
11161117 let collectAt = \tnow -> atomic do
@@ -1140,8 +1141,7 @@ ackPolledMessages = \pubkey cursor t -> do
11401141 let mids = pluck toAck (\m -> m.messageId)
11411142 set *messages = removeWhere msgs matches
11421143 pruneRef forwards (\f -> not (f.toKey == pubkey && elem mids f.messageId))
1143− ackedFederated <- recordAcksAndDeliveries toAck t
1144− yield {pendingAcks: ackedFederated}
1144+ recordAcksAndDeliveries toAck t
11451145
11461146-- ============================================
11471147-- Handler: send