on master help sign in
Remove message cancellation functionality
commit133d0df1ae3b911f1a0cb6fec4ce1b18c457dfde authoriko <mail@iko.soy> dateSun 3 May 2026, 12:49 (5 months ago) parent1ca7aa4
1 file changed+24−183
server.knot+24−183
server.knot+24−183
@@ -41,7 +41,7 @@ maxSignatureLen = 256
4141-- Cap on the number of message IDs accepted in a single ack/lookup request.
4242maxMessageIds : Int
4343maxMessageIds = 100
44−-- Cap on items in a batched send or batched cancel request.
44+-- Cap on items in a batched send request.
4545maxBatchSize : Int
4646maxBatchSize = 100
4747-- Cap on gossip events accepted or emitted in a single federation exchange.
@@ -363,15 +363,6 @@ type SenderDelivery = {
363363 toKey: PubkeyHex,
364364 deliveredAt: Timestamp
365365}
366−type CancelNotification = {toKey: PubkeyHex, messageId: MessageIdHex, cancelledAt: Timestamp}
367−type PendingFederationCancel = {
368− messageId: MessageIdHex,
369− toKey: PubkeyHex,
370− toServer: ServerName,
371− createdAt: Timestamp,
372− retries: Int
373−}
374−
375366type BatchMessage = {
376367 to: PubkeyHex,
377368 encryptedBlob: BlobHex,
@@ -381,9 +372,6 @@ type BatchMessage = {
381372}
382373type BatchResult = {messageId: OptionalMessageId, status: MessageStatus}
383374
384−type BatchCancelEntry = {messageId: MessageIdHex, to: PubkeyHex}
385−type BatchCancelResult = {messageId: MessageIdHex, cancelled: Bool}
386−
387375-- ============================================
388376-- Persisted State (all flat lists, no sharding)
389377-- ============================================
@@ -429,8 +417,6 @@ type BatchCancelResult = {messageId: MessageIdHex, cancelled: Bool}
429417
430418*senderExpirations : [SenderExpiration]
431419*senderDeliveries : [SenderDelivery]
432−*cancelNotifications : [CancelNotification]
433−*pendingFederationCancels : [PendingFederationCancel]
434420
435421*runtimeMaintenanceSecret : Text
436422
@@ -448,8 +434,8 @@ type BatchCancelResult = {messageId: MessageIdHex, cancelled: Bool}
448434-- ============================================
449435-- Public HTTP surface, grouped by audience:
450436-- AuthApi -- challenge/verify/revoke/logout (client identity)
451−-- ClientApi -- /poll, send/batch/list/ack/delete/cancel, presence lookup
452−-- FederationApi -- inter-server gossip, forward, ack, cancel, delivery receipts
437+-- ClientApi -- /poll, send/batch/list/ack/delete, presence lookup
438+-- FederationApi -- inter-server gossip, forward, ack, delivery receipts
453439-- FederationPubkey -- exposes this server's federation Ed25519 public key
454440-- MaintenanceApi -- operator endpoints (drain, prune, peer allow/block, admin)
455441-- HealthApi/StatsApi/MetricsApi/ReadyApi -- observability
@@ -463,15 +449,13 @@ route AuthApi where
463449 POST /auth/logout headers {authorization: Text, clientIp: Text} -> {ok: Bool, authorized: Bool} = Logout
464450
465451route ClientApi where
466− POST {cursor: Timestamp, generation: Int} /poll headers {authorization: Text, clientIp: Text} -> {events: [PollEvent], cursor: Timestamp, authorized: Bool, rateLimited: Bool, draining: Bool, generation: Int, heartbeat: Bool, retryAfterMs: Int, cursorReset: Bool, hasMore: Bool, cursorStale: Bool, messageExpirations: [{messageId: MessageIdHex, toKey: PubkeyHex, expiredAt: Timestamp}], messageDeliveries: [DeliveredEntry], droppedDeliveryReceipts: Int, droppedExpirationNotifs: Int, loadPct: Int, messageCancellations: [{messageId: MessageIdHex, cancelledAt: Timestamp}]} = Poll
452+ POST {cursor: Timestamp, generation: Int} /poll headers {authorization: Text, clientIp: Text} -> {events: [PollEvent], cursor: Timestamp, authorized: Bool, rateLimited: Bool, draining: Bool, generation: Int, heartbeat: Bool, retryAfterMs: Int, cursorReset: Bool, hasMore: Bool, cursorStale: Bool, messageExpirations: [{messageId: MessageIdHex, toKey: PubkeyHex, expiredAt: Timestamp}], messageDeliveries: [DeliveredEntry], droppedDeliveryReceipts: Int, droppedExpirationNotifs: Int, loadPct: Int} = Poll
467453 POST {to: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp, messageId: OptionalMessageId, expiresInMs: Int} /messages headers {authorization: Text, clientIp: Text} -> {status: MessageStatus, messageId: OptionalMessageId, retryAfterMs: Int, error: Text, loadPct: Int} = SendMessage
468454 POST {to: PubkeyHex, messageId: MessageIdHex} /messages/status headers {authorization: Text, clientIp: Text} -> {status: Text, authorized: Bool} = MessageStatus
469455 POST {messageIds: [MessageIdHex]} /messages/ack headers {authorization: Text, clientIp: Text} -> {ok: Bool, authorized: Bool, ackedCount: Int, error: Text} = AckMessages
470456 POST {messages: [BatchMessage]} /messages/batch headers {authorization: Text, clientIp: Text} -> {results: [BatchResult], authorized: Bool, retryAfterMs: Int} = SendBatch
471457 POST {offset: Int} /messages/list headers {authorization: Text, clientIp: Text} -> {messages: [{messageId: MessageIdHex, timestamp: Timestamp, receivedAt: Timestamp}], authorized: Bool, count: Int, error: Text} = ListMessages
472458 POST {messageIds: [MessageIdHex]} /messages/delete headers {authorization: Text, clientIp: Text} -> {ok: Bool, authorized: Bool, deletedCount: Int, error: Text} = DeleteMessages
473− POST {messageId: MessageIdHex, to: PubkeyHex} /messages/cancel headers {authorization: Text, clientIp: Text} -> {ok: Bool, authorized: Bool, cancelled: Bool, error: Text} = CancelMessage
474− POST {cancels: [BatchCancelEntry]} /messages/cancel-batch headers {authorization: Text, clientIp: Text} -> {results: [BatchCancelResult], authorized: Bool, error: Text} = CancelBatch
475459 GET /lookup/{pubkey: PubkeyHex} headers {authorization: Text, clientIp: Text} -> {server: AnyServerName, authorized: Bool, error: Text} = LookupPresence
476460 POST {pubkeys: [PubkeyHex]} /lookup/batch headers {authorization: Text, clientIp: Text} -> {results: [{pubkey: PubkeyHex, server: AnyServerName}], authorized: Bool, error: Text} = BatchLookupPresence
477461
@@ -479,8 +463,6 @@ route FederationApi where
479463 POST {events: [GossipEvent], hopsRemaining: Int, fromServer: ServerName, requestTimestamp: Timestamp, correlationId: Text, protocolVersion: Int} /federation/gossip headers {authorization: Text} -> {ok: Bool, error: Text, retryAfterMs: Int} = RecvGossip
480464 POST {toKey: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp, originServer: ServerName, messageId: MessageIdHex, fromServer: ServerName, requestTimestamp: Timestamp, correlationId: Text, expiresAt: Timestamp, protocolVersion: Int} /federation/forward headers {authorization: Text} -> {ok: Bool, error: Text, retryAfterMs: Int} = ForwardMessage
481465 POST {messageIds: [MessageIdHex], originServer: ServerName, fromServer: ServerName, requestTimestamp: Timestamp, correlationId: Text, protocolVersion: Int} /federation/ack headers {authorization: Text} -> {ok: Bool, error: Text, ackedCount: Int, results: [{messageId: MessageIdHex, found: Bool}], retryAfterMs: Int} = FederationAck
482− POST {messageId: MessageIdHex, toKey: PubkeyHex, fromServer: ServerName, requestTimestamp: Timestamp, correlationId: Text, protocolVersion: Int} /federation/cancel headers {authorization: Text} -> {ok: Bool, error: Text, cancelled: Bool, retryAfterMs: Int} = FederationCancel
483− POST {cancels: [{messageId: MessageIdHex, toKey: PubkeyHex}], fromServer: ServerName, requestTimestamp: Timestamp, correlationId: Text, protocolVersion: Int} /federation/cancel-batch headers {authorization: Text} -> {ok: Bool, error: Text, results: [BatchCancelResult], retryAfterMs: Int} = FederationBatchCancel
484466 POST {forwards: [{toKey: PubkeyHex, encryptedBlob: BlobHex, timestamp: Timestamp, originServer: ServerName, messageId: MessageIdHex, expiresAt: Timestamp}], fromServer: ServerName, requestTimestamp: Timestamp, correlationId: Text, protocolVersion: Int} /federation/forward-batch headers {authorization: Text} -> {ok: Bool, error: Text, results: [{messageId: MessageIdHex, ok: Bool}], retryAfterMs: Int} = FederationBatchForward
485467 POST {messageIds: [MessageIdHex], toKeys: [PubkeyHex], fromServer: ServerName, requestTimestamp: Timestamp, correlationId: Text, protocolVersion: Int} /federation/delivery-receipt headers {authorization: Text} -> {ok: Bool, error: Text, receivedCount: Int, retryAfterMs: Int} = FederationDeliveryReceipt
486468
@@ -531,7 +513,7 @@ type KeyPresenceEntry = {server: Text, expiresAt: Timestamp}
531513type RecipientCount = {pubkey: PubkeyHex, count: Int}
532514
533515route StatsApi where
534− GET /stats headers {authorization: Text} -> {ok: Bool, version: Text, capacity: {sessions: Int, sessionsMax: Int, sessionsPct: Int, messages: Int, messagesMax: Int, messagesPct: Int, totalMessageCount: Int, presence: Int, presenceMax: Int, presencePct: Int, forwards: Int, forwardsMax: Int, forwardsPct: Int, challenges: Int, challengesMax: Int, ackedIds: Int, ackedIdsMax: Int, ackOverflow: Int, ackOverflowMax: Int, deliveredIds: Int, deliveredIdsMax: Int, forwardNonces: Int, forwardNoncesMax: Int, nonceEvictions: Int, activePolls: Int, activePollsMax: Int, recipientCounts: Int, messageBucketMax: Int, messageBucketMin: Int}, federation: {enabled: Bool, inFlight: Int, hungSlots: Int, avgLatencyMs: Int, maxLatencyMs: Int, callCount: Int, slowVerifies: Int, verifyFailures: Int, forwardFailures: Int, oldestForwardAge: Int, oldestAckAge: Int, recentGossip: Int, blockedPeers: Int, drainCompletedAt: Timestamp, forwardServers: Int, maxForwardQueueDepth: Int, topFailingPeers: [{server: Text, failures: Int}], pendingCancels: Int, gossipSuccess: Int, gossipFail: Int, latencyBuckets: {lt100: Int, lt500: Int, lt2000: Int, gte2000: Int}}, rates: {send: Int, challenge: Int, federation: Int, federationPair: Int, global: Int, ip: Int, lookup: Int, recv: Int, pair: Int, gossipEvent: Int, verifyIp: Int, uniqueKeyIp: Int, pendingAckCleanups: Int, senderExpirations: Int, senderDeliveries: Int, rejectedGlobal: Int, rejectedIp: Int, rejectedAuth: Int, rejectedPerKey: Int, rejectedFederation: Int, clientCancels: Int, federationCancels: Int}, timing: {pruneCount: Int, pruneRunning: Timestamp, heartbeatCount: Int, uptime: Int, lastPruneAt: Timestamp, lastHeartbeatAt: Timestamp, draining: Bool, drainPhase: Int, totalEntries: Int, activeRequests: Int, peakActiveRequests: Int, pollAvgMs: Int, pollMaxMs: Int, pollCount: Int, pollLatencyBuckets: {lt50: Int, lt200: Int, lt1000: Int, gte1000: Int}, sendAvgMs: Int, sendMaxMs: Int, sendCount: Int, sendLatencyBuckets: {lt50: Int, lt200: Int, lt1000: Int, gte1000: Int}, authAvgMs: Int, authMaxMs: Int, authCount: Int}} = Stats
516+ GET /stats headers {authorization: Text} -> {ok: Bool, version: Text, capacity: {sessions: Int, sessionsMax: Int, sessionsPct: Int, messages: Int, messagesMax: Int, messagesPct: Int, totalMessageCount: Int, presence: Int, presenceMax: Int, presencePct: Int, forwards: Int, forwardsMax: Int, forwardsPct: Int, challenges: Int, challengesMax: Int, ackedIds: Int, ackedIdsMax: Int, ackOverflow: Int, ackOverflowMax: Int, deliveredIds: Int, deliveredIdsMax: Int, forwardNonces: Int, forwardNoncesMax: Int, nonceEvictions: Int, activePolls: Int, activePollsMax: Int, recipientCounts: Int, messageBucketMax: Int, messageBucketMin: Int}, federation: {enabled: Bool, inFlight: Int, hungSlots: Int, avgLatencyMs: Int, maxLatencyMs: Int, callCount: Int, slowVerifies: Int, verifyFailures: Int, forwardFailures: Int, oldestForwardAge: Int, oldestAckAge: Int, recentGossip: Int, blockedPeers: Int, drainCompletedAt: Timestamp, forwardServers: Int, maxForwardQueueDepth: Int, topFailingPeers: [{server: Text, failures: Int}], gossipSuccess: Int, gossipFail: Int, latencyBuckets: {lt100: Int, lt500: Int, lt2000: Int, gte2000: Int}}, rates: {send: Int, challenge: Int, federation: Int, federationPair: Int, global: Int, ip: Int, lookup: Int, recv: Int, pair: Int, gossipEvent: Int, verifyIp: Int, uniqueKeyIp: Int, pendingAckCleanups: Int, senderExpirations: Int, senderDeliveries: Int, rejectedGlobal: Int, rejectedIp: Int, rejectedAuth: Int, rejectedPerKey: Int, rejectedFederation: Int}, timing: {pruneCount: Int, pruneRunning: Timestamp, heartbeatCount: Int, uptime: Int, lastPruneAt: Timestamp, lastHeartbeatAt: Timestamp, draining: Bool, drainPhase: Int, totalEntries: Int, activeRequests: Int, peakActiveRequests: Int, pollAvgMs: Int, pollMaxMs: Int, pollCount: Int, pollLatencyBuckets: {lt50: Int, lt200: Int, lt1000: Int, gte1000: Int}, sendAvgMs: Int, sendMaxMs: Int, sendCount: Int, sendLatencyBuckets: {lt50: Int, lt200: Int, lt1000: Int, gte1000: Int}, authAvgMs: Int, authMaxMs: Int, authCount: Int}} = Stats
535517 GET /stats/peers headers {authorization: Text} -> {peers: [PeerStatus], ok: Bool} = StatsPeers
536518 GET /stats/key/{pubkey: PubkeyHex} headers {authorization: Text} -> {messages: Int, presence: [KeyPresenceEntry], sessions: Int, forwards: Int, forwardDetails: [{server: Text, retries: Int, createdAt: Timestamp}], activePolls: Int, ok: Bool} = StatsKey
537519 POST {limit: Int} /stats/top-recipients headers {authorization: Text} -> {recipients: [RecipientCount], ok: Bool, total: Int} = TopRecipients
@@ -623,7 +605,7 @@ capPerKey : [a] -> (a -> Bool) -> Int -> (a -> b) -> [a]
623605capPerKey = \items pred maxN getKey -> union (removeWhere items pred) (capList (filter items pred) (maxN - 1) getKey)
624606
625607-- Shared empty/error shape for /poll. Only a few fields ever vary.
626−emptyPollResp : Timestamp -> Bool -> Bool -> Bool -> Int -> Int -> Bool -> {events: [PollEvent], cursor: Timestamp, authorized: Bool, rateLimited: Bool, draining: Bool, generation: Int, heartbeat: Bool, retryAfterMs: Int, cursorReset: Bool, hasMore: Bool, cursorStale: Bool, messageExpirations: [{messageId: MessageIdHex, toKey: PubkeyHex, expiredAt: Timestamp}], messageDeliveries: [DeliveredEntry], droppedDeliveryReceipts: Int, droppedExpirationNotifs: Int, loadPct: Int, messageCancellations: [{messageId: MessageIdHex, cancelledAt: Timestamp}]}
608+emptyPollResp : Timestamp -> Bool -> Bool -> Bool -> Int -> Int -> Bool -> {events: [PollEvent], cursor: Timestamp, authorized: Bool, rateLimited: Bool, draining: Bool, generation: Int, heartbeat: Bool, retryAfterMs: Int, cursorReset: Bool, hasMore: Bool, cursorStale: Bool, messageExpirations: [{messageId: MessageIdHex, toKey: PubkeyHex, expiredAt: Timestamp}], messageDeliveries: [DeliveredEntry], droppedDeliveryReceipts: Int, droppedExpirationNotifs: Int, loadPct: Int}
627609emptyPollResp = \cursor authorized rateLimited draining gen retryAfterMs cursorWasReset -> {
628610 events: [],
629611 cursor,
@@ -640,8 +622,7 @@ emptyPollResp = \cursor authorized rateLimited draining gen retryAfterMs cursorW
640622 messageDeliveries: [],
641623 droppedDeliveryReceipts: 0,
642624 droppedExpirationNotifs: 0,
643− loadPct: 0,
644− messageCancellations: []
625+ loadPct: 0
645626}
646627
647628-- Shared error shape for /messages send.
@@ -1021,7 +1002,7 @@ mergePresence = \existing incoming -> fold (\acc e -> (\same -> if any acc (\x -
10211002-- block. Returns the locally-expired presence rows whose pubkey has no other
10221003-- live local session, so the caller can broadcast offline gossip for them.
10231004
1024−pruneExpired : Int<Ms> -> IO {reads *ackedIds, reads *activePolls, reads *cancelNotifications, reads *challenges, reads *deliveredIds, reads *forwardFailures, reads *forwards, reads *messages, reads *pendingFederationCancels, reads *presence, reads *senderDeliveries, reads *senderExpirations, reads *sessions, writes *ackedIds, writes *activePolls, writes *cancelNotifications, writes *challenges, writes *deliveredIds, writes *forwardFailures, writes *forwards, writes *messages, writes *pendingFederationCancels, writes *presence, writes *senderDeliveries, writes *senderExpirations, writes *sessions} [{expiresAt: Timestamp, pubkey: PubkeyHex, server: AnyServerName, timestamp: Timestamp}]
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}]
10251006pruneExpired = \t -> atomic do
10261007 ch <- *challenges
10271008 *challenges = filter ch (\x -> x.expiresAt > t)
@@ -1043,10 +1024,6 @@ pruneExpired = \t -> atomic do
10431024 *senderExpirations = filter se (\x -> t - x.expiredAt < unackedMessageMaxAge)
10441025 sd <- *senderDeliveries
10451026 *senderDeliveries = filter sd (\x -> t - x.deliveredAt < unackedMessageMaxAge)
1046− cn <- *cancelNotifications
1047− *cancelNotifications = filter cn (\x -> t - x.cancelledAt < unackedMessageMaxAge)
1048− pfc <- *pendingFederationCancels
1049− *pendingFederationCancels = filter pfc (\x -> t - x.createdAt < messageMaxAge)
10501027 -- presence: remember locally-expired keys for offline gossip
10511028 pres <- *presence
10521029 *presence = filter pres (\p -> p.expiresAt > t)
@@ -1093,29 +1070,6 @@ recordAcksAndDeliveries = \toAck t -> do
10931070 *senderDeliveries = capList (union sds myDel) 10000 (\d -> d.deliveredAt)
10941071 yield ackedFed
10951072
1096−-- Common cancel block: drop matching messages + forwards, record cancel notification.
1097−-- Returns the federated forward servers that had the message (for caller follow-up).
1098−applyCancel : (forall r. {messageId: MessageIdHex, toKey: PubkeyHex | r} -> Bool) -> PubkeyHex -> MessageIdHex -> Int<Ms> -> IO {reads *cancelNotifications, reads *forwards, reads *messages, writes *cancelNotifications, writes *forwards, writes *messages} [Text]
1099−applyCancel = \matches to mid t -> do
1100− mssgs <- *messages
1101− *messages = removeWhere mssgs matches
1102− fwds <- *forwards
1103− *forwards = removeWhere fwds matches
1104− cn <- *cancelNotifications
1105− *cancelNotifications = capList (union cn [{toKey: to, messageId: mid, cancelledAt: t}]) 10000 (\c -> c.cancelledAt)
1106− yield (collectPeers (filter fwds matches) (\f -> f.toServer))
1107−
1108−-- Cancel one message if `ownerPred` holds for it; returns whether a cancel happened.
1109−cancelOne : MessageIdHex -> PubkeyHex -> ({| r} -> Bool) -> Int<Ms> -> IO {reads *messages, reads *cancelNotifications, reads *forwards, writes *cancelNotifications, writes *forwards, writes *messages} Bool
1110−cancelOne = \mid to ownerPred t -> atomic (do
1111− msgs <- *messages
1112− let matches = \x -> x.messageId == mid && x.toKey == to
1113− let owns = any msgs (\m -> matches m && ownerPred m)
1114− when owns (do
1115− applyCancel matches to mid t
1116− yield {})
1117− yield owns)
1118−
11191073sendBatchedGossip : [Text] -> [GossipEvent] -> IO {} {}
11201074sendBatchedGossip = \peers events -> forEach peers (\server -> do
11211075 t <- now
@@ -1248,7 +1202,7 @@ forwardQueuedToServer = \pubkey server t -> atomic (do
12481202-- expire stale rows, then opportunistically retry pending forwards and refresh
12491203-- local presence so peers don't see us go offline mid-session.
12501204
1251−pruneAndRetry : Int<Ms> -> IO {reads *ackedIds, reads *activePolls, reads *cancelNotifications, reads *challenges, reads *deliveredIds, reads *federationPrivateKeyHex, reads *forwardFailures, reads *forwards, reads *lastPrunedAt, reads *messages, reads *pendingFederationCancels, reads *presence, reads *senderDeliveries, reads *senderExpirations, reads *sessions, writes *ackedIds, writes *activePolls, writes *cancelNotifications, writes *challenges, writes *deliveredIds, writes *forwardFailures, writes *forwards, writes *lastPrunedAt, writes *messages, writes *pendingFederationCancels, writes *presence, writes *senderDeliveries, writes *senderExpirations, writes *sessions, reads *blockedPeers, reads *lastPresenceRefresh, writes *lastPresenceRefresh, clock} {}
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} {}
12521206pruneAndRetry = \t -> do
12531207 *lastPrunedAt = t
12541208 expiredLocal <- pruneExpired t
@@ -1429,13 +1383,13 @@ handleLogout = \authorization clientIp respond -> do
14291383-- * If the generation mismatches, reset the cursor and signal `cursorReset`.
14301384-- * Ack-and-drop everything <= the cursor (treating poll progress as ack).
14311385-- * Wait on `pollWakeup` (bumped by send/heartbeat) and return new events
1432−-- plus piggybacked sender-side expirations/deliveries/cancellations.
1386+-- plus piggybacked sender-side expirations/deliveries.
14331387-- Per-pubkey concurrency is capped via `activePolls` to bound long-poll holds.
14341388
14351389-- POST /poll: top-level dispatch. Validates drain/global gates, detects a
14361390-- generation mismatch (server restart) to force a cursor reset, then hands
14371391-- off to `pollOnce` for the actual long-poll body.
1438−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, messageCancellations: [{cancelledAt: Timestamp, messageId: MessageIdHex}], messageDeliveries: [DeliveredEntry], messageExpirations: [{expiredAt: Timestamp, messageId: MessageIdHex, toKey: PubkeyHex}], rateLimited: Bool, retryAfterMs: Int} -> Response) -> IO {reads *activePolls, reads *cancelNotifications, reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *pollWakeup, reads *senderDeliveries, reads *senderExpirations, reads *serverGeneration, reads *sessions, writes *activePolls, writes *cancelNotifications, writes *globalRateCount, writes *globalRateWindowStart, writes *senderDeliveries, writes *senderExpirations, clock} Response
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
14391393handlePoll = \cursor generation authorization clientIp respond -> do
14401394 t <- now
14411395 serverGen <- *serverGeneration
@@ -1452,8 +1406,8 @@ handlePoll = \cursor generation authorization clientIp respond -> do
14521406-- Body of a single poll. Treats the cursor advance as an implicit ack
14531407-- (`ackPolledMessages`), acquires a per-pubkey poll slot, runs `doPoll` to
14541408-- collect events (or block until wakeup), then assembles the response with
1455−-- piggybacked expiration/delivery/cancellation metadata.
1456−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, messageCancellations: [{cancelledAt: Timestamp, messageId: MessageIdHex}], messageDeliveries: [DeliveredEntry], messageExpirations: [{expiredAt: Timestamp, messageId: MessageIdHex, toKey: PubkeyHex}], rateLimited: Bool, retryAfterMs: Int} -> Response) -> Int<Ms> -> IO {reads *activePolls, reads *cancelNotifications, reads *pollWakeup, reads *senderDeliveries, reads *senderExpirations, reads *sessions, writes *activePolls, writes *cancelNotifications, writes *senderDeliveries, writes *senderExpirations, clock} Response
1409+-- 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
14571411pollOnce = \authorization cursor cursorWasReset clientIp serverGen respond t -> do
14581412 setup <- atomic withSessionAuth authorization t clientIp (yield (Nothing {})) (\pubkey -> do
14591413 let clampedCursor = clamp 0 t cursor
@@ -1484,14 +1438,9 @@ pollOnce = \authorization cursor cursorWasReset clientIp serverGen respond t ->
14841438 sdItems <- *senderDeliveries
14851439 *senderDeliveries = removeWhere sdItems mine
14861440 let myDel = mapWhere sdItems mine (\x -> {x.messageId, x.toKey, x.deliveredAt})
1487− cnItems <- *cancelNotifications
1488− let isMine = \x -> x.toKey == s.pubkey
1489− *cancelNotifications = removeWhere cnItems isMine
1490− let myCancels = mapWhere cnItems isMine (\x -> {x.messageId, x.cancelledAt})
14911441 yield {
14921442 expirations: myExp,
1493− deliveries: myDel,
1494− cancellations: myCancels
1443+ deliveries: myDel
14951444 })
14961445 yield (respond {
14971446 pollResult.events,
@@ -1509,8 +1458,7 @@ pollOnce = \authorization cursor cursorWasReset clientIp serverGen respond t ->
15091458 messageDeliveries: extras.deliveries,
15101459 droppedDeliveryReceipts: 0,
15111460 droppedExpirationNotifs: 0,
1512− loadPct: 0,
1513− messageCancellations: extras.cancellations
1461+ loadPct: 0
15141462 })
15151463
15161464-- One pass: collect events at `t`. If empty, retry-wait on `pollWakeup`
@@ -1762,12 +1710,9 @@ handleMessageStatus = \to messageId authorization clientIp respond -> do
17621710 yield (respond {status: s, authorized: True {}}))
17631711
17641712-- ============================================
1765−-- Handler: ack / list / delete / cancel
1713+-- Handler: ack / list / delete
17661714-- ============================================
1767−-- Mailbox mutation by the recipient (ack, list, delete) and by the original
1768−-- sender (cancel, cancel-batch). Cancel propagates over federation when the
1769−-- message has already been forwarded to peers; if the remote rejects, we
1770−-- queue a `pendingFederationCancels` retry.
1715+-- Mailbox mutation by the recipient (ack, list, delete).
17711716
17721717-- POST /messages/ack: explicit ack by message id (alternative to the implicit
17731718-- cursor-based ack in /poll). Drops mailbox+forward rows and forks federation
@@ -1845,76 +1790,6 @@ handleDeleteMessages = \messageIds authorization clientIp respond -> do
18451790 when federationEnabled (fork (sendAcksAndClean result.pendingAcks))
18461791 yield (respond {ok: True {}, authorized: True {}, result.deletedCount, error: ""}))
18471792
1848−-- POST /messages/cancel: sender-side recall. Only succeeds while the message
1849−-- is still locally undelivered AND was sent by this caller; if it had already
1850−-- been forwarded to peers, federation cancels are dispatched in a fork.
1851−handleCancelMessage : MessageIdHex -> PubkeyHex -> Text -> Text -> ({authorized: Bool, cancelled: Bool, error: Text, ok: Bool} -> a) -> IO {reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart, clock} a
1852−handleCancelMessage = \messageId to authorization clientIp respond -> do
1853− t <- now
1854− let mkErr = \e -> {
1855− ok: False {},
1856− authorized: False {},
1857− cancelled: False {},
1858− error: if e == "draining" then "" else e
1859− }
1860− gateClientWrite authorization clientIp t respond mkErr {
1861− ok: False {},
1862− authorized: True {},
1863− cancelled: False {},
1864− error: "rate_limited"
1865− } (\senderKey -> do
1866− let matches = \x -> x.messageId == messageId && x.toKey == to
1867− result <- atomic (do
1868− msgs <- *messages
1869− if any msgs (\m -> matches m && m.originServer == "" && m.senderKey == senderKey)
1870− then do
1871− fwdServers <- applyCancel matches to messageId t
1872− pres <- *presence
1873− yield {
1874− cancelled: True {},
1875− cancelServers: unique (union (collectPeers (presenceForKey pres to t) (\p -> p.server)) fwdServers)
1876− }
1877− else yield {cancelled: False {}, cancelServers: []})
1878− when (result.cancelled && federationEnabled) (fork (sendCancelToEach messageId to result.cancelServers))
1879− yield (respond {ok: True {}, authorized: True {}, result.cancelled, error: ""}))
1880−
1881−-- Best-effort federation cancel; on failure the cancel is queued in
1882−-- `pendingFederationCancels` so a later background pass can retry it.
1883−sendCancelToEach : MessageIdHex -> PubkeyHex -> [Text] -> IO {} {}
1884−sendCancelToEach = \messageId to servers -> forEach servers (\server -> do
1885− t <- now
1886− auth <- generateFederationAuth ("cancel:" ++ messageId) t
1887− result <- fetch (federationUrl server) (FederationCancel {
1888− messageId,
1889− toKey: to,
1890− fromServer: serverHostN,
1891− requestTimestamp: t,
1892− correlationId: "",
1893− authorization: auth,
1894− protocolVersion: federationProtocolVersion
1895− })
1896− when (case result of Err _ -> True {}; Ok {value: r} -> not r.ok) (atomic do
1897− cur <- *pendingFederationCancels
1898− *pendingFederationCancels = capList (union cur [
1899− {messageId, toKey: to, toServer: server, createdAt: t, retries: 0}
1900− ]) 10000 (\c -> c.createdAt)))
1901−
1902−-- POST /messages/cancel-batch: multi-id sender-side cancel. Same ownership
1903−-- check as `handleCancelMessage`, applied independently per entry.
1904−handleCancelBatch : [{messageId: MessageIdHex, to: PubkeyHex | a}] -> Text -> Text -> ({authorized: Bool, error: Text, results: [BatchCancelResult]} -> b) -> IO {reads *drainPhase, reads *globalRateCount, reads *globalRateWindowStart, reads *sessions, writes *globalRateCount, writes *globalRateWindowStart, clock} b
1905−handleCancelBatch = \cancels authorization clientIp respond -> do
1906− t <- now
1907− let mkErr = \e -> {results: [], authorized: False {}, error: if e == "draining" then "" else e}
1908− gateClientWrite authorization clientIp t respond mkErr {
1909− results: [],
1910− authorized: True {},
1911− error: "rate_limited"
1912− } (\senderKey -> do
1913− results <- mapEach (takeRelation maxBatchSize cancels) (\e -> do
1914− r <- cancelOne e.messageId e.to (\m -> m.originServer == "" && m.senderKey == senderKey) t
1915− yield {e.messageId, cancelled: r})
1916− yield (respond {results, authorized: True {}, error: ""}))
1917−
19181793-- POST /messages/batch: multi-recipient send in one call. Each entry runs
19191794-- through `sendMessageCore` separately so per-entry rate limits/errors don't
19201795-- abort the rest; federated forwards are forked per entry.
@@ -2082,29 +1957,6 @@ handleFederationAck = \messageIds originServer fromServer requestTimestamp autho
20821957 })
20831958 yield (respond {ok: True {}, error: "", result.ackedCount, result.results, retryAfterMs: 0}))
20841959
2085−-- POST /federation/cancel: peer asks us to cancel a message it originated
2086−-- here. The owner predicate requires `originServer == fromServer` so peers
2087−-- can only cancel their own messages.
2088−handleFederationCancel : Text -> PubkeyHex -> Text -> Int -> Text -> ({cancelled: Bool, error: Text, ok: Bool, retryAfterMs: Int} -> a) -> IO {reads *allowedPeers, reads *blockedPeers, reads *federationRates, writes *federationRates, clock, reads *cancelNotifications, reads *forwards, reads *messages, writes *cancelNotifications, writes *forwards, writes *messages, network} a
2089−handleFederationCancel = \messageId toKey fromServer requestTimestamp authorization respond -> do
2090− t <- now
2091− let cancelRespond = \r -> respond {r.ok, r.error, cancelled: False {}, r.retryAfterMs}
2092− withFederationAuth authorization t cancelRespond fromServer ("cancel:" ++ messageId) requestTimestamp (isRemote fromServer) (do
2093− cancelled <- cancelOne messageId toKey (\m -> m.originServer == fromServer) t
2094− yield (respond {ok: True {}, error: "", cancelled, retryAfterMs: 0}))
2095−
2096−-- POST /federation/cancel-batch: same ownership constraint as
2097−-- `handleFederationCancel`, applied independently per entry.
2098−handleFederationBatchCancel : [{messageId: MessageIdHex, toKey: PubkeyHex | a}] -> Text -> Int -> Text -> ({error: Text, ok: Bool, results: [BatchCancelResult], retryAfterMs: Int} -> b) -> IO {reads *allowedPeers, reads *blockedPeers, reads *federationRates, writes *federationRates, clock, network} b
2099−handleFederationBatchCancel = \cancels fromServer requestTimestamp authorization respond -> do
2100− t <- now
2101− let batchRespond = \r -> respond {r.ok, r.error, results: [], r.retryAfterMs}
2102− withFederationAuth authorization t batchRespond fromServer "cancel-batch" requestTimestamp (isRemote fromServer && count cancels <= maxBatchSize) (do
2103− results <- mapEach (takeRelation maxBatchSize cancels) (\e -> do
2104− cancelled <- cancelOne e.messageId e.toKey (\m -> m.originServer == fromServer) t
2105− yield {e.messageId, cancelled})
2106− yield (respond {ok: True {}, error: "", results, retryAfterMs: 0}))
2107−
21081960-- POST /federation/forward-batch: bulk variant of `handleForwardMessage`.
21091961-- Skips per-entry recv-rate charging (the per-server federation rate limit
21101962-- already gates the call as a whole).
@@ -2206,7 +2058,7 @@ handleShutdown = \authorization respond -> handleDrain 2 authorization (\r -> re
22062058-- POST /maintenance/prune: trigger an out-of-band `pruneAndRetry` tick.
22072059-- Useful when the operator wants to force expiry/forward retries without
22082060-- waiting for the next `backgroundPrune` interval.
2209−handlePrune : Text -> ({ok: Bool} -> a) -> IO {reads *runtimeMaintenanceSecret, clock, reads *ackedIds, reads *activePolls, reads *blockedPeers, reads *cancelNotifications, reads *challenges, reads *deliveredIds, reads *federationPrivateKeyHex, reads *forwardFailures, reads *forwards, reads *lastPresenceRefresh, reads *lastPrunedAt, reads *messages, reads *pendingFederationCancels, reads *presence, reads *senderDeliveries, reads *senderExpirations, reads *sessions, writes *ackedIds, writes *activePolls, writes *cancelNotifications, writes *challenges, writes *deliveredIds, writes *forwardFailures, writes *forwards, writes *lastPresenceRefresh, writes *lastPrunedAt, writes *messages, writes *pendingFederationCancels, writes *presence, writes *senderDeliveries, writes *senderExpirations, writes *sessions} a
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
22102062handlePrune = \authorization respond -> do
22112063 t <- now
22122064 authorized <- verifyMaintenanceAuth authorization
@@ -2491,7 +2343,7 @@ handleGetConfig = \authorization respond -> do
24912343-- GET /stats: capacity gauges + minimal federation/timing snapshot. Most of
24922344-- the historical metric fields are zero-filled (see `zeroStats`) — only the
24932345-- counts that come straight from the state refs are populated.
2494−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, pendingCancels: Int, recentGossip: Int, slowVerifies: Int, topFailingPeers: [{failures: Int, server: Text}], verifyFailures: Int}, ok: Bool, rates: {challenge: Int, clientCancels: Int, federation: Int, federationCancels: 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 *pendingFederationCancels, reads *presence, reads *pruneCount, reads *runtimeMaintenanceSecret, reads *sessions, reads *startedAt, clock} a
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
24952347handleStats = \authorization respond -> withMaintAuth authorization (yield (respond zeroStats)) (do
24962348 t <- now
24972349 sa <- *startedAt
@@ -2509,7 +2361,6 @@ handleStats = \authorization respond -> withMaintAuth authorization (yield (resp
25092361 lp <- *lastPrunedAt
25102362 lh <- *lastHeartbeatAt
25112363 dp <- *drainPhase
2512− pfc <- *pendingFederationCancels
25132364 let sCount = count sessions
25142365 let mCount = count msgs
25152366 let pCount = count pres
@@ -2566,7 +2417,6 @@ handleStats = \authorization respond -> withMaintAuth authorization (yield (resp
25662417 forwardServers: 0,
25672418 maxForwardQueueDepth: 0,
25682419 topFailingPeers: [],
2569− pendingCancels: count pfc,
25702420 gossipSuccess: 0,
25712421 gossipFail: 0,
25722422 latencyBuckets: {lt100: 0, lt500: 0, lt2000: 0, gte2000: 0}
@@ -2591,9 +2441,7 @@ handleStats = \authorization respond -> withMaintAuth authorization (yield (resp
25912441 rejectedIp: 0,
25922442 rejectedAuth: 0,
25932443 rejectedPerKey: 0,
2594− rejectedFederation: 0,
2595− clientCancels: 0,
2596− federationCancels: 0
2444+ rejectedFederation: 0
25972445 },
25982446 timing: {
25992447 pruneCount: pc,
@@ -2621,7 +2469,7 @@ handleStats = \authorization respond -> withMaintAuth authorization (yield (resp
26212469 }
26222470 }))
26232471
2624−zeroStats : {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, pendingCancels: Int, recentGossip: Int, slowVerifies: Int, topFailingPeers: [{failures: Int, server: Text}], verifyFailures: Int}, ok: Bool, rates: {challenge: Int, clientCancels: Int, federation: Int, federationCancels: 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}
2472+zeroStats : {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}
26252473zeroStats = {
26262474 ok: False {},
26272475 version: serverBuildVersion,
@@ -2674,7 +2522,6 @@ zeroStats = {
26742522 forwardServers: 0,
26752523 maxForwardQueueDepth: 0,
26762524 topFailingPeers: [],
2677− pendingCancels: 0,
26782525 gossipSuccess: 0,
26792526 gossipFail: 0,
26802527 latencyBuckets: {lt100: 0, lt500: 0, lt2000: 0, gte2000: 0}
@@ -2699,9 +2546,7 @@ zeroStats = {
26992546 rejectedIp: 0,
27002547 rejectedAuth: 0,
27012548 rejectedPerKey: 0,
2702− rejectedFederation: 0,
2703− clientCancels: 0,
2704− federationCancels: 0
2549+ rejectedFederation: 0
27052550 },
27062551 timing: {
27072552 pruneCount: 0,
@@ -2875,7 +2720,7 @@ handleMetrics = \authorization respond -> do
28752720-- to the corresponding `handleXxx` function. Adding a new route means adding
28762721-- a `route ... = NewReq` line, a `handleNewReq` definition, and a clause here.
28772722
2878−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} | CancelBatch {authorization: Text, cancels: [BatchCancelEntry], clientIp: Text, respond: {authorized: Bool, error: Text, results: [BatchCancelResult]} -> Response} | CancelMessage {authorization: Text, clientIp: Text, messageId: MessageIdHex, respond: {authorized: Bool, cancelled: Bool, error: Text, ok: Bool} -> Response, to: PubkeyHex} | 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} | FederationBatchCancel {authorization: Text, cancels: [{messageId: MessageIdHex, toKey: PubkeyHex}], correlationId: Text, fromServer: ServerName, protocolVersion: Int, requestTimestamp: Timestamp, respond: {error: Text, ok: Bool, results: [BatchCancelResult], 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} | FederationCancel {authorization: Text, correlationId: Text, fromServer: ServerName, messageId: MessageIdHex, protocolVersion: Int, requestTimestamp: Timestamp, respond: {cancelled: Bool, error: Text, ok: Bool, retryAfterMs: Int} -> Response, toKey: PubkeyHex} | 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, messageCancellations: [{cancelledAt: Timestamp, messageId: MessageIdHex}], 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, pendingCancels: Int, recentGossip: Int, slowVerifies: Int, topFailingPeers: [{failures: Int, server: Text}], verifyFailures: Int}, ok: Bool, rates: {challenge: Int, clientCancels: Int, federation: Int, federationCancels: 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 *cancelNotifications, 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 *pendingFederationCancels, 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 *cancelNotifications, 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 *pendingFederationCancels, writes *pollWakeup, writes *presence, writes *recvRates, writes *runtimeMaintenanceSecret, writes *senderDeliveries, writes *senderExpirations, writes *seqCounter, writes *sessions, network, clock, random} Response
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
28792724serve = \req -> case req of
28802725 ReqChallenge {pubkey, clientIp, respond} -> handleChallenge pubkey clientIp respond
28812726 VerifyAuth {pubkey, challenge, signature, revokeOthers, clientIp, respond} -> handleVerifyAuth pubkey challenge signature revokeOthers clientIp respond
@@ -2888,15 +2733,11 @@ serve = \req -> case req of
28882733 SendBatch {messages, authorization, clientIp, respond} -> handleSendBatch messages authorization clientIp respond
28892734 ListMessages {offset, authorization, clientIp, respond} -> handleListMessages offset authorization clientIp respond
28902735 DeleteMessages {messageIds, authorization, clientIp, respond} -> handleDeleteMessages messageIds authorization clientIp respond
2891− CancelMessage {messageId, to, authorization, clientIp, respond} -> handleCancelMessage messageId to authorization clientIp respond
2892− CancelBatch {cancels, authorization, clientIp, respond} -> handleCancelBatch cancels authorization clientIp respond
28932736 LookupPresence {pubkey, authorization, clientIp, respond} -> handleLookupPresence pubkey authorization clientIp respond
28942737 BatchLookupPresence {pubkeys, authorization, clientIp, respond} -> handleBatchLookupPresence pubkeys authorization clientIp respond
28952738 RecvGossip {events, hopsRemaining, fromServer, requestTimestamp, authorization, respond} -> handleRecvGossip events hopsRemaining fromServer requestTimestamp authorization respond
28962739 ForwardMessage {toKey, encryptedBlob, timestamp, originServer, messageId, fromServer, requestTimestamp, expiresAt, authorization, respond} -> handleForwardMessage toKey encryptedBlob timestamp originServer messageId fromServer requestTimestamp expiresAt authorization respond
28972740 FederationAck {messageIds, originServer, fromServer, requestTimestamp, authorization, respond} -> handleFederationAck messageIds originServer fromServer requestTimestamp authorization respond
2898− FederationCancel {messageId, toKey, fromServer, requestTimestamp, authorization, respond} -> handleFederationCancel messageId toKey fromServer requestTimestamp authorization respond
2899− FederationBatchCancel {cancels, fromServer, requestTimestamp, authorization, respond} -> handleFederationBatchCancel cancels fromServer requestTimestamp authorization respond
29002741 FederationBatchForward {forwards, fromServer, requestTimestamp, authorization, respond} -> handleFederationBatchForward forwards fromServer requestTimestamp authorization respond
29012742 FederationDeliveryReceipt {messageIds, toKeys, fromServer, requestTimestamp, authorization, respond} -> handleFederationDeliveryReceipt messageIds toKeys fromServer requestTimestamp authorization respond
29022743 FederationPubkey {respond} -> handleFederationPubkey respond
@@ -2940,7 +2781,7 @@ serve = \req -> case req of
29402781-- `pollTimeoutMs` so long-poll holders unblock and return an empty heartbeat
29412782-- response, keeping connections fresh.
29422783
2943−backgroundPrune : IO {reads *drainPhase, clock, reads *ackedIds, reads *activePolls, reads *blockedPeers, reads *cancelNotifications, reads *challenges, reads *deliveredIds, reads *federationPrivateKeyHex, reads *forwardFailures, reads *forwards, reads *lastPresenceRefresh, reads *lastPrunedAt, reads *messages, reads *pendingFederationCancels, reads *presence, reads *pruneCount, reads *senderDeliveries, reads *senderExpirations, reads *sessions, writes *ackedIds, writes *activePolls, writes *cancelNotifications, writes *challenges, writes *deliveredIds, writes *forwardFailures, writes *forwards, writes *lastPresenceRefresh, writes *lastPrunedAt, writes *messages, writes *pendingFederationCancels, writes *presence, writes *pruneCount, writes *senderDeliveries, writes *senderExpirations, writes *sessions} a
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
29442785backgroundPrune = do
29452786 sleep backgroundPruneInterval
29462787 dp <- *drainPhase