@@ -592,7 +592,6 @@ pct : Int -> Int -> Int
592592pct = \cur maxN -> if maxN > 0 then cur * 100 / maxN else 0
593593
594594-- Common ack/delete tail: pull matching messages, drop them and their forwards.
595−removeForKey : (forall a. {toKey: PubkeyHex, messageId: MessageIdHex | a} -> Bool) -> IO {reads *forwards, reads *messages, writes *forwards, writes *messages} [Message]
596595removeForKey = \pred -> do
597596 msgs <- *messages
598597 fwds <- *forwards
@@ -731,19 +730,19 @@ extractToken = \h -> if hasPrefix "Bearer " h && drop 7 h != ""
731730then Just {value: drop 7 h}
732731else Nothing {}
733732
734−verifyMaintenanceAuth : Text -> IO {reads *runtimeMaintenanceSecret} Bool
733+verifyMaintenanceAuth : Text -> IO {r *runtimeMaintenanceSecret} Bool
735734verifyMaintenanceAuth = \authorization -> do
736735 secret <- *runtimeMaintenanceSecret
737736 yield (secret != "" && extractToken authorization == Just {value: secret})
738737
739−withMaintAuth : Text -> IO {| effs} a -> IO {| effs} a -> IO {reads *runtimeMaintenanceSecret | effs} a
738+withMaintAuth : Text -> IO {| effs} a -> IO {| effs} a -> IO {r *runtimeMaintenanceSecret | effs} a
740739withMaintAuth = \authorization onUnauth onAuth -> do
741740 authorized <- verifyMaintenanceAuth authorization
742741 if authorized then onAuth else onUnauth
743742
744743-- Common gate for handlers: optional drain check, global rate, session auth.
745744-- mkErr is called with one of: "draining", "rate_limited", "unauthorized".
746−gateAuth : Bool -> Text -> Text -> Timestamp -> (resp -> ret) -> (Text -> resp) -> (PubkeyHex -> IO {} ret) -> IO {reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart} ret
745+gateAuth : Bool -> Text -> Text -> Timestamp -> (resp -> ret) -> (Text -> resp) -> (PubkeyHex -> IO {} ret) -> IO {r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, r *sessions} ret
747746gateAuth = \checkDrain auth ip t respond mkErr onAuth -> do
748747 dp <- *drainPhase
749748 g <- checkGlobalRate t
@@ -753,7 +752,7 @@ gateAuth = \checkDrain auth ip t respond mkErr onAuth -> do
753752 else if g then err "rate_limited" else withSessionAuth auth t ip (err "unauthorized") onAuth
754753
755754-- gateAuth + per-key rate gate. `rateResp` is the response value when the per-key gate trips.
756−gateClientWrite : Text -> Text -> Int<Ms> -> (a -> b) -> (Text -> a) -> a -> (Text -> IO {} b) -> IO {reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart} b
755+gateClientWrite : Text -> Text -> Int<Ms> -> (a -> b) -> (Text -> a) -> a -> (Text -> IO {} b) -> IO {r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, r *sessions} b
757756gateClientWrite = \auth ip t respond mkErr rateResp onAuth ->
758757 gateAuth (True {}) auth ip t respond mkErr (\pk -> do
759758 lim <- atomic do
@@ -764,7 +763,7 @@ gateClientWrite = \auth ip t respond mkErr rateResp onAuth ->
764763 yield r.limited
765764 if lim then yield (respond rateResp) else onAuth pk)
766765
767−gateLookup : Text -> Text -> Int<Ms> -> (a -> b) -> (Text -> a) -> a -> (Text -> IO {} b) -> IO {reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart} b
766+gateLookup : Text -> Text -> Int<Ms> -> (a -> b) -> (Text -> a) -> a -> (Text -> IO {} b) -> IO {r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, r *sessions} b
768767gateLookup = \auth ip t respond mkErr rateResp onAuth ->
769768 gateAuth (False {}) auth ip t respond mkErr (\pk -> do
770769 lim <- atomic do
@@ -775,7 +774,7 @@ gateLookup = \auth ip t respond mkErr rateResp onAuth ->
775774 yield r.limited
776775 if lim then yield (respond rateResp) else onAuth pk)
777776
778−withSessionAuth : Text -> Int<Ms> -> Text -> IO {| e} a -> (PubkeyHex -> IO {| e} a) -> IO {reads *sessions | e} a
777+withSessionAuth : Text -> Int<Ms> -> Text -> IO {| e} a -> (PubkeyHex -> IO {| e} a) -> IO {r *sessions | e} a
779778withSessionAuth = \authorization t clientIp onUnauth onAuth -> do
780779 ses <- *sessions
781780 case extractToken authorization of
@@ -784,7 +783,7 @@ withSessionAuth = \authorization t clientIp onUnauth onAuth -> do
784783 Just {value: s} -> onAuth s.pubkey
785784 _ -> onUnauth
786785
787−generateFederationAuth : Text -> a -> IO {reads *federationPrivateKeyHex, random} Text
786+generateFederationAuth : Text -> a -> IO {r *federationPrivateKeyHex, random} Text
788787generateFederationAuth = \scope requestTimestamp -> do
789788 hex <- *federationPrivateKeyHex
790789 let privKey = if hex == "" then Nothing {} else bytesFromHex hex
@@ -858,7 +857,7 @@ checkRate = \entries key maxRate windowMs t -> (\cur -> if cur.count > maxRate t
858857 Just {value: r} -> {r.windowStart, count: r.count + 1}
859858 _ -> {windowStart: t, count: 1})
860859
861−checkGlobalRate : Timestamp -> IO {reads *globalRateCount, reads *globalRateWindowStart, writes *globalRateCount, writes *globalRateWindowStart} Bool
860+checkGlobalRate : Timestamp -> IO {rw *globalRateCount, rw *globalRateWindowStart} Bool
862861checkGlobalRate = \t -> atomic (do
863862 ws <- *globalRateWindowStart
864863 c <- *globalRateCount
@@ -887,7 +886,7 @@ checkAuthGlobalRate = \t -> atomic (do
887886 *authGlobalRateCount = c + 1
888887 yield (False {}))
889888
890−checkIpRateGate : Text -> Int<Ms> -> IO {reads *ipRates, writes *ipRates} Bool
889+checkIpRateGate : Text -> Int<Ms> -> IO {rw *ipRates} Bool
891890checkIpRateGate = \ip t -> if ip == ""
892891then yield (False {})
893892else atomic (do
@@ -896,7 +895,7 @@ else atomic (do
896895 replace *ipRates = r.entries
897896 yield r.limited)
898897
899−checkServerRateGate : Text -> Int -> IO {reads *federationRates, writes *federationRates} Bool
898+checkServerRateGate : Text -> Int -> IO {rw *federationRates} Bool
900899checkServerRateGate = \server t -> atomic (do
901900 rates <- *federationRates
902901 let r = checkRate rates server maxFederationRatePerServer federationRateWindowMs t
@@ -904,7 +903,7 @@ checkServerRateGate = \server t -> atomic (do
904903 yield r.limited)
905904
906905-- Pre-auth gate: drain + global + ip + auth-global. Returns "" when ok, else an error code.
907−authPreCheck : Text -> Int<Ms> -> IO {reads *authGlobalRateCount, reads *authGlobalRateWindowStart, reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *ipRates, writes *authGlobalRateCount, writes *authGlobalRateWindowStart, writes *globalRateCount, writes *globalRateWindowStart, writes *ipRates} Text
906+authPreCheck : Text -> Int<Ms> -> IO {rw *authGlobalRateCount, rw *authGlobalRateWindowStart, r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, rw *ipRates} Text
908907authPreCheck = \clientIp t -> do
909908 dp <- *drainPhase
910909 g <- checkGlobalRate t
@@ -930,7 +929,7 @@ retryBackoff = \failures -> min retryBackoffMax (retryBackoffBase * max 1 failur
930929shouldRetryServer : Text -> Timestamp -> [ForwardFailure] -> Bool
931930shouldRetryServer = \server t failures -> none failures (\f -> f.server == server && t - f.failedAt < retryBackoff f.failures)
932931
933−recordFailure : Text -> IO {reads *forwardFailures, writes *forwardFailures, clock} {}
932+recordFailure : Text -> IO {rw *forwardFailures, clock} {}
934933recordFailure = \server -> do
935934 t <- now
936935 atomic (do
@@ -943,7 +942,7 @@ recordFailure = \server -> do
943942 {server, failedAt: t, prev.failures, prev.firstFailedAt}
944943 ])
945944
946−clearFailure : Text -> IO {reads *forwardFailures, writes *forwardFailures} {}
945+clearFailure : Text -> IO {rw *forwardFailures} {}
947946clearFailure = \server -> atomic (do
948947 forwardFailures <- *forwardFailures
949948 *forwardFailures = filter forwardFailures (\f -> f.server != server))
@@ -1002,7 +1001,7 @@ mergePresence = \existing incoming -> fold (\acc e -> (\same -> if any acc (\x -
10021001-- block. Returns the locally-expired presence rows whose pubkey has no other
10031002-- live local session, so the caller can broadcast offline gossip for them.
10041003
1005−pruneExpired : Int<Ms> -> IO {reads *ackedIds, reads *activePolls, reads *challenges, reads *deliveredIds, reads *forwardFailures, reads *forwards, reads *messages, reads *presence, reads *senderDeliveries, reads *senderExpirations, reads *sessions, writes *ackedIds, writes *activePolls, writes *challenges, writes *deliveredIds, writes *forwardFailures, writes *forwards, writes *messages, writes *presence, writes *senderDeliveries, writes *senderExpirations, writes *sessions} [{expiresAt: Timestamp, pubkey: PubkeyHex, server: AnyServerName, timestamp: Timestamp}]
1004+pruneExpired : Int<Ms> -> IO {rw *ackedIds, rw *activePolls, rw *challenges, rw *deliveredIds, rw *forwardFailures, rw *forwards, rw *messages, rw *presence, rw *senderDeliveries, rw *senderExpirations, rw *sessions} [{expiresAt: Timestamp, pubkey: PubkeyHex, server: AnyServerName, timestamp: Timestamp}]
10061005pruneExpired = \t -> atomic do
10071006 ch <- *challenges
10081007 *challenges = filter ch (\x -> x.expiresAt > t)
@@ -1055,7 +1054,7 @@ mkAckedEntry = \m t -> {m.messageId, m.toKey, ackedAt: t, m.originServer, retrie
10551054
10561055-- Record federation acks and sender deliveries for a batch of just-removed messages.
10571056-- Returns the federated-ack entries (the caller flushes them remotely later).
1058−recordAcksAndDeliveries : [{messageId: MessageIdHex, originServer: Text, senderKey: Text, toKey: PubkeyHex | a}] -> Int<Ms> -> IO {reads *ackedIds, reads *senderDeliveries, writes *ackedIds, writes *senderDeliveries} [{ackedAt: Timestamp, messageId: MessageIdHex, originServer: ServerName, retries: Int, toKey: PubkeyHex}]
1057+recordAcksAndDeliveries : [{messageId: MessageIdHex, originServer: Text, senderKey: Text, toKey: PubkeyHex | a}] -> Int<Ms> -> IO {rw *ackedIds, rw *senderDeliveries} [{ackedAt: Timestamp, messageId: MessageIdHex, originServer: ServerName, retries: Int, toKey: PubkeyHex}]
10591058recordAcksAndDeliveries = \toAck t -> do
10601059 let ackedFed = mapWhere toAck (\m -> m.originServer != "") (\m -> mkAckedEntry m t)
10611060 let myDel = mapWhere toAck (\m -> m.senderKey != "" && m.originServer == "") (\m -> {
@@ -1089,7 +1088,7 @@ sendBatchedGossip = \peers events -> forEach peers (\server -> do
10891088 Err _ -> recordFailure server
10901089 Ok _ -> clearFailure server))
10911090
1092−broadcastOfflineGossip : Int<Ms> -> [{pubkey: PubkeyHex | a}] -> IO {reads *presence} {}
1091+broadcastOfflineGossip : Int<Ms> -> [{pubkey: PubkeyHex | a}] -> IO {r *presence} {}
10931092broadcastOfflineGossip = \t expiredLocal -> do
10941093 presence <- *presence
10951094 when (federationEnabled && count expiredLocal > 0) (sendBatchedGossip (knownPeerServersFromPresence presence) (pluck expiredLocal (\p -> mkGossip (GossipOffline {}) p.pubkey t offlineGossipMinTtl)))
@@ -1105,7 +1104,7 @@ broadcastOfflineGossip = \t expiredLocal -> do
11051104-- comes online: any messages already buffered for that pubkey are enqueued
11061105-- as forwards to the newly-known server.
11071106
1108−retryPendingForwards : IO {reads *blockedPeers, reads *forwardFailures, reads *forwards, clock} {}
1107+retryPendingForwards : IO {r *blockedPeers, r *forwardFailures, r *forwards, clock} {}
11091108retryPendingForwards = do
11101109 t <- now
11111110 fwds <- *forwards
@@ -1176,7 +1175,7 @@ sendAcksAndClean = \pendingAcks -> forEach (collectPeers pendingAcks (\a -> a.or
11761175 *ackedIds = filter aids (\a -> not (a.originServer == server && elem mids a.messageId))))
11771176
11781177-- Forward queued messages to a server when a remote key comes online.
1179−forwardQueuedToServer : PubkeyHex -> ServerName -> Int<Ms> -> IO {reads *forwards, reads *messages, writes *forwards} {}
1178+forwardQueuedToServer : PubkeyHex -> ServerName -> Int<Ms> -> IO {rw *forwards, r *messages} {}
11801179forwardQueuedToServer = \pubkey server t -> atomic (do
11811180 msgs <- *messages
11821181 fwds <- *forwards
@@ -1202,7 +1201,6 @@ forwardQueuedToServer = \pubkey server t -> atomic (do
12021201-- expire stale rows, then opportunistically retry pending forwards and refresh
12031202-- local presence so peers don't see us go offline mid-session.
12041203
1205−pruneAndRetry : Int<Ms> -> IO {reads *ackedIds, reads *activePolls, reads *challenges, reads *deliveredIds, reads *federationPrivateKeyHex, reads *forwardFailures, reads *forwards, reads *lastPrunedAt, reads *messages, reads *presence, reads *senderDeliveries, reads *senderExpirations, reads *sessions, writes *ackedIds, writes *activePolls, writes *challenges, writes *deliveredIds, writes *forwardFailures, writes *forwards, writes *lastPrunedAt, writes *messages, writes *presence, writes *senderDeliveries, writes *senderExpirations, writes *sessions, reads *blockedPeers, reads *lastPresenceRefresh, writes *lastPresenceRefresh, clock} {}
12061204pruneAndRetry = \t -> do
12071205 *lastPrunedAt = t
12081206 expiredLocal <- pruneExpired t
@@ -1216,7 +1214,7 @@ pruneAndRetry = \t -> do
12161214 *lastPresenceRefresh = t
12171215 refreshLocalPresence t))
12181216
1219−refreshLocalPresence : Int<Ms> -> IO {reads *presence, reads *sessions, writes *presence} {}
1217+refreshLocalPresence : Int<Ms> -> IO {rw *presence, r *sessions} {}
12201218refreshLocalPresence = \t -> do
12211219 refreshState <- atomic (do
12221220 sessions <- *sessions
@@ -1251,7 +1249,7 @@ refreshLocalPresence = \t -> do
12511249-- POST /auth/challenge: issue a fresh signed-nonce challenge for `pubkey`.
12521250-- Capped per-key (maxChallengesPerKey) and rate-limited; the IP is recorded
12531251-- so /auth/verify can require the same client.
1254−handleChallenge : PubkeyHex -> Text -> ({challenge: Text, error: Text, expiresAt: Int, ok: Bool, retryAfterMs: Int} -> a) -> IO {reads *authGlobalRateCount, reads *authGlobalRateWindowStart, reads *challengeRates, reads *challenges, reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *ipRates, writes *authGlobalRateCount, writes *authGlobalRateWindowStart, writes *challengeRates, writes *challenges, writes *globalRateCount, writes *globalRateWindowStart, writes *ipRates, clock, random} a
1252+handleChallenge : PubkeyHex -> Text -> ({challenge: Text, error: Text, expiresAt: Int, ok: Bool, retryAfterMs: Int} -> a) -> IO {rw *authGlobalRateCount, rw *authGlobalRateWindowStart, rw *challengeRates, rw *challenges, r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, rw *ipRates, clock, random} a
12551253handleChallenge = \pubkey clientIp respond -> do
12561254 t <- now
12571255 preErr <- if requireClientIp && clientIp == ""
@@ -1289,7 +1287,7 @@ handleChallenge = \pubkey clientIp respond -> do
12891287-- On success, mints a session token, records local presence (`pubkey` is now
12901288-- live on this server), and gossips an `online` event to known peers. If
12911289-- `revokeOthers` is set, all other sessions for the same pubkey are dropped.
1292−handleVerifyAuth : PubkeyHex -> Text -> Text -> Bool -> Text -> ({error: Text, expiresAt: Int, token: Text} -> a) -> IO {reads *authGlobalRateCount, reads *authGlobalRateWindowStart, reads *challenges, reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *ipRates, reads *presence, reads *sessions, writes *authGlobalRateCount, writes *authGlobalRateWindowStart, writes *challenges, writes *globalRateCount, writes *globalRateWindowStart, writes *ipRates, writes *presence, writes *sessions, clock, random} a
1290+handleVerifyAuth : PubkeyHex -> Text -> Text -> Bool -> Text -> ({error: Text, expiresAt: Int, token: Text} -> a) -> IO {rw *authGlobalRateCount, rw *authGlobalRateWindowStart, rw *challenges, r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, rw *ipRates, rw *presence, rw *sessions, clock, random} a
12931291handleVerifyAuth = \pubkey challenge signature revokeOthers clientIp respond -> do
12941292 t <- now
12951293 preErr <- authPreCheck clientIp t
@@ -1336,7 +1334,7 @@ handleVerifyAuth = \pubkey challenge signature revokeOthers clientIp respond ->
13361334
13371335-- POST /auth/revoke: caller (any session for the pubkey) revokes a sibling
13381336-- session by token. Returns ok=False if no such sibling existed.
1339−handleRevoke : Text -> Text -> Text -> ({authorized: Bool, ok: Bool} -> a) -> IO {reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart, clock} a
1337+handleRevoke : Text -> Text -> Text -> ({authorized: Bool, ok: Bool} -> a) -> IO {r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, r *sessions, clock} a
13401338handleRevoke = \targetToken authorization clientIp respond -> do
13411339 t <- now
13421340 gateAuth (False {}) authorization clientIp t respond (\_ -> {ok: False {}, authorized: False {}}) (\caller -> do
@@ -1349,7 +1347,7 @@ handleRevoke = \targetToken authorization clientIp respond -> do
13491347
13501348-- POST /auth/logout: drop the caller's own session. If this was the last
13511349-- session for the pubkey, also clear local presence and gossip offline.
1352−handleLogout : Text -> Text -> ({authorized: Bool, ok: Bool} -> a) -> IO {reads *sessions, clock} a
1350+handleLogout : Text -> Text -> ({authorized: Bool, ok: Bool} -> a) -> IO {r *sessions, clock} a
13531351handleLogout = \authorization clientIp respond -> do
13541352 t <- now
13551353 let myToken = case extractToken authorization of
@@ -1389,7 +1387,7 @@ handleLogout = \authorization clientIp respond -> do
13891387-- POST /poll: top-level dispatch. Validates drain/global gates, detects a
13901388-- generation mismatch (server restart) to force a cursor reset, then hands
13911389-- off to `pollOnce` for the actual long-poll body.
1392−handlePoll : Int -> Int -> Text -> Text -> ({authorized: Bool, cursor: Timestamp, cursorReset: Bool, cursorStale: Bool, draining: Bool, droppedDeliveryReceipts: Int, droppedExpirationNotifs: Int, events: [PollEvent], generation: Int, hasMore: Bool, heartbeat: Bool, loadPct: Int, messageDeliveries: [DeliveredEntry], messageExpirations: [{expiredAt: Timestamp, messageId: MessageIdHex, toKey: PubkeyHex}], rateLimited: Bool, retryAfterMs: Int} -> Response) -> IO {reads *activePolls, reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *pollWakeup, reads *senderDeliveries, reads *senderExpirations, reads *serverGeneration, reads *sessions, writes *activePolls, writes *globalRateCount, writes *globalRateWindowStart, writes *senderDeliveries, writes *senderExpirations, clock} Response
1390+handlePoll : Int -> Int -> Text -> Text -> ({authorized: Bool, cursor: Timestamp, cursorReset: Bool, cursorStale: Bool, draining: Bool, droppedDeliveryReceipts: Int, droppedExpirationNotifs: Int, events: [PollEvent], generation: Int, hasMore: Bool, heartbeat: Bool, loadPct: Int, messageDeliveries: [DeliveredEntry], messageExpirations: [{expiredAt: Timestamp, messageId: MessageIdHex, toKey: PubkeyHex}], rateLimited: Bool, retryAfterMs: Int} -> Response) -> IO {rw *activePolls, r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, r *pollWakeup, rw *senderDeliveries, rw *senderExpirations, r *serverGeneration, r *sessions, clock} Response
13931391handlePoll = \cursor generation authorization clientIp respond -> do
13941392 t <- now
13951393 serverGen <- *serverGeneration
@@ -1407,7 +1405,7 @@ handlePoll = \cursor generation authorization clientIp respond -> do
14071405-- (`ackPolledMessages`), acquires a per-pubkey poll slot, runs `doPoll` to
14081406-- collect events (or block until wakeup), then assembles the response with
14091407-- piggybacked expiration/delivery metadata.
1410−pollOnce : Text -> Int -> Bool -> Text -> Int -> ({authorized: Bool, cursor: Timestamp, cursorReset: Bool, cursorStale: Bool, draining: Bool, droppedDeliveryReceipts: Int, droppedExpirationNotifs: Int, events: [PollEvent], generation: Int, hasMore: Bool, heartbeat: Bool, loadPct: Int, messageDeliveries: [DeliveredEntry], messageExpirations: [{expiredAt: Timestamp, messageId: MessageIdHex, toKey: PubkeyHex}], rateLimited: Bool, retryAfterMs: Int} -> Response) -> Int<Ms> -> IO {reads *activePolls, reads *pollWakeup, reads *senderDeliveries, reads *senderExpirations, reads *sessions, writes *activePolls, writes *senderDeliveries, writes *senderExpirations, clock} Response
1408+pollOnce : Text -> Int -> Bool -> Text -> Int -> ({authorized: Bool, cursor: Timestamp, cursorReset: Bool, cursorStale: Bool, draining: Bool, droppedDeliveryReceipts: Int, droppedExpirationNotifs: Int, events: [PollEvent], generation: Int, hasMore: Bool, heartbeat: Bool, loadPct: Int, messageDeliveries: [DeliveredEntry], messageExpirations: [{expiredAt: Timestamp, messageId: MessageIdHex, toKey: PubkeyHex}], rateLimited: Bool, retryAfterMs: Int} -> Response) -> Int<Ms> -> IO {rw *activePolls, r *pollWakeup, rw *senderDeliveries, rw *senderExpirations, r *sessions, clock} Response
14111409pollOnce = \authorization cursor cursorWasReset clientIp serverGen respond t -> do
14121410 setup <- atomic withSessionAuth authorization t clientIp (yield (Nothing {})) (\pubkey -> do
14131411 let clampedCursor = clamp 0 t cursor
@@ -1438,10 +1436,7 @@ pollOnce = \authorization cursor cursorWasReset clientIp serverGen respond t ->
14381436 sdItems <- *senderDeliveries
14391437 *senderDeliveries = removeWhere sdItems mine
14401438 let myDel = mapWhere sdItems mine (\x -> {x.messageId, x.toKey, x.deliveredAt})
1441− yield {
1442− expirations: myExp,
1443− deliveries: myDel
1444− })
1439+ yield {expirations: myExp, deliveries: myDel})
14451440 yield (respond {
14461441 pollResult.events,
14471442 pollResult.cursor,
@@ -1463,7 +1458,7 @@ pollOnce = \authorization cursor cursorWasReset clientIp serverGen respond t ->
14631458
14641459-- One pass: collect events at `t`. If empty, retry-wait on `pollWakeup`
14651460-- (bumped by send/heartbeat) and re-collect once at the new wall time.
1466−doPoll : {clampedCursor: Int, pendingAcks: [AckedEntry], pubkey: PubkeyHex, startWakeup: Timestamp} -> Int<Ms> -> IO {reads *pollWakeup, clock} {cursor: Int<Ms>, events: [{eventType: Text, id: MessageIdHex, payload: {encryptedBlob: BlobHex, expiresAt: Timestamp, timestamp: Timestamp, toKey: PubkeyHex}}], hasMore: Bool}
1461+doPoll : {clampedCursor: Int, pendingAcks: [AckedEntry], pubkey: PubkeyHex, startWakeup: Timestamp} -> Int<Ms> -> IO {r *pollWakeup, clock} {cursor: Int<Ms>, events: [{eventType: Text, id: MessageIdHex, payload: {encryptedBlob: BlobHex, expiresAt: Timestamp, timestamp: Timestamp, toKey: PubkeyHex}}], hasMore: Bool}
14671462doPoll = \s t -> do
14681463 let collectAt = \tnow -> atomic (do
14691464 msgs <- *messages
@@ -1484,7 +1479,7 @@ collectPollEvents = \messages pubkey cursor t -> (\myMsgs -> (\limited -> {event
14841479-- Treat the supplied cursor as an ack: drop all messages with
14851480-- receivedAt <= cursor, plus their pending forwards, and record federated
14861481-- acks/sender deliveries via `recordAcksAndDeliveries`.
1487−ackPolledMessages : PubkeyHex -> Int -> Int<Ms> -> IO {reads *ackedIds, reads *forwards, reads *messages, reads *senderDeliveries, writes *ackedIds, writes *forwards, writes *messages, writes *senderDeliveries} [{ackedAt: Timestamp, messageId: MessageIdHex, originServer: ServerName, retries: Int, toKey: PubkeyHex}]
1482+ackPolledMessages : PubkeyHex -> Int -> Int<Ms> -> IO {rw *ackedIds, rw *forwards, rw *messages, rw *senderDeliveries} [{ackedAt: Timestamp, messageId: MessageIdHex, originServer: ServerName, retries: Int, toKey: PubkeyHex}]
14881483ackPolledMessages = \pubkey cursor t -> do
14891484 msgs <- *messages
14901485 let matches = \m -> m.toKey == pubkey && m.receivedAt <= cursor
@@ -1510,7 +1505,7 @@ ackPolledMessages = \pubkey cursor t -> do
15101505-- POST /messages: send one ciphertext to `to`. Rejects on draining/rate
15111506-- limit/stale timestamp; otherwise calls `sendMessageCore` inside an atomic
15121507-- block and forks the federation forward (if any) once the local commit lands.
1513−handleSendMessage : PubkeyHex -> BlobHex -> Int<Ms> -> OptionalMessageId -> Int -> Text -> Text -> ({error: Text, loadPct: Int, messageId: OptionalMessageId, retryAfterMs: Int, status: MessageStatus} -> a) -> IO {reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart, clock, random} a
1508+handleSendMessage : PubkeyHex -> BlobHex -> Int<Ms> -> OptionalMessageId -> Int -> Text -> Text -> ({error: Text, loadPct: Int, messageId: OptionalMessageId, retryAfterMs: Int, status: MessageStatus} -> a) -> IO {r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, r *sessions, clock, random} a
15141509handleSendMessage = \to encryptedBlob timestamp messageId expiresInMs authorization clientIp respond -> do
15151510 t <- now
15161511 dp <- *drainPhase
@@ -1543,7 +1538,7 @@ handleSendMessage = \to encryptedBlob timestamp messageId expiresInMs authorizat
15431538-- `seqCounter`, appends the message, and pre-stages forwards for any remote
15441539-- presence rows that already exist for `to`. Returns the chosen status and
15451540-- the list of remote servers the caller should kick forwards to.
1546−sendMessageCore : PubkeyHex -> PubkeyHex -> BlobHex -> Int<Ms> -> OptionalMessageId -> Int -> Int<Ms> -> IO {reads *ackedIds, reads *deliveredIds, reads *forwards, reads *messages, reads *pairRates, reads *pollWakeup, reads *presence, reads *recvRates, reads *sendRates, reads *seqCounter, writes *forwards, writes *messages, writes *pairRates, writes *pollWakeup, writes *recvRates, writes *sendRates, writes *seqCounter} {remoteServers: [Text], status: MessageStatus}
1541+sendMessageCore : PubkeyHex -> PubkeyHex -> BlobHex -> Int<Ms> -> OptionalMessageId -> Int -> Int<Ms> -> IO {r *ackedIds, r *deliveredIds, rw *forwards, rw *messages, rw *pairRates, rw *pollWakeup, r *presence, rw *recvRates, rw *sendRates, rw *seqCounter} {remoteServers: [Text], status: MessageStatus}
15471542sendMessageCore = \senderKey to encryptedBlob clampedTs effMsgId expiresInMs t -> do
15481543 msgs <- *messages
15491544 aids <- *ackedIds
@@ -1658,7 +1653,7 @@ forkForwardsTo = \effMsgId to encryptedBlob clampedTs servers -> forEach servers
16581653-- GET /lookup/{pubkey}: return our local server name if the key is here,
16591654-- "remote" if known on a peer, "" otherwise. Peer hostnames are deliberately
16601655-- masked (see section header).
1661−handleLookupPresence : PubkeyHex -> Text -> Text -> ({authorized: Bool, error: Text, server: Text} -> a) -> IO {reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart, clock} a
1656+handleLookupPresence : PubkeyHex -> Text -> Text -> ({authorized: Bool, error: Text, server: Text} -> a) -> IO {r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, r *sessions, clock} a
16621657handleLookupPresence = \pubkey authorization clientIp respond -> do
16631658 t <- now
16641659 let mkErr = \e -> {server: "", authorized: False {}, error: if e == "rate_limited" then e else ""}
@@ -1672,7 +1667,7 @@ handleLookupPresence = \pubkey authorization clientIp respond -> do
16721667
16731668-- POST /lookup/batch: same masking as `handleLookupPresence`, but for up to
16741669-- 50 pubkeys in one round-trip.
1675−handleBatchLookupPresence : [PubkeyHex] -> Text -> Text -> ({authorized: Bool, error: Text, results: [{pubkey: PubkeyHex, server: Text}]} -> a) -> IO {reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart, clock} a
1670+handleBatchLookupPresence : [PubkeyHex] -> Text -> Text -> ({authorized: Bool, error: Text, results: [{pubkey: PubkeyHex, server: Text}]} -> a) -> IO {r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, r *sessions, clock} a
16761671handleBatchLookupPresence = \pubkeys authorization clientIp respond -> do
16771672 t <- now
16781673 let mkErr = \e -> {
@@ -1692,7 +1687,7 @@ handleBatchLookupPresence = \pubkeys authorization clientIp respond -> do
16921687-- POST /messages/status: classify a message as
16931688-- pending (in mailbox), forwarding (en-route to a peer),
16941689-- delivered (acked or terminal), or unknown.
1695−handleMessageStatus : PubkeyHex -> MessageIdHex -> Text -> Text -> ({authorized: Bool, status: Text} -> a) -> IO {reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart, clock} a
1690+handleMessageStatus : PubkeyHex -> MessageIdHex -> Text -> Text -> ({authorized: Bool, status: Text} -> a) -> IO {r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, r *sessions, clock} a
16961691handleMessageStatus = \to messageId authorization clientIp respond -> do
16971692 t <- now
16981693 let mkErr = \_ -> {status: "", authorized: False {}}
@@ -1717,7 +1712,7 @@ handleMessageStatus = \to messageId authorization clientIp respond -> do
17171712-- POST /messages/ack: explicit ack by message id (alternative to the implicit
17181713-- cursor-based ack in /poll). Drops mailbox+forward rows and forks federation
17191714-- ack flush for any messages that originated remotely.
1720−handleAckMessages : [MessageIdHex] -> Text -> Text -> ({ackedCount: Int, authorized: Bool, error: Text, ok: Bool} -> a) -> IO {reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart, clock} a
1715+handleAckMessages : [MessageIdHex] -> Text -> Text -> ({ackedCount: Int, authorized: Bool, error: Text, ok: Bool} -> a) -> IO {r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, r *sessions, clock} a
17211716handleAckMessages = \messageIds authorization clientIp respond -> do
17221717 t <- now
17231718 let mkErr = \e -> {ok: False {}, authorized: False {}, ackedCount: 0, error: e}
@@ -1736,7 +1731,7 @@ handleAckMessages = \messageIds authorization clientIp respond -> do
17361731
17371732-- POST /messages/list: paginated peek at the caller's mailbox without acking.
17381733-- Returns metadata only (no ciphertext) plus the total count.
1739−handleListMessages : Int -> Text -> Text -> ({authorized: Bool, count: Int, error: Text, messages: [{messageId: MessageIdHex, receivedAt: Int<Ms>, timestamp: Int<Ms>}]} -> a) -> IO {reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart, clock} a
1734+handleListMessages : Int -> Text -> Text -> ({authorized: Bool, count: Int, error: Text, messages: [{messageId: MessageIdHex, receivedAt: Int<Ms>, timestamp: Int<Ms>}]} -> a) -> IO {r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, r *sessions, clock} a
17401735handleListMessages = \offset authorization clientIp respond -> do
17411736 t <- now
17421737 let mkErr = \e -> {
@@ -1766,7 +1761,7 @@ handleListMessages = \offset authorization clientIp respond -> do
17661761
17671762-- POST /messages/delete: same effect as ack from the recipient's side, with
17681763-- a "delete" semantic for the response.
1769−handleDeleteMessages : [MessageIdHex] -> Text -> Text -> ({authorized: Bool, deletedCount: Int, error: Text, ok: Bool} -> a) -> IO {reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart, clock} a
1764+handleDeleteMessages : [MessageIdHex] -> Text -> Text -> ({authorized: Bool, deletedCount: Int, error: Text, ok: Bool} -> a) -> IO {r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, r *sessions, clock} a
17701765handleDeleteMessages = \messageIds authorization clientIp respond -> do
17711766 t <- now
17721767 let mkErr = \e -> {
@@ -1793,7 +1788,7 @@ handleDeleteMessages = \messageIds authorization clientIp respond -> do
17931788-- POST /messages/batch: multi-recipient send in one call. Each entry runs
17941789-- through `sendMessageCore` separately so per-entry rate limits/errors don't
17951790-- abort the rest; federated forwards are forked per entry.
1796−handleSendBatch : [{encryptedBlob: BlobHex, expiresInMs: Int, messageId: OptionalMessageId, timestamp: Int<Ms>, to: PubkeyHex | a}] -> Text -> Text -> ({authorized: Bool, results: [BatchResult], retryAfterMs: Int} -> b) -> IO {reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart, clock} b
1791+handleSendBatch : [{encryptedBlob: BlobHex, expiresInMs: Int, messageId: OptionalMessageId, timestamp: Int<Ms>, to: PubkeyHex | a}] -> Text -> Text -> ({authorized: Bool, results: [BatchResult], retryAfterMs: Int} -> b) -> IO {r *drainPhase, rw *globalRateCount, rw *globalRateWindowStart, r *sessions, clock} b
17971792handleSendBatch = \messages authorization clientIp respond -> do
17981793 t <- now
17991794 let mkErr = \_ -> {results: [], authorized: False {}, retryAfterMs: 0}
@@ -1822,7 +1817,7 @@ handleSendBatch = \messages authorization clientIp respond -> do
18221817-- GET /federation/pubkey: publish the Ed25519 public key peers should use to
18231818-- verify our `Signed ...` federation auth headers. Returns ok=False when the
18241819-- keypair has not yet been generated (should not happen post-startup).
1825−handleFederationPubkey : ({pubkey: Text, ok: Bool} -> ret) -> IO {reads *federationPublicKeyHex} ret
1820+handleFederationPubkey : ({pubkey: Text, ok: Bool} -> ret) -> IO {r *federationPublicKeyHex} ret
18261821handleFederationPubkey = \respond -> do
18271822 hex <- *federationPublicKeyHex
18281823 yield (respond {pubkey: hex, ok: hex != ""})
@@ -1832,7 +1827,7 @@ handleFederationPubkey = \respond -> do
18321827-- skew tolerance, applies last-write-wins merge per (pubkey, server), and
18331828-- — for keys that became newly online — flushes any locally-buffered messages
18341829-- for them via `forwardQueuedToServer` (the catch-up path).
1835−handleRecvGossip : [{eventType: <GossipOffline {} | GossipOnline {} | a>, pubkey: PubkeyHex, server: Text, timestamp: Int<Ms>, ttl: Int<Ms> | b}] -> Int -> Text -> Int -> Text -> ({error: Text, ok: Bool, retryAfterMs: Int} -> c) -> IO {reads *allowedPeers, reads *blockedPeers, reads *federationRates, writes *federationRates, clock, reads *presence, writes *presence, network} c
1830+handleRecvGossip : [{eventType: <GossipOffline {} | GossipOnline {} | a>, pubkey: PubkeyHex, server: Text, timestamp: Int<Ms>, ttl: Int<Ms> | b}] -> Int -> Text -> Int -> Text -> ({error: Text, ok: Bool, retryAfterMs: Int} -> c) -> IO {r *allowedPeers, r *blockedPeers, rw *federationRates, rw *presence, clock, network} c
18361831handleRecvGossip = \events hopsRemaining fromServer requestTimestamp authorization respond -> do
18371832 t <- now
18381833 withFederationAuth authorization t respond fromServer "gossip" requestTimestamp (clamp 0 maxGossipHops hopsRemaining >= 1 && isRemote fromServer && count events <= maxGossipEvents) (do
@@ -1868,7 +1863,7 @@ handleRecvGossip = \events hopsRemaining fromServer requestTimestamp authorizati
18681863-- Store a federated/forwarded message into the local mailbox. Caller wraps in `atomic`.
18691864-- Returns "" on success (including duplicates), otherwise an error code:
18701865-- "no_presence" | "global_full" | "mailbox_full" | "rate_limited" (only when checkRecvRate).
1871−storeFederated : PubkeyHex -> BlobHex -> Int<Ms> -> Text -> MessageIdHex -> Int<Ms> -> Bool -> Int<Ms> -> IO {reads *ackedIds, reads *deliveredIds, reads *messages, reads *pollWakeup, reads *presence, reads *recvRates, reads *seqCounter, writes *messages, writes *pollWakeup, writes *recvRates, writes *seqCounter} Text
1866+storeFederated : PubkeyHex -> BlobHex -> Int<Ms> -> Text -> MessageIdHex -> Int<Ms> -> Bool -> Int<Ms> -> IO {r *ackedIds, r *deliveredIds, rw *messages, rw *pollWakeup, r *presence, rw *recvRates, rw *seqCounter} Text
18721867storeFederated = \toKey encryptedBlob timestamp originServer messageId expiresAt checkRecvRate t -> do
18731868 pres <- *presence
18741869 msgs <- *messages
@@ -1917,7 +1912,7 @@ storeFederated = \toKey encryptedBlob timestamp originServer messageId expiresAt
19171912-- POST /federation/forward: receive a single federated message destined for
19181913-- a local key. Delegates the actual store decision to `storeFederated`,
19191914-- which returns "" on success (including the duplicate case).
1920−handleForwardMessage : PubkeyHex -> BlobHex -> Int<Ms> -> Text -> Text -> Text -> Int -> Int<Ms> -> Text -> ({error: Text, ok: Bool, retryAfterMs: Int} -> a) -> IO {reads *allowedPeers, reads *blockedPeers, reads *federationRates, writes *federationRates, clock, reads *ackedIds, reads *deliveredIds, reads *messages, reads *pollWakeup, reads *presence, reads *recvRates, reads *seqCounter, writes *messages, writes *pollWakeup, writes *recvRates, writes *seqCounter, network} a
1915+handleForwardMessage : PubkeyHex -> BlobHex -> Int<Ms> -> Text -> Text -> Text -> Int -> Int<Ms> -> Text -> ({error: Text, ok: Bool, retryAfterMs: Int} -> a) -> IO {r *allowedPeers, r *blockedPeers, rw *federationRates, r *ackedIds, r *deliveredIds, rw *messages, rw *pollWakeup, r *presence, rw *recvRates, rw *seqCounter, clock, network} a
19211916handleForwardMessage = \toKey encryptedBlob timestamp originServer messageId fromServer requestTimestamp expiresAt authorization respond -> do
19221917 t <- now
19231918 withFederationAuth authorization t respond fromServer ("forward:" ++ messageId) requestTimestamp (isRemote originServer && isRemote fromServer) (do
@@ -1931,7 +1926,7 @@ handleForwardMessage = \toKey encryptedBlob timestamp originServer messageId fro
19311926-- We drop the matching `forwards` rows; if the recipient is no longer locally
19321927-- live and no other forward is still pending for the same id, also reap the
19331928-- backing `messages` row (after `ackGracePeriod` to dampen race with /poll).
1934−handleFederationAck : [MessageIdHex] -> Text -> Text -> Int -> Text -> ({ackedCount: Int, error: Text, ok: Bool, results: [{found: Bool, messageId: MessageIdHex}], retryAfterMs: Int} -> a) -> IO {reads *allowedPeers, reads *blockedPeers, reads *federationRates, writes *federationRates, clock, reads *forwards, reads *messages, reads *presence, writes *forwards, writes *messages, network} a
1929+handleFederationAck : [MessageIdHex] -> Text -> Text -> Int -> Text -> ({ackedCount: Int, error: Text, ok: Bool, results: [{found: Bool, messageId: MessageIdHex}], retryAfterMs: Int} -> a) -> IO {r *allowedPeers, r *blockedPeers, rw *federationRates, rw *forwards, rw *messages, r *presence, clock, network} a
19351930handleFederationAck = \messageIds originServer fromServer requestTimestamp authorization respond -> do
19361931 t <- now
19371932 let ackRespond = \r -> respond {r.ok, r.error, ackedCount: 0, results: [], r.retryAfterMs}
@@ -1960,7 +1955,7 @@ handleFederationAck = \messageIds originServer fromServer requestTimestamp autho
19601955-- POST /federation/forward-batch: bulk variant of `handleForwardMessage`.
19611956-- Skips per-entry recv-rate charging (the per-server federation rate limit
19621957-- already gates the call as a whole).
1963−handleFederationBatchForward : [{encryptedBlob: BlobHex, expiresAt: Int<Ms>, messageId: MessageIdHex, originServer: Text, timestamp: Int<Ms>, toKey: PubkeyHex | a}] -> Text -> Int -> Text -> ({error: Text, ok: Bool, results: [{messageId: MessageIdHex, ok: Bool}], retryAfterMs: Int} -> b) -> IO {reads *allowedPeers, reads *blockedPeers, reads *federationRates, writes *federationRates, clock, network} b
1958+handleFederationBatchForward : [{encryptedBlob: BlobHex, expiresAt: Int<Ms>, messageId: MessageIdHex, originServer: Text, timestamp: Int<Ms>, toKey: PubkeyHex | a}] -> Text -> Int -> Text -> ({error: Text, ok: Bool, results: [{messageId: MessageIdHex, ok: Bool}], retryAfterMs: Int} -> b) -> IO {r *allowedPeers, r *blockedPeers, rw *federationRates, clock, network} b
19641959handleFederationBatchForward = \forwards fromServer requestTimestamp authorization respond -> do
19651960 t <- now
19661961 let batchRespond = \r -> respond {r.ok, r.error, results: [], r.retryAfterMs}
@@ -1977,7 +1972,7 @@ handleFederationBatchForward = \forwards fromServer requestTimestamp authorizati
19771972-- POST /federation/delivery-receipt: peer confirms delivery of messages whose
19781973-- sender is local to us; we record per-sender delivery rows so that sender's
19791974-- next /poll can include the deliveredAt timestamp.
1980−handleFederationDeliveryReceipt : [MessageIdHex] -> [PubkeyHex] -> Text -> Int -> Text -> ({error: Text, ok: Bool, receivedCount: Int, retryAfterMs: Int} -> a) -> IO {reads *allowedPeers, reads *blockedPeers, reads *federationRates, writes *federationRates, clock, reads *messages, reads *senderDeliveries, writes *senderDeliveries, network} a
1975+handleFederationDeliveryReceipt : [MessageIdHex] -> [PubkeyHex] -> Text -> Int -> Text -> ({error: Text, ok: Bool, receivedCount: Int, retryAfterMs: Int} -> a) -> IO {r *allowedPeers, r *blockedPeers, rw *federationRates, r *messages, rw *senderDeliveries, clock, network} a
19811976handleFederationDeliveryReceipt = \messageIds toKeys fromServer requestTimestamp authorization respond -> do
19821977 t <- now
19831978 let receiptRespond = \r -> respond {r.ok, r.error, receivedCount: 0, r.retryAfterMs}
@@ -2018,7 +2013,7 @@ handleFederationDeliveryReceipt = \messageIds toKeys fromServer requestTimestamp
20182013-- POST /maintenance/drain: phase 1 stops accepting new auth (challenges,
20192014-- verify); phase 2 additionally clears local presence and broadcasts offline
20202015-- gossip for every locally-live key so peers can reroute. Idempotent.
2021−handleDrain : Int -> Text -> ({drained: Int, ok: Bool, phase: Int} -> a) -> IO {reads *drainCompletedAt, reads *drainPhase, reads *pollWakeup, reads *presence, reads *runtimeMaintenanceSecret, writes *drainCompletedAt, writes *drainPhase, writes *pollWakeup, writes *presence, clock} a
2016+handleDrain : Int -> Text -> ({drained: Int, ok: Bool, phase: Int} -> a) -> IO {rw *drainCompletedAt, rw *drainPhase, rw *pollWakeup, rw *presence, r *runtimeMaintenanceSecret, clock} a
20222017handleDrain = \phase authorization respond -> withMaintAuth authorization (yield (respond {
20232018 ok: False {},
20242019 drained: 0,
@@ -2042,14 +2037,14 @@ handleDrain = \phase authorization respond -> withMaintAuth authorization (yield
20422037
20432038-- POST /maintenance/undrain: clear drain state and resume normal operation.
20442039-- Does not restore presence — clients must re-authenticate to repopulate.
2045−handleUndrain : Text -> ({ok: Bool} -> a) -> IO {reads *drainCompletedAt, reads *drainPhase, reads *runtimeMaintenanceSecret, writes *drainCompletedAt, writes *drainPhase} a
2040+handleUndrain : Text -> ({ok: Bool} -> a) -> IO {rw *drainCompletedAt, rw *drainPhase, r *runtimeMaintenanceSecret} a
20462041handleUndrain = \authorization respond -> withMaintAuth authorization (yield (respond {ok: False {}})) (do
20472042 atomic (do *drainPhase = 0; *drainCompletedAt = 0)
20482043 yield (respond {ok: True {}}))
20492044
20502045-- POST /maintenance/shutdown: convenience wrapper that performs a phase-2
20512046-- drain and reports the count of presence rows cleared.
2052−handleShutdown : Text -> ({drained: Int, ok: Bool} -> a) -> IO {reads *drainCompletedAt, reads *drainPhase, reads *pollWakeup, reads *presence, reads *runtimeMaintenanceSecret, writes *drainCompletedAt, writes *drainPhase, writes *pollWakeup, writes *presence, clock} a
2047+handleShutdown : Text -> ({drained: Int, ok: Bool} -> a) -> IO {rw *drainCompletedAt, rw *drainPhase, rw *pollWakeup, rw *presence, r *runtimeMaintenanceSecret, clock} a
20532048handleShutdown = \authorization respond -> handleDrain 2 authorization (\r -> respond {
20542049 r.ok,
20552050 r.drained
@@ -2058,7 +2053,7 @@ handleShutdown = \authorization respond -> handleDrain 2 authorization (\r -> re
20582053-- POST /maintenance/prune: trigger an out-of-band `pruneAndRetry` tick.
20592054-- Useful when the operator wants to force expiry/forward retries without
20602055-- waiting for the next `backgroundPrune` interval.
2061−handlePrune : Text -> ({ok: Bool} -> a) -> IO {reads *runtimeMaintenanceSecret, clock, reads *ackedIds, reads *activePolls, reads *blockedPeers, reads *challenges, reads *deliveredIds, reads *federationPrivateKeyHex, reads *forwardFailures, reads *forwards, reads *lastPresenceRefresh, reads *lastPrunedAt, reads *messages, reads *presence, reads *senderDeliveries, reads *senderExpirations, reads *sessions, writes *ackedIds, writes *activePolls, writes *challenges, writes *deliveredIds, writes *forwardFailures, writes *forwards, writes *lastPresenceRefresh, writes *lastPrunedAt, writes *messages, writes *presence, writes *senderDeliveries, writes *senderExpirations, writes *sessions} a
2056+handlePrune : Text -> ({ok: Bool} -> a) -> IO {r *runtimeMaintenanceSecret, rw *ackedIds, rw *activePolls, r *blockedPeers, rw *challenges, rw *deliveredIds, rw *forwardFailures, rw *forwards, rw *lastPresenceRefresh, rw *lastPrunedAt, rw *messages, rw *presence, rw *senderDeliveries, rw *senderExpirations, rw *sessions, clock} a
20622057handlePrune = \authorization respond -> do
20632058 t <- now
20642059 authorized <- verifyMaintenanceAuth authorization
@@ -2067,7 +2062,7 @@ handlePrune = \authorization respond -> do
20672062
20682063-- POST /maintenance/reconcile: stub. Production builds run a state
20692064-- reconciliation pass here; this simplified server reports zero drift.
2070−handleReconcile : Text -> ({messageDrift: Int, ok: Bool, presenceDrift: Int, sessionDrift: Int} -> a) -> IO {reads *runtimeMaintenanceSecret} a
2065+handleReconcile : Text -> ({messageDrift: Int, ok: Bool, presenceDrift: Int, sessionDrift: Int} -> a) -> IO {r *runtimeMaintenanceSecret} a
20712066handleReconcile = \authorization respond -> do
20722067 authorized <- verifyMaintenanceAuth authorization
20732068 yield (respond {ok: authorized, messageDrift: 0, presenceDrift: 0, sessionDrift: 0})
@@ -2075,7 +2070,7 @@ handleReconcile = \authorization respond -> do
20752070-- GET /health: aggregate readiness flags. During an initial warmup window
20762071-- (3 prune intervals after startup) prune/heartbeat checks always pass to
20772072-- avoid false negatives before the background loops have ticked.
2078−handleHealth : a -> ({capacityHealthy: Bool, capacityWarning: Bool, degradedPeers: Int, draining: Bool, federationHealthy: Bool, forwardQueueHealthy: Bool, heartbeatHealthy: Bool, nonceHealthy: Bool, ok: Bool, pruneHealthy: Bool, requestPressureHealthy: Bool, version: Text, watchdogHealthy: Bool} -> b) -> IO {reads *drainPhase, reads *lastHeartbeatAt, reads *lastPrunedAt, reads *startedAt, clock} b
2073+handleHealth : a -> ({capacityHealthy: Bool, capacityWarning: Bool, degradedPeers: Int, draining: Bool, federationHealthy: Bool, forwardQueueHealthy: Bool, heartbeatHealthy: Bool, nonceHealthy: Bool, ok: Bool, pruneHealthy: Bool, requestPressureHealthy: Bool, version: Text, watchdogHealthy: Bool} -> b) -> IO {r *drainPhase, r *lastHeartbeatAt, r *lastPrunedAt, r *startedAt, clock} b
20792074handleHealth = \authorization respond -> do
20802075 t <- now
20812076 lastPrune <- *lastPrunedAt
@@ -2103,7 +2098,7 @@ handleHealth = \authorization respond -> do
21032098
21042099-- GET /ready: signals readiness once the first background prune has run
21052100-- (load-balancer / orchestrator probe).
2106−handleReady : ({ready: Bool} -> a) -> IO {reads *lastPrunedAt, reads *startedAt} a
2101+handleReady : ({ready: Bool} -> a) -> IO {r *lastPrunedAt, r *startedAt} a
21072102handleReady = \respond -> do
21082103 sa <- *startedAt
21092104 lastPrune <- *lastPrunedAt
@@ -2111,7 +2106,7 @@ handleReady = \respond -> do
21112106
21122107-- POST /maintenance/block: deny-list a peer. Outbound retries skip blocked
21132108-- peers; inbound federation calls from blocked peers fail in `withFederationAuth`.
2114−handleBlockPeer : Text -> Text -> ({blocked: Int, ok: Bool} -> a) -> IO {reads *blockedPeers, reads *runtimeMaintenanceSecret, writes *blockedPeers} a
2109+handleBlockPeer : Text -> Text -> ({blocked: Int, ok: Bool} -> a) -> IO {rw *blockedPeers, r *runtimeMaintenanceSecret} a
21152110handleBlockPeer = \server authorization respond -> withMaintAuth authorization (yield (respond {
21162111 ok: False {},
21172112 blocked: 0
@@ -2124,7 +2119,7 @@ handleBlockPeer = \server authorization respond -> withMaintAuth authorization (
21242119 yield (respond {ok: True {}, blocked: n}))
21252120
21262121-- POST /maintenance/unblock: remove a peer from the deny-list.
2127−handleUnblockPeer : Text -> Text -> ({ok: Bool, unblocked: Bool} -> a) -> IO {reads *blockedPeers, reads *runtimeMaintenanceSecret, writes *blockedPeers} a
2122+handleUnblockPeer : Text -> Text -> ({ok: Bool, unblocked: Bool} -> a) -> IO {rw *blockedPeers, r *runtimeMaintenanceSecret} a
21282123handleUnblockPeer = \server authorization respond -> withMaintAuth authorization (yield (respond {
21292124 ok: False {},
21302125 unblocked: False {}
@@ -2135,7 +2130,7 @@ handleUnblockPeer = \server authorization respond -> withMaintAuth authorization
21352130 yield (elem current server))
21362131 yield (respond {ok: True {}, unblocked: removed}))
21372132
2138−handleListBlocked : Text -> ({ok: Bool, peers: [Text]} -> a) -> IO {reads *blockedPeers, reads *runtimeMaintenanceSecret} a
2133+handleListBlocked : Text -> ({ok: Bool, peers: [Text]} -> a) -> IO {r *blockedPeers, r *runtimeMaintenanceSecret} a
21392134handleListBlocked = \authorization respond -> do
21402135 ok <- verifyMaintenanceAuth authorization
21412136 list <- *blockedPeers
@@ -2144,7 +2139,7 @@ handleListBlocked = \authorization respond -> do
21442139-- POST /maintenance/allow: when the allowed-peers list is non-empty, only
21452140-- federation calls from listed peers are accepted (whitelist mode). An empty
21462141-- list disables the whitelist.
2147−handleAllowPeer : Text -> Text -> ({allowed: Int, ok: Bool} -> a) -> IO {reads *allowedPeers, reads *runtimeMaintenanceSecret, writes *allowedPeers} a
2142+handleAllowPeer : Text -> Text -> ({allowed: Int, ok: Bool} -> a) -> IO {rw *allowedPeers, r *runtimeMaintenanceSecret} a
21482143handleAllowPeer = \server authorization respond -> withMaintAuth authorization (yield (respond {
21492144 ok: False {},
21502145 allowed: 0
@@ -2158,7 +2153,7 @@ handleAllowPeer = \server authorization respond -> withMaintAuth authorization (
21582153
21592154-- POST /maintenance/unallow: remove an entry from the allow-list. If the
21602155-- list becomes empty, the whitelist gate disengages.
2161−handleUnallowPeer : Text -> Text -> ({ok: Bool, removed: Bool} -> a) -> IO {reads *allowedPeers, reads *runtimeMaintenanceSecret, writes *allowedPeers} a
2156+handleUnallowPeer : Text -> Text -> ({ok: Bool, removed: Bool} -> a) -> IO {rw *allowedPeers, r *runtimeMaintenanceSecret} a
21622157handleUnallowPeer = \server authorization respond -> withMaintAuth authorization (yield (respond {
21632158 ok: False {},
21642159 removed: False {}
@@ -2169,7 +2164,7 @@ handleUnallowPeer = \server authorization respond -> withMaintAuth authorization
21692164 yield (elem current server))
21702165 yield (respond {ok: True {}, removed}))
21712166
2172−handleListAllowed : Text -> ({ok: Bool, peers: [Text]} -> a) -> IO {reads *allowedPeers, reads *runtimeMaintenanceSecret} a
2167+handleListAllowed : Text -> ({ok: Bool, peers: [Text]} -> a) -> IO {r *allowedPeers, r *runtimeMaintenanceSecret} a
21732168handleListAllowed = \authorization respond -> do
21742169 ok <- verifyMaintenanceAuth authorization
21752170 list <- *allowedPeers
@@ -2178,7 +2173,7 @@ handleListAllowed = \authorization respond -> do
21782173-- POST /maintenance/ping: send a zero-event gossip to a peer and return the
21792174-- round-trip latency. Useful for diagnosing federation reachability without
21802175-- waiting for organic traffic.
2181−handlePingPeer : Text -> Text -> ({error: Text, latencyMs: Int, ok: Bool} -> a) -> IO {reads *federationPrivateKeyHex, reads *runtimeMaintenanceSecret, network, clock, random} a
2176+handlePingPeer : Text -> Text -> ({error: Text, latencyMs: Int, ok: Bool} -> a) -> IO {r *federationPrivateKeyHex, r *runtimeMaintenanceSecret, network, clock, random} a
21822177handlePingPeer = \server authorization respond -> withMaintAuth authorization (yield (respond {
21832178 ok: False {},
21842179 latencyMs: 0,
@@ -2202,7 +2197,7 @@ handlePingPeer = \server authorization respond -> withMaintAuth authorization (y
22022197
22032198-- GET /maintenance/sessions/{pubkey}: list a single user's active sessions.
22042199-- Token values are masked ("***") — operators see expiry, ip, createdAt only.
2205−handleAdminListSessions : PubkeyHex -> Text -> ({count: Int, ok: Bool, sessions: [{createdAt: Int<Ms>, expiresAt: Int<Ms>, ip: Text, token: Text}]} -> a) -> IO {reads *runtimeMaintenanceSecret, reads *sessions} a
2200+handleAdminListSessions : PubkeyHex -> Text -> ({count: Int, ok: Bool, sessions: [{createdAt: Int<Ms>, expiresAt: Int<Ms>, ip: Text, token: Text}]} -> a) -> IO {r *runtimeMaintenanceSecret, r *sessions} a
22062201handleAdminListSessions = \pubkey authorization respond -> do
22072202 ok <- verifyMaintenanceAuth authorization
22082203 ses <- *sessions
@@ -2213,7 +2208,7 @@ handleAdminListSessions = \pubkey authorization respond -> do
22132208
22142209-- POST /maintenance/sessions/revoke-all: drop every session and local presence
22152210-- row for `pubkey`. Operator-side kick for a compromised key.
2216−handleAdminRevokeSessions : PubkeyHex -> Text -> ({ok: Bool, revokedCount: Int} -> a) -> IO {reads *presence, reads *runtimeMaintenanceSecret, reads *sessions, writes *presence, writes *sessions} a
2211+handleAdminRevokeSessions : PubkeyHex -> Text -> ({ok: Bool, revokedCount: Int} -> a) -> IO {rw *presence, r *runtimeMaintenanceSecret, rw *sessions} a
22172212handleAdminRevokeSessions = \pubkey authorization respond -> withMaintAuth authorization (yield (respond {
22182213 ok: False {},
22192214 revokedCount: 0
@@ -2230,7 +2225,7 @@ handleAdminRevokeSessions = \pubkey authorization respond -> withMaintAuth autho
22302225-- POST /maintenance/messages/purge: drop all queued/forwarding messages
22312226-- destined for `pubkey`, recording federation acks for any that originated
22322227-- elsewhere so peers learn we have nothing for the key.
2233−handleAdminPurgeMessages : PubkeyHex -> Text -> ({ok: Bool, purgedCount: Int} -> a) -> IO {reads *ackedIds, reads *forwards, reads *messages, reads *runtimeMaintenanceSecret, writes *ackedIds, writes *forwards, writes *messages, clock} a
2228+handleAdminPurgeMessages : PubkeyHex -> Text -> ({ok: Bool, purgedCount: Int} -> a) -> IO {rw *ackedIds, rw *forwards, rw *messages, r *runtimeMaintenanceSecret, clock} a
22342229handleAdminPurgeMessages = \pubkey authorization respond -> withMaintAuth authorization (yield (respond {
22352230 ok: False {},
22362231 purgedCount: 0
@@ -2250,7 +2245,7 @@ handleAdminPurgeMessages = \pubkey authorization respond -> withMaintAuth author
22502245
22512246-- POST /maintenance/sessions/revoke-ip: kill every session bound to a given
22522247-- client IP, regardless of pubkey. Reports total revoked + distinct keys.
2253−handleAdminRevokeIp : Text -> Text -> ({affectedKeys: Int, ok: Bool, revokedCount: Int} -> a) -> IO {reads *runtimeMaintenanceSecret, reads *sessions, writes *sessions} a
2248+handleAdminRevokeIp : Text -> Text -> ({affectedKeys: Int, ok: Bool, revokedCount: Int} -> a) -> IO {r *runtimeMaintenanceSecret, rw *sessions} a
22542249handleAdminRevokeIp = \ip authorization respond -> withMaintAuth authorization (yield (respond {
22552250 ok: False {},
22562251 revokedCount: 0,
@@ -2267,7 +2262,7 @@ handleAdminRevokeIp = \ip authorization respond -> withMaintAuth authorization (
22672262
22682263-- POST /maintenance/sessions/list: paged session inventory aggregated by
22692264-- pubkey (count, distinct ips, latest expiry). Sorted by descending count.
2270−handleAdminListAllSessions : Int -> Text -> ({ok: Bool, sessions: [{count: Int, ips: [Text], latestExpiry: Int, pubkey: PubkeyHex}], total: Int} -> a) -> IO {reads *runtimeMaintenanceSecret, reads *sessions, clock} a
2265+handleAdminListAllSessions : Int -> Text -> ({ok: Bool, sessions: [{count: Int, ips: [Text], latestExpiry: Int, pubkey: PubkeyHex}], total: Int} -> a) -> IO {r *runtimeMaintenanceSecret, r *sessions, clock} a
22712266handleAdminListAllSessions = \limit authorization respond -> withMaintAuth authorization (yield (respond {
22722267 sessions: [],
22732268 ok: False {},
@@ -2283,7 +2278,7 @@ handleAdminListAllSessions = \limit authorization respond -> withMaintAuth autho
22832278
22842279-- GET /maintenance/rate-limits: expose the configured rate ceilings. Returns
22852280-- zeros for unauthenticated callers (response shape stays stable).
2286−handleGetRateLimits : Text -> ({authGlobal: Int, challengeRate: Int, clientWriteRate: Int, federationRate: Int, globalRequest: Int, ipRate: Int, lookupRate: Int, ok: Bool, pairRate: Int, recvRate: Int, sendRate: Int} -> a) -> IO {reads *runtimeMaintenanceSecret} a
2281+handleGetRateLimits : Text -> ({authGlobal: Int, challengeRate: Int, clientWriteRate: Int, federationRate: Int, globalRequest: Int, ipRate: Int, lookupRate: Int, ok: Bool, pairRate: Int, recvRate: Int, sendRate: Int} -> a) -> IO {r *runtimeMaintenanceSecret} a
22872282handleGetRateLimits = \authorization respond -> do
22882283 ok <- verifyMaintenanceAuth authorization
22892284 let v = \x -> if ok then x else 0
@@ -2303,14 +2298,14 @@ handleGetRateLimits = \authorization respond -> do
23032298
23042299-- POST /maintenance/rate-limits: stub. Rate ceilings are compile-time constants
23052300-- in this simplified server, so this just authorizes and returns ok=true.
2306−handleSetRateLimits : a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> Text -> ({ok: Bool} -> k) -> IO {reads *runtimeMaintenanceSecret} k
2301+handleSetRateLimits : a -> b -> c -> d -> e -> f -> g -> h -> i -> j -> Text -> ({ok: Bool} -> k) -> IO {r *runtimeMaintenanceSecret} k
23072302handleSetRateLimits = \globalRequest ipRate sendRate federationRate authGlobal recvRate challengeRate pairRate lookupRate clientWriteRate authorization respond -> do
23082303 authorized <- verifyMaintenanceAuth authorization
23092304 yield (respond {ok: authorized})
23102305
23112306-- POST /maintenance/nonces/flush: stub. Nonce caches were removed; the
23122307-- endpoint is preserved so older operator tooling keeps working.
2313−handleFlushNonces : Text -> ({flushedCount: Int, ok: Bool} -> a) -> IO {reads *runtimeMaintenanceSecret} a
2308+handleFlushNonces : Text -> ({flushedCount: Int, ok: Bool} -> a) -> IO {r *runtimeMaintenanceSecret} a
23142309handleFlushNonces = \authorization respond -> do
23152310 authorized <- verifyMaintenanceAuth authorization
23162311 yield (respond {ok: authorized, flushedCount: 0})
@@ -2318,7 +2313,7 @@ handleFlushNonces = \authorization respond -> do
23182313-- POST /maintenance/rotate-secret: replace the maintenance Bearer secret.
23192314-- The old secret authenticates the rotation; subsequent calls must use the
23202315-- new value.
2321−handleRotateSecret : Text -> Text -> ({error: Text, ok: Bool} -> a) -> IO {reads *runtimeMaintenanceSecret, writes *runtimeMaintenanceSecret} a
2316+handleRotateSecret : Text -> Text -> ({error: Text, ok: Bool} -> a) -> IO {rw *runtimeMaintenanceSecret} a
23222317handleRotateSecret = \newSecret authorization respond -> withMaintAuth authorization (yield (respond {
23232318 ok: False {},
23242319 error: "unauthorized"
@@ -2328,7 +2323,7 @@ handleRotateSecret = \newSecret authorization respond -> withMaintAuth authoriza
23282323
23292324-- GET /maintenance/config: expose a small set of operationally-relevant
23302325-- compile-time constants (mailbox cap, gossip hops, poll timeout, ack grace).
2331−handleGetConfig : Text -> ({ackGracePeriod: Int, maxGossipHops: Int, maxMessagesPerRecipient: Int, ok: Bool, pollTimeoutMs: Int} -> a) -> IO {reads *runtimeMaintenanceSecret} a
2326+handleGetConfig : Text -> ({ackGracePeriod: Int, maxGossipHops: Int, maxMessagesPerRecipient: Int, ok: Bool, pollTimeoutMs: Int} -> a) -> IO {r *runtimeMaintenanceSecret} a
23322327handleGetConfig = \authorization respond -> do
23332328 ok <- verifyMaintenanceAuth authorization
23342329 let v = \x -> if ok then x else 0
@@ -2343,7 +2338,7 @@ handleGetConfig = \authorization respond -> do
23432338-- GET /stats: capacity gauges + minimal federation/timing snapshot. Most of
23442339-- the historical metric fields are zero-filled (see `zeroStats`) — only the
23452340-- counts that come straight from the state refs are populated.
2346−handleStats : Text -> ({capacity: {ackOverflow: Int, ackOverflowMax: Int, ackedIds: Int, ackedIdsMax: Int, activePolls: Int, activePollsMax: Int, challenges: Int, challengesMax: Int, deliveredIds: Int, deliveredIdsMax: Int, forwardNonces: Int, forwardNoncesMax: Int, forwards: Int, forwardsMax: Int, forwardsPct: Int, messageBucketMax: Int, messageBucketMin: Int, messages: Int, messagesMax: Int, messagesPct: Int, nonceEvictions: Int, presence: Int, presenceMax: Int, presencePct: Int, recipientCounts: Int, sessions: Int, sessionsMax: Int, sessionsPct: Int, totalMessageCount: Int}, federation: {avgLatencyMs: Int, blockedPeers: Int, callCount: Int, drainCompletedAt: Timestamp, enabled: Bool, forwardFailures: Int, forwardServers: Int, gossipFail: Int, gossipSuccess: Int, hungSlots: Int, inFlight: Int, latencyBuckets: {gte2000: Int, lt100: Int, lt2000: Int, lt500: Int}, maxForwardQueueDepth: Int, maxLatencyMs: Int, oldestAckAge: Int, oldestForwardAge: Int, recentGossip: Int, slowVerifies: Int, topFailingPeers: [{failures: Int, server: Text}], verifyFailures: Int}, ok: Bool, rates: {challenge: Int, federation: Int, federationPair: Int, global: Int, gossipEvent: Int, ip: Int, lookup: Int, pair: Int, pendingAckCleanups: Int, recv: Int, rejectedAuth: Int, rejectedFederation: Int, rejectedGlobal: Int, rejectedIp: Int, rejectedPerKey: Int, send: Int, senderDeliveries: Int, senderExpirations: Int, uniqueKeyIp: Int, verifyIp: Int}, timing: {activeRequests: Int, authAvgMs: Int, authCount: Int, authMaxMs: Int, drainPhase: Int, draining: Bool, heartbeatCount: Int, lastHeartbeatAt: Timestamp, lastPruneAt: Timestamp, peakActiveRequests: Int, pollAvgMs: Int, pollCount: Int, pollLatencyBuckets: {gte1000: Int, lt1000: Int, lt200: Int, lt50: Int}, pollMaxMs: Int, pruneCount: Int, pruneRunning: Timestamp, sendAvgMs: Int, sendCount: Int, sendLatencyBuckets: {gte1000: Int, lt1000: Int, lt200: Int, lt50: Int}, sendMaxMs: Int, totalEntries: Int, uptime: Int}, version: Text} -> a) -> IO {reads *ackedIds, reads *activePolls, reads *blockedPeers, reads *challenges, reads *deliveredIds, reads *drainPhase, reads *forwards, reads *heartbeatCount, reads *lastHeartbeatAt, reads *lastPrunedAt, reads *messages, reads *presence, reads *pruneCount, reads *runtimeMaintenanceSecret, reads *sessions, reads *startedAt, clock} a
2341+handleStats : Text -> ({capacity: {ackOverflow: Int, ackOverflowMax: Int, ackedIds: Int, ackedIdsMax: Int, activePolls: Int, activePollsMax: Int, challenges: Int, challengesMax: Int, deliveredIds: Int, deliveredIdsMax: Int, forwardNonces: Int, forwardNoncesMax: Int, forwards: Int, forwardsMax: Int, forwardsPct: Int, messageBucketMax: Int, messageBucketMin: Int, messages: Int, messagesMax: Int, messagesPct: Int, nonceEvictions: Int, presence: Int, presenceMax: Int, presencePct: Int, recipientCounts: Int, sessions: Int, sessionsMax: Int, sessionsPct: Int, totalMessageCount: Int}, federation: {avgLatencyMs: Int, blockedPeers: Int, callCount: Int, drainCompletedAt: Timestamp, enabled: Bool, forwardFailures: Int, forwardServers: Int, gossipFail: Int, gossipSuccess: Int, hungSlots: Int, inFlight: Int, latencyBuckets: {gte2000: Int, lt100: Int, lt2000: Int, lt500: Int}, maxForwardQueueDepth: Int, maxLatencyMs: Int, oldestAckAge: Int, oldestForwardAge: Int, recentGossip: Int, slowVerifies: Int, topFailingPeers: [{failures: Int, server: Text}], verifyFailures: Int}, ok: Bool, rates: {challenge: Int, federation: Int, federationPair: Int, global: Int, gossipEvent: Int, ip: Int, lookup: Int, pair: Int, pendingAckCleanups: Int, recv: Int, rejectedAuth: Int, rejectedFederation: Int, rejectedGlobal: Int, rejectedIp: Int, rejectedPerKey: Int, send: Int, senderDeliveries: Int, senderExpirations: Int, uniqueKeyIp: Int, verifyIp: Int}, timing: {activeRequests: Int, authAvgMs: Int, authCount: Int, authMaxMs: Int, drainPhase: Int, draining: Bool, heartbeatCount: Int, lastHeartbeatAt: Timestamp, lastPruneAt: Timestamp, peakActiveRequests: Int, pollAvgMs: Int, pollCount: Int, pollLatencyBuckets: {gte1000: Int, lt1000: Int, lt200: Int, lt50: Int}, pollMaxMs: Int, pruneCount: Int, pruneRunning: Timestamp, sendAvgMs: Int, sendCount: Int, sendLatencyBuckets: {gte1000: Int, lt1000: Int, lt200: Int, lt50: Int}, sendMaxMs: Int, totalEntries: Int, uptime: Int}, version: Text} -> a) -> IO {r *ackedIds, r *activePolls, r *blockedPeers, r *challenges, r *deliveredIds, r *drainPhase, r *forwards, r *heartbeatCount, r *lastHeartbeatAt, r *lastPrunedAt, r *messages, r *presence, r *pruneCount, r *runtimeMaintenanceSecret, r *sessions, r *startedAt, clock} a
23472342handleStats = \authorization respond -> withMaintAuth authorization (yield (respond zeroStats)) (do
23482343 t <- now
23492344 sa <- *startedAt
@@ -2576,7 +2571,7 @@ zeroStats = {
25762571
25772572-- GET /stats/peers: per-peer failure summary derived from `forwardFailures`,
25782573-- including current backoff and pending forward queue depth.
2579−handleStatsPeers : Text -> ({ok: Bool, peers: [{avgLatencyMs: Int, backoffMs: Int<Ms>, failures: Int, firstFailedAt: Int<Ms>, gossipEventsPerMin: Int, lastFailedAt: Int<Ms>, latencySamples: Int, maxLatencyMs: Int, pendingForwards: Int, retriable: Bool, server: Text}]} -> a) -> IO {reads *forwardFailures, reads *forwards, reads *runtimeMaintenanceSecret, clock} a
2574+handleStatsPeers : Text -> ({ok: Bool, peers: [{avgLatencyMs: Int, backoffMs: Int<Ms>, failures: Int, firstFailedAt: Int<Ms>, gossipEventsPerMin: Int, lastFailedAt: Int<Ms>, latencySamples: Int, maxLatencyMs: Int, pendingForwards: Int, retriable: Bool, server: Text}]} -> a) -> IO {r *forwardFailures, r *forwards, r *runtimeMaintenanceSecret, clock} a
25802575handleStatsPeers = \authorization respond -> withMaintAuth authorization (yield (respond {
25812576 peers: [],
25822577 ok: False {}
@@ -2603,7 +2598,7 @@ handleStatsPeers = \authorization respond -> withMaintAuth authorization (yield
26032598
26042599-- GET /stats/key/{pubkey}: per-key footprint — message count, presence rows,
26052600-- session count, queued forwards (with details), and active long-poll count.
2606−handleStatsKey : PubkeyHex -> Text -> ({activePolls: Int, forwardDetails: [{createdAt: Int<Ms>, retries: Int, server: ServerName}], forwards: Int, messages: Int, ok: Bool, presence: [{expiresAt: Int<Ms>, server: AnyServerName}], sessions: Int} -> a) -> IO {reads *activePolls, reads *forwards, reads *messages, reads *presence, reads *runtimeMaintenanceSecret, reads *sessions, clock} a
2601+handleStatsKey : PubkeyHex -> Text -> ({activePolls: Int, forwardDetails: [{createdAt: Int<Ms>, retries: Int, server: ServerName}], forwards: Int, messages: Int, ok: Bool, presence: [{expiresAt: Int<Ms>, server: AnyServerName}], sessions: Int} -> a) -> IO {r *activePolls, r *forwards, r *messages, r *presence, r *runtimeMaintenanceSecret, r *sessions, clock} a
26072602handleStatsKey = \pubkey authorization respond -> withMaintAuth authorization (yield (respond {
26082603 messages: 0,
26092604 presence: [],
@@ -2635,7 +2630,7 @@ handleStatsKey = \pubkey authorization respond -> withMaintAuth authorization (y
26352630
26362631-- POST /stats/top-recipients: ranked list of pubkeys by mailbox depth.
26372632-- `limit` defaults to 20, capped at 100, used to spot hot-spots / abuse.
2638−handleTopRecipients : Int -> Text -> ({ok: Bool, recipients: [RecipientCount], total: Int} -> a) -> IO {reads *messages, reads *runtimeMaintenanceSecret} a
2633+handleTopRecipients : Int -> Text -> ({ok: Bool, recipients: [RecipientCount], total: Int} -> a) -> IO {r *messages, r *runtimeMaintenanceSecret} a
26392634handleTopRecipients = \limit authorization respond -> withMaintAuth authorization (yield (respond {
26402635 recipients: [],
26412636 ok: False {},
@@ -2652,7 +2647,7 @@ handleTopRecipients = \limit authorization respond -> withMaintAuth authorizatio
26522647
26532648-- GET /stats/forwards: per-destination forward queue summary — count, age of
26542649-- oldest pending entry, and total accumulated retries.
2655−handleStatsForwards : Text -> ({ok: Bool, servers: [{count: Int<u>, oldestAge: Int<Ms>, server: ServerName, totalRetries: Int}], total: Int} -> a) -> IO {reads *forwards, reads *runtimeMaintenanceSecret, clock} a
2650+handleStatsForwards : Text -> ({ok: Bool, servers: [{count: Int<u>, oldestAge: Int<Ms>, server: ServerName, totalRetries: Int}], total: Int} -> a) -> IO {r *forwards, r *runtimeMaintenanceSecret, clock} a
26562651handleStatsForwards = \authorization respond -> withMaintAuth authorization (yield (respond {
26572652 servers: [],
26582653 ok: False {},
@@ -2668,7 +2663,7 @@ handleStatsForwards = \authorization respond -> withMaintAuth authorization (yie
26682663
26692664-- GET /stats/rates: same shape as /maintenance/rate-limits with an extra
26702665-- scalePct field. Kept separate for backwards compatibility with older clients.
2671−handleStatsRates : Text -> ({authGlobal: Int, challengeRate: Int, clientWriteRate: Int, federationRate: Int, globalRequest: Int, ipRate: Int, lookupRate: Int, ok: Bool, pairRate: Int, recvRate: Int, scalePct: Int, sendRate: Int} -> a) -> IO {reads *runtimeMaintenanceSecret} a
2666+handleStatsRates : Text -> ({authGlobal: Int, challengeRate: Int, clientWriteRate: Int, federationRate: Int, globalRequest: Int, ipRate: Int, lookupRate: Int, ok: Bool, pairRate: Int, recvRate: Int, scalePct: Int, sendRate: Int} -> a) -> IO {r *runtimeMaintenanceSecret} a
26722667handleStatsRates = \authorization respond -> do
26732668 ok <- verifyMaintenanceAuth authorization
26742669 let v = \x -> if ok then x else 0
@@ -2690,7 +2685,7 @@ handleStatsRates = \authorization respond -> do
26902685-- GET /metrics: Prometheus-style text exposition of the most useful gauges.
26912686-- The `authorization` header is accepted but not validated here — operators
26922687-- relying on auth must front this endpoint with a reverse proxy that enforces it.
2693−handleMetrics : a -> ({metrics: Text, ok: Bool} -> b) -> IO {reads *activePolls, reads *drainPhase, reads *forwards, reads *messages, reads *presence, reads *sessions, reads *startedAt, clock} b
2688+handleMetrics : a -> ({metrics: Text, ok: Bool} -> b) -> IO {r *activePolls, r *drainPhase, r *forwards, r *messages, r *presence, r *sessions, r *startedAt, clock} b
26942689handleMetrics = \authorization respond -> do
26952690 t <- now
26962691 msgs <- *messages
@@ -2720,7 +2715,7 @@ handleMetrics = \authorization respond -> do
27202715-- to the corresponding `handleXxx` function. Adding a new route means adding
27212716-- a `route ... = NewReq` line, a `handleNewReq` definition, and a clause here.
27222717
2723−serve : <AckMessages {authorization: Text, clientIp: Text, messageIds: [MessageIdHex], respond: {ackedCount: Int, authorized: Bool, error: Text, ok: Bool} -> Response} | AdminListAllSessions {authorization: Text, limit: Int, respond: {ok: Bool, sessions: [{count: Int, ips: [Text], latestExpiry: Timestamp, pubkey: PubkeyHex}], total: Int} -> Response} | AdminListSessions {authorization: Text, pubkey: PubkeyHex, respond: {count: Int, ok: Bool, sessions: [{createdAt: Timestamp, expiresAt: Timestamp, ip: Text, token: Text}]} -> Response} | AdminPurgeMessages {authorization: Text, pubkey: PubkeyHex, respond: {ok: Bool, purgedCount: Int} -> Response} | AdminRevokeIp {authorization: Text, ip: Text, respond: {affectedKeys: Int, ok: Bool, revokedCount: Int} -> Response} | AdminRevokeSessions {authorization: Text, pubkey: PubkeyHex, respond: {ok: Bool, revokedCount: Int} -> Response} | AllowPeer {authorization: Text, respond: {allowed: Int, ok: Bool} -> Response, server: ServerName} | BatchLookupPresence {authorization: Text, clientIp: Text, pubkeys: [PubkeyHex], respond: {authorized: Bool, error: Text, results: [{pubkey: PubkeyHex, server: AnyServerName}]} -> Response} | BlockPeer {authorization: Text, respond: {blocked: Int, ok: Bool} -> Response, server: ServerName} | DeleteMessages {authorization: Text, clientIp: Text, messageIds: [MessageIdHex], respond: {authorized: Bool, deletedCount: Int, error: Text, ok: Bool} -> Response} | Drain {authorization: Text, phase: Int, respond: {drained: Int, ok: Bool, phase: Int} -> Response} | FederationAck {authorization: Text, correlationId: Text, fromServer: ServerName, messageIds: [MessageIdHex], originServer: ServerName, protocolVersion: Int, requestTimestamp: Timestamp, respond: {ackedCount: Int, error: Text, ok: Bool, results: [{found: Bool, messageId: MessageIdHex}], retryAfterMs: Int} -> Response} | FederationBatchForward {authorization: Text, correlationId: Text, forwards: [{encryptedBlob: BlobHex, expiresAt: Timestamp, messageId: MessageIdHex, originServer: ServerName, timestamp: Timestamp, toKey: PubkeyHex}], fromServer: ServerName, protocolVersion: Int, requestTimestamp: Timestamp, respond: {error: Text, ok: Bool, results: [{messageId: MessageIdHex, ok: Bool}], retryAfterMs: Int} -> Response} | FederationDeliveryReceipt {authorization: Text, correlationId: Text, fromServer: ServerName, messageIds: [MessageIdHex], protocolVersion: Int, requestTimestamp: Timestamp, respond: {error: Text, ok: Bool, receivedCount: Int, retryAfterMs: Int} -> Response, toKeys: [PubkeyHex]} | FederationPubkey {respond: {ok: Bool, pubkey: Text} -> Response} | FlushNonces {authorization: Text, respond: {flushedCount: Int, ok: Bool} -> Response} | ForwardMessage {authorization: Text, correlationId: Text, encryptedBlob: BlobHex, expiresAt: Timestamp, fromServer: ServerName, messageId: MessageIdHex, originServer: ServerName, protocolVersion: Int, requestTimestamp: Timestamp, respond: {error: Text, ok: Bool, retryAfterMs: Int} -> Response, timestamp: Timestamp, toKey: PubkeyHex} | GetConfig {authorization: Text, respond: {ackGracePeriod: Int, maxGossipHops: Int, maxMessagesPerRecipient: Int, ok: Bool, pollTimeoutMs: Int} -> Response} | GetRateLimits {authorization: Text, respond: {authGlobal: Int, challengeRate: Int, clientWriteRate: Int, federationRate: Int, globalRequest: Int, ipRate: Int, lookupRate: Int, ok: Bool, pairRate: Int, recvRate: Int, sendRate: Int} -> Response} | Health {authorization: Text, respond: {capacityHealthy: Bool, capacityWarning: Bool, degradedPeers: Int, draining: Bool, federationHealthy: Bool, forwardQueueHealthy: Bool, heartbeatHealthy: Bool, nonceHealthy: Bool, ok: Bool, pruneHealthy: Bool, requestPressureHealthy: Bool, version: Text, watchdogHealthy: Bool} -> Response} | ListAllowed {authorization: Text, respond: {ok: Bool, peers: [Text]} -> Response} | ListBlocked {authorization: Text, respond: {ok: Bool, peers: [Text]} -> Response} | ListMessages {authorization: Text, clientIp: Text, offset: Int, respond: {authorized: Bool, count: Int, error: Text, messages: [{messageId: MessageIdHex, receivedAt: Timestamp, timestamp: Timestamp}]} -> Response} | Logout {authorization: Text, clientIp: Text, respond: {authorized: Bool, ok: Bool} -> Response} | LookupPresence {authorization: Text, clientIp: Text, pubkey: PubkeyHex, respond: {authorized: Bool, error: Text, server: AnyServerName} -> Response} | MessageStatus {authorization: Text, clientIp: Text, messageId: MessageIdHex, respond: {authorized: Bool, status: Text} -> Response, to: PubkeyHex} | Metrics {authorization: Text, respond: {metrics: Text, ok: Bool} -> Response} | PingPeer {authorization: Text, respond: {error: Text, latencyMs: Int, ok: Bool} -> Response, server: ServerName} | Poll {authorization: Text, clientIp: Text, cursor: Timestamp, generation: Int, respond: {authorized: Bool, cursor: Timestamp, cursorReset: Bool, cursorStale: Bool, draining: Bool, droppedDeliveryReceipts: Int, droppedExpirationNotifs: Int, events: [PollEvent], generation: Int, hasMore: Bool, heartbeat: Bool, loadPct: Int, messageDeliveries: [DeliveredEntry], messageExpirations: [{expiredAt: Timestamp, messageId: MessageIdHex, toKey: PubkeyHex}], rateLimited: Bool, retryAfterMs: Int} -> Response} | Prune {authorization: Text, respond: {ok: Bool} -> Response} | Ready {respond: {ready: Bool} -> Response} | Reconcile {authorization: Text, respond: {messageDrift: Int, ok: Bool, presenceDrift: Int, sessionDrift: Int} -> Response} | RecvGossip {authorization: Text, correlationId: Text, events: [GossipEvent], fromServer: ServerName, hopsRemaining: Int, protocolVersion: Int, requestTimestamp: Timestamp, respond: {error: Text, ok: Bool, retryAfterMs: Int} -> Response} | ReqChallenge {clientIp: Text, pubkey: PubkeyHex, respond: {challenge: Text, error: Text, expiresAt: Timestamp, ok: Bool, retryAfterMs: Int} -> Response} | RevokeSession {authorization: Text, clientIp: Text, respond: {authorized: Bool, ok: Bool} -> Response, targetToken: Text} | RotateSecret {authorization: Text, newSecret: Text, respond: {error: Text, ok: Bool} -> Response} | SendBatch {authorization: Text, clientIp: Text, messages: [BatchMessage], respond: {authorized: Bool, results: [BatchResult], retryAfterMs: Int} -> Response} | SendMessage {authorization: Text, clientIp: Text, encryptedBlob: BlobHex, expiresInMs: Int, messageId: OptionalMessageId, respond: {error: Text, loadPct: Int, messageId: OptionalMessageId, retryAfterMs: Int, status: MessageStatus} -> Response, timestamp: Timestamp, to: PubkeyHex} | SetRateLimits {authGlobal: Int, authorization: Text, challengeRate: Int, clientWriteRate: Int, federationRate: Int, globalRequest: Int, ipRate: Int, lookupRate: Int, pairRate: Int, recvRate: Int, respond: {ok: Bool} -> Response, sendRate: Int} | Shutdown {authorization: Text, respond: {drained: Int, ok: Bool} -> Response} | Stats {authorization: Text, respond: {capacity: {ackOverflow: Int, ackOverflowMax: Int, ackedIds: Int, ackedIdsMax: Int, activePolls: Int, activePollsMax: Int, challenges: Int, challengesMax: Int, deliveredIds: Int, deliveredIdsMax: Int, forwardNonces: Int, forwardNoncesMax: Int, forwards: Int, forwardsMax: Int, forwardsPct: Int, messageBucketMax: Int, messageBucketMin: Int, messages: Int, messagesMax: Int, messagesPct: Int, nonceEvictions: Int, presence: Int, presenceMax: Int, presencePct: Int, recipientCounts: Int, sessions: Int, sessionsMax: Int, sessionsPct: Int, totalMessageCount: Int}, federation: {avgLatencyMs: Int, blockedPeers: Int, callCount: Int, drainCompletedAt: Timestamp, enabled: Bool, forwardFailures: Int, forwardServers: Int, gossipFail: Int, gossipSuccess: Int, hungSlots: Int, inFlight: Int, latencyBuckets: {gte2000: Int, lt100: Int, lt2000: Int, lt500: Int}, maxForwardQueueDepth: Int, maxLatencyMs: Int, oldestAckAge: Int, oldestForwardAge: Int, recentGossip: Int, slowVerifies: Int, topFailingPeers: [{failures: Int, server: Text}], verifyFailures: Int}, ok: Bool, rates: {challenge: Int, federation: Int, federationPair: Int, global: Int, gossipEvent: Int, ip: Int, lookup: Int, pair: Int, pendingAckCleanups: Int, recv: Int, rejectedAuth: Int, rejectedFederation: Int, rejectedGlobal: Int, rejectedIp: Int, rejectedPerKey: Int, send: Int, senderDeliveries: Int, senderExpirations: Int, uniqueKeyIp: Int, verifyIp: Int}, timing: {activeRequests: Int, authAvgMs: Int, authCount: Int, authMaxMs: Int, drainPhase: Int, draining: Bool, heartbeatCount: Int, lastHeartbeatAt: Timestamp, lastPruneAt: Timestamp, peakActiveRequests: Int, pollAvgMs: Int, pollCount: Int, pollLatencyBuckets: {gte1000: Int, lt1000: Int, lt200: Int, lt50: Int}, pollMaxMs: Int, pruneCount: Int, pruneRunning: Timestamp, sendAvgMs: Int, sendCount: Int, sendLatencyBuckets: {gte1000: Int, lt1000: Int, lt200: Int, lt50: Int}, sendMaxMs: Int, totalEntries: Int, uptime: Int}, version: Text} -> Response} | StatsForwards {authorization: Text, respond: {ok: Bool, servers: [{count: Int, oldestAge: Int, server: Text, totalRetries: Int}], total: Int} -> Response} | StatsKey {authorization: Text, pubkey: PubkeyHex, respond: {activePolls: Int, forwardDetails: [{createdAt: Timestamp, retries: Int, server: Text}], forwards: Int, messages: Int, ok: Bool, presence: [KeyPresenceEntry], sessions: Int} -> Response} | StatsPeers {authorization: Text, respond: {ok: Bool, peers: [PeerStatus]} -> Response} | StatsRates {authorization: Text, respond: {authGlobal: Int, challengeRate: Int, clientWriteRate: Int, federationRate: Int, globalRequest: Int, ipRate: Int, lookupRate: Int, ok: Bool, pairRate: Int, recvRate: Int, scalePct: Int, sendRate: Int} -> Response} | TopRecipients {authorization: Text, limit: Int, respond: {ok: Bool, recipients: [RecipientCount], total: Int} -> Response} | UnallowPeer {authorization: Text, respond: {ok: Bool, removed: Bool} -> Response, server: ServerName} | UnblockPeer {authorization: Text, respond: {ok: Bool, unblocked: Bool} -> Response, server: ServerName} | Undrain {authorization: Text, respond: {ok: Bool} -> Response} | VerifyAuth {challenge: Text, clientIp: Text, pubkey: PubkeyHex, respond: {error: Text, expiresAt: Timestamp, token: Text} -> Response, revokeOthers: Bool, signature: SignatureHex}> -> IO {reads *ackedIds, reads *activePolls, reads *allowedPeers, reads *authGlobalRateCount, reads *authGlobalRateWindowStart, reads *blockedPeers, reads *challengeRates, reads *challenges, reads *deliveredIds, reads *drainCompletedAt, reads *drainPhase, reads *federationPrivateKeyHex, reads *federationPublicKeyHex, reads *federationRates, reads *forwardFailures, reads *forwards, reads *globalRateCount, reads *globalRateWindowStart, reads *heartbeatCount, reads *ipRates, reads *lastHeartbeatAt, reads *lastPresenceRefresh, reads *lastPrunedAt, reads *messages, reads *pollWakeup, reads *presence, reads *pruneCount, reads *recvRates, reads *runtimeMaintenanceSecret, reads *senderDeliveries, reads *senderExpirations, reads *seqCounter, reads *serverGeneration, reads *sessions, reads *startedAt, writes *ackedIds, writes *activePolls, writes *allowedPeers, writes *authGlobalRateCount, writes *authGlobalRateWindowStart, writes *blockedPeers, writes *challengeRates, writes *challenges, writes *deliveredIds, writes *drainCompletedAt, writes *drainPhase, writes *federationRates, writes *forwardFailures, writes *forwards, writes *globalRateCount, writes *globalRateWindowStart, writes *ipRates, writes *lastPresenceRefresh, writes *lastPrunedAt, writes *messages, writes *pollWakeup, writes *presence, writes *recvRates, writes *runtimeMaintenanceSecret, writes *senderDeliveries, writes *senderExpirations, writes *seqCounter, writes *sessions, network, clock, random} Response
2718+serve : <AckMessages {authorization: Text, clientIp: Text, messageIds: [MessageIdHex], respond: {ackedCount: Int, authorized: Bool, error: Text, ok: Bool} -> Response} | AdminListAllSessions {authorization: Text, limit: Int, respond: {ok: Bool, sessions: [{count: Int, ips: [Text], latestExpiry: Timestamp, pubkey: PubkeyHex}], total: Int} -> Response} | AdminListSessions {authorization: Text, pubkey: PubkeyHex, respond: {count: Int, ok: Bool, sessions: [{createdAt: Timestamp, expiresAt: Timestamp, ip: Text, token: Text}]} -> Response} | AdminPurgeMessages {authorization: Text, pubkey: PubkeyHex, respond: {ok: Bool, purgedCount: Int} -> Response} | AdminRevokeIp {authorization: Text, ip: Text, respond: {affectedKeys: Int, ok: Bool, revokedCount: Int} -> Response} | AdminRevokeSessions {authorization: Text, pubkey: PubkeyHex, respond: {ok: Bool, revokedCount: Int} -> Response} | AllowPeer {authorization: Text, respond: {allowed: Int, ok: Bool} -> Response, server: ServerName} | BatchLookupPresence {authorization: Text, clientIp: Text, pubkeys: [PubkeyHex], respond: {authorized: Bool, error: Text, results: [{pubkey: PubkeyHex, server: AnyServerName}]} -> Response} | BlockPeer {authorization: Text, respond: {blocked: Int, ok: Bool} -> Response, server: ServerName} | DeleteMessages {authorization: Text, clientIp: Text, messageIds: [MessageIdHex], respond: {authorized: Bool, deletedCount: Int, error: Text, ok: Bool} -> Response} | Drain {authorization: Text, phase: Int, respond: {drained: Int, ok: Bool, phase: Int} -> Response} | FederationAck {authorization: Text, correlationId: Text, fromServer: ServerName, messageIds: [MessageIdHex], originServer: ServerName, protocolVersion: Int, requestTimestamp: Timestamp, respond: {ackedCount: Int, error: Text, ok: Bool, results: [{found: Bool, messageId: MessageIdHex}], retryAfterMs: Int} -> Response} | FederationBatchForward {authorization: Text, correlationId: Text, forwards: [{encryptedBlob: BlobHex, expiresAt: Timestamp, messageId: MessageIdHex, originServer: ServerName, timestamp: Timestamp, toKey: PubkeyHex}], fromServer: ServerName, protocolVersion: Int, requestTimestamp: Timestamp, respond: {error: Text, ok: Bool, results: [{messageId: MessageIdHex, ok: Bool}], retryAfterMs: Int} -> Response} | FederationDeliveryReceipt {authorization: Text, correlationId: Text, fromServer: ServerName, messageIds: [MessageIdHex], protocolVersion: Int, requestTimestamp: Timestamp, respond: {error: Text, ok: Bool, receivedCount: Int, retryAfterMs: Int} -> Response, toKeys: [PubkeyHex]} | FederationPubkey {respond: {ok: Bool, pubkey: Text} -> Response} | FlushNonces {authorization: Text, respond: {flushedCount: Int, ok: Bool} -> Response} | ForwardMessage {authorization: Text, correlationId: Text, encryptedBlob: BlobHex, expiresAt: Timestamp, fromServer: ServerName, messageId: MessageIdHex, originServer: ServerName, protocolVersion: Int, requestTimestamp: Timestamp, respond: {error: Text, ok: Bool, retryAfterMs: Int} -> Response, timestamp: Timestamp, toKey: PubkeyHex} | GetConfig {authorization: Text, respond: {ackGracePeriod: Int, maxGossipHops: Int, maxMessagesPerRecipient: Int, ok: Bool, pollTimeoutMs: Int} -> Response} | GetRateLimits {authorization: Text, respond: {authGlobal: Int, challengeRate: Int, clientWriteRate: Int, federationRate: Int, globalRequest: Int, ipRate: Int, lookupRate: Int, ok: Bool, pairRate: Int, recvRate: Int, sendRate: Int} -> Response} | Health {authorization: Text, respond: {capacityHealthy: Bool, capacityWarning: Bool, degradedPeers: Int, draining: Bool, federationHealthy: Bool, forwardQueueHealthy: Bool, heartbeatHealthy: Bool, nonceHealthy: Bool, ok: Bool, pruneHealthy: Bool, requestPressureHealthy: Bool, version: Text, watchdogHealthy: Bool} -> Response} | ListAllowed {authorization: Text, respond: {ok: Bool, peers: [Text]} -> Response} | ListBlocked {authorization: Text, respond: {ok: Bool, peers: [Text]} -> Response} | ListMessages {authorization: Text, clientIp: Text, offset: Int, respond: {authorized: Bool, count: Int, error: Text, messages: [{messageId: MessageIdHex, receivedAt: Timestamp, timestamp: Timestamp}]} -> Response} | Logout {authorization: Text, clientIp: Text, respond: {authorized: Bool, ok: Bool} -> Response} | LookupPresence {authorization: Text, clientIp: Text, pubkey: PubkeyHex, respond: {authorized: Bool, error: Text, server: AnyServerName} -> Response} | MessageStatus {authorization: Text, clientIp: Text, messageId: MessageIdHex, respond: {authorized: Bool, status: Text} -> Response, to: PubkeyHex} | Metrics {authorization: Text, respond: {metrics: Text, ok: Bool} -> Response} | PingPeer {authorization: Text, respond: {error: Text, latencyMs: Int, ok: Bool} -> Response, server: ServerName} | Poll {authorization: Text, clientIp: Text, cursor: Timestamp, generation: Int, respond: {authorized: Bool, cursor: Timestamp, cursorReset: Bool, cursorStale: Bool, draining: Bool, droppedDeliveryReceipts: Int, droppedExpirationNotifs: Int, events: [PollEvent], generation: Int, hasMore: Bool, heartbeat: Bool, loadPct: Int, messageDeliveries: [DeliveredEntry], messageExpirations: [{expiredAt: Timestamp, messageId: MessageIdHex, toKey: PubkeyHex}], rateLimited: Bool, retryAfterMs: Int} -> Response} | Prune {authorization: Text, respond: {ok: Bool} -> Response} | Ready {respond: {ready: Bool} -> Response} | Reconcile {authorization: Text, respond: {messageDrift: Int, ok: Bool, presenceDrift: Int, sessionDrift: Int} -> Response} | RecvGossip {authorization: Text, correlationId: Text, events: [GossipEvent], fromServer: ServerName, hopsRemaining: Int, protocolVersion: Int, requestTimestamp: Timestamp, respond: {error: Text, ok: Bool, retryAfterMs: Int} -> Response} | ReqChallenge {clientIp: Text, pubkey: PubkeyHex, respond: {challenge: Text, error: Text, expiresAt: Timestamp, ok: Bool, retryAfterMs: Int} -> Response} | RevokeSession {authorization: Text, clientIp: Text, respond: {authorized: Bool, ok: Bool} -> Response, targetToken: Text} | RotateSecret {authorization: Text, newSecret: Text, respond: {error: Text, ok: Bool} -> Response} | SendBatch {authorization: Text, clientIp: Text, messages: [BatchMessage], respond: {authorized: Bool, results: [BatchResult], retryAfterMs: Int} -> Response} | SendMessage {authorization: Text, clientIp: Text, encryptedBlob: BlobHex, expiresInMs: Int, messageId: OptionalMessageId, respond: {error: Text, loadPct: Int, messageId: OptionalMessageId, retryAfterMs: Int, status: MessageStatus} -> Response, timestamp: Timestamp, to: PubkeyHex} | SetRateLimits {authGlobal: Int, authorization: Text, challengeRate: Int, clientWriteRate: Int, federationRate: Int, globalRequest: Int, ipRate: Int, lookupRate: Int, pairRate: Int, recvRate: Int, respond: {ok: Bool} -> Response, sendRate: Int} | Shutdown {authorization: Text, respond: {drained: Int, ok: Bool} -> Response} | Stats {authorization: Text, respond: {capacity: {ackOverflow: Int, ackOverflowMax: Int, ackedIds: Int, ackedIdsMax: Int, activePolls: Int, activePollsMax: Int, challenges: Int, challengesMax: Int, deliveredIds: Int, deliveredIdsMax: Int, forwardNonces: Int, forwardNoncesMax: Int, forwards: Int, forwardsMax: Int, forwardsPct: Int, messageBucketMax: Int, messageBucketMin: Int, messages: Int, messagesMax: Int, messagesPct: Int, nonceEvictions: Int, presence: Int, presenceMax: Int, presencePct: Int, recipientCounts: Int, sessions: Int, sessionsMax: Int, sessionsPct: Int, totalMessageCount: Int}, federation: {avgLatencyMs: Int, blockedPeers: Int, callCount: Int, drainCompletedAt: Timestamp, enabled: Bool, forwardFailures: Int, forwardServers: Int, gossipFail: Int, gossipSuccess: Int, hungSlots: Int, inFlight: Int, latencyBuckets: {gte2000: Int, lt100: Int, lt2000: Int, lt500: Int}, maxForwardQueueDepth: Int, maxLatencyMs: Int, oldestAckAge: Int, oldestForwardAge: Int, recentGossip: Int, slowVerifies: Int, topFailingPeers: [{failures: Int, server: Text}], verifyFailures: Int}, ok: Bool, rates: {challenge: Int, federation: Int, federationPair: Int, global: Int, gossipEvent: Int, ip: Int, lookup: Int, pair: Int, pendingAckCleanups: Int, recv: Int, rejectedAuth: Int, rejectedFederation: Int, rejectedGlobal: Int, rejectedIp: Int, rejectedPerKey: Int, send: Int, senderDeliveries: Int, senderExpirations: Int, uniqueKeyIp: Int, verifyIp: Int}, timing: {activeRequests: Int, authAvgMs: Int, authCount: Int, authMaxMs: Int, drainPhase: Int, draining: Bool, heartbeatCount: Int, lastHeartbeatAt: Timestamp, lastPruneAt: Timestamp, peakActiveRequests: Int, pollAvgMs: Int, pollCount: Int, pollLatencyBuckets: {gte1000: Int, lt1000: Int, lt200: Int, lt50: Int}, pollMaxMs: Int, pruneCount: Int, pruneRunning: Timestamp, sendAvgMs: Int, sendCount: Int, sendLatencyBuckets: {gte1000: Int, lt1000: Int, lt200: Int, lt50: Int}, sendMaxMs: Int, totalEntries: Int, uptime: Int}, version: Text} -> Response} | StatsForwards {authorization: Text, respond: {ok: Bool, servers: [{count: Int, oldestAge: Int, server: Text, totalRetries: Int}], total: Int} -> Response} | StatsKey {authorization: Text, pubkey: PubkeyHex, respond: {activePolls: Int, forwardDetails: [{createdAt: Timestamp, retries: Int, server: Text}], forwards: Int, messages: Int, ok: Bool, presence: [KeyPresenceEntry], sessions: Int} -> Response} | StatsPeers {authorization: Text, respond: {ok: Bool, peers: [PeerStatus]} -> Response} | StatsRates {authorization: Text, respond: {authGlobal: Int, challengeRate: Int, clientWriteRate: Int, federationRate: Int, globalRequest: Int, ipRate: Int, lookupRate: Int, ok: Bool, pairRate: Int, recvRate: Int, scalePct: Int, sendRate: Int} -> Response} | TopRecipients {authorization: Text, limit: Int, respond: {ok: Bool, recipients: [RecipientCount], total: Int} -> Response} | UnallowPeer {authorization: Text, respond: {ok: Bool, removed: Bool} -> Response, server: ServerName} | UnblockPeer {authorization: Text, respond: {ok: Bool, unblocked: Bool} -> Response, server: ServerName} | Undrain {authorization: Text, respond: {ok: Bool} -> Response} | VerifyAuth {challenge: Text, clientIp: Text, pubkey: PubkeyHex, respond: {error: Text, expiresAt: Timestamp, token: Text} -> Response, revokeOthers: Bool, signature: SignatureHex}> -> IO {rw *ackedIds, rw *activePolls, rw *allowedPeers, rw *authGlobalRateCount, rw *authGlobalRateWindowStart, rw *blockedPeers, rw *challengeRates, rw *challenges, rw *deliveredIds, rw *drainCompletedAt, rw *drainPhase, r *federationPrivateKeyHex, r *federationPublicKeyHex, rw *federationRates, rw *forwardFailures, rw *forwards, rw *globalRateCount, rw *globalRateWindowStart, r *heartbeatCount, rw *ipRates, r *lastHeartbeatAt, rw *lastPresenceRefresh, rw *lastPrunedAt, rw *messages, rw *pollWakeup, rw *presence, r *pruneCount, rw *recvRates, rw *runtimeMaintenanceSecret, rw *senderDeliveries, rw *senderExpirations, rw *seqCounter, r *serverGeneration, rw *sessions, r *startedAt, network, clock, random} Response
27242719serve = \req -> case req of
27252720 ReqChallenge {pubkey, clientIp, respond} -> handleChallenge pubkey clientIp respond
27262721 VerifyAuth {pubkey, challenge, signature, revokeOthers, clientIp, respond} -> handleVerifyAuth pubkey challenge signature revokeOthers clientIp respond
@@ -2781,7 +2776,7 @@ serve = \req -> case req of
27812776-- `pollTimeoutMs` so long-poll holders unblock and return an empty heartbeat
27822777-- response, keeping connections fresh.
27832778
2784−backgroundPrune : IO {reads *drainPhase, clock, reads *ackedIds, reads *activePolls, reads *blockedPeers, reads *challenges, reads *deliveredIds, reads *federationPrivateKeyHex, reads *forwardFailures, reads *forwards, reads *lastPresenceRefresh, reads *lastPrunedAt, reads *messages, reads *presence, reads *pruneCount, reads *senderDeliveries, reads *senderExpirations, reads *sessions, writes *ackedIds, writes *activePolls, writes *challenges, writes *deliveredIds, writes *forwardFailures, writes *forwards, writes *lastPresenceRefresh, writes *lastPrunedAt, writes *messages, writes *presence, writes *pruneCount, writes *senderDeliveries, writes *senderExpirations, writes *sessions} a
2779+backgroundPrune : IO {r *drainPhase, rw *ackedIds, rw *activePolls, r *blockedPeers, rw *challenges, rw *deliveredIds, rw *forwardFailures, rw *forwards, rw *lastPresenceRefresh, rw *lastPrunedAt, rw *messages, rw *presence, rw *pruneCount, rw *senderDeliveries, rw *senderExpirations, rw *sessions, clock} a
27852780backgroundPrune = do
27862781 sleep backgroundPruneInterval
27872782 dp <- *drainPhase
@@ -2791,7 +2786,7 @@ backgroundPrune = do
27912786 pruneAndRetry t)
27922787 backgroundPrune
27932788
2794−pollHeartbeat : IO {reads *heartbeatCount, reads *lastHeartbeatAt, reads *pollWakeup, writes *heartbeatCount, writes *lastHeartbeatAt, writes *pollWakeup, clock} a
2789+pollHeartbeat : IO {rw *heartbeatCount, rw *lastHeartbeatAt, rw *pollWakeup, clock} a
27952790pollHeartbeat = do
27962791 sleep pollTimeoutMs
27972792 t <- now
@@ -2822,3 +2817,4 @@ main = do
28222817 fork backgroundPrune
28232818 fork pollHeartbeat
28242819 listen listenPort serve
2820+ listen listenPort serve