@@ -324,7 +324,7 @@ type ActivePoll = {pubkey: PubkeyHex, startedAt: Timestamp}
324*pairRates : [RateEntry]
324*pairRates : [RateEntry]
325-- Per-key /auth/challenge counters (separate from `*challenges` capacity gate).
325-- Per-key /auth/challenge counters (separate from `*challenges` capacity gate).
326*challengeRates : [RateEntry]
326*challengeRates : [RateEntry]
327−-- Per-key counters for read-only lookups (presence, /messages/list).
327+-- Per-key counters for read-only lookups (presence).
328*lookupRates : [RateEntry]
328*lookupRates : [RateEntry]
329-- Per-peer counters for inbound federation calls.
329-- Per-peer counters for inbound federation calls.
330*federationRates : [RateEntry]
330*federationRates : [RateEntry]
@@ -377,7 +377,7 @@ type ActivePoll = {pubkey: PubkeyHex, startedAt: Timestamp}
377-- ============================================
377-- ============================================
378-- Public HTTP surface, grouped by audience:
378-- Public HTTP surface, grouped by audience:
379-- AuthApi -- challenge/verify (client identity)
379-- AuthApi -- challenge/verify (client identity)
380−-- ClientApi -- /poll, /messages (send), /messages/list, /messages/status, presence lookup
380+-- ClientApi -- /poll, /messages (send), /messages/status, presence lookup
381-- FederationApi -- inter-server gossip, forward (single + batch)
381-- FederationApi -- inter-server gossip, forward (single + batch)
382-- FederationPubkey -- exposes this server's federation Ed25519 public key
382-- FederationPubkey -- exposes this server's federation Ed25519 public key
383-- MaintenanceApi -- operator endpoints (prune, peer allow/block, admin)
383-- MaintenanceApi -- operator endpoints (prune, peer allow/block, admin)
@@ -393,7 +393,6 @@ route ClientApi where
393 POST {cursor: Timestamp, generation: Int} /poll headers {authorization: Text, clientIp: Text} -> {events: [PollEvent], cursor: Timestamp, authorized: Bool, rateLimited: Bool, generation: Int, heartbeat: Bool, retryAfterMs: Int, cursorReset: Bool, hasMore: Bool, cursorStale: Bool, loadPct: Int} = Poll
393 POST {cursor: Timestamp, generation: Int} /poll headers {authorization: Text, clientIp: Text} -> {events: [PollEvent], cursor: Timestamp, authorized: Bool, rateLimited: Bool, generation: Int, heartbeat: Bool, retryAfterMs: Int, cursorReset: Bool, hasMore: Bool, cursorStale: Bool, loadPct: Int} = Poll
394 POST {messages: [{to: PubkeyHex, encryptedBlob: BlobHex}]} /messages headers {authorization: Text, clientIp: Text} -> {results: [{messageId: MessageIdHex, error: Text}], error: Text, retryAfterMs: Int} = SendMessages
394 POST {messages: [{to: PubkeyHex, encryptedBlob: BlobHex}]} /messages headers {authorization: Text, clientIp: Text} -> {results: [{messageId: MessageIdHex, error: Text}], error: Text, retryAfterMs: Int} = SendMessages
395 POST {to: PubkeyHex, messageId: MessageIdHex} /messages/status headers {authorization: Text, clientIp: Text} -> {status: Text, authorized: Bool} = MessageStatus
395 POST {to: PubkeyHex, messageId: MessageIdHex} /messages/status headers {authorization: Text, clientIp: Text} -> {status: Text, authorized: Bool} = MessageStatus
396− POST {offset: Int} /messages/list headers {authorization: Text, clientIp: Text} -> {messages: [{messageId: MessageIdHex, receivedAt: Timestamp}], authorized: Bool, count: Int, error: Text} = ListMessages
397 GET /lookup/{pubkey: PubkeyHex} headers {authorization: Text, clientIp: Text} -> {server: ServerName, authorized: Bool, error: Text} = LookupPresence
396 GET /lookup/{pubkey: PubkeyHex} headers {authorization: Text, clientIp: Text} -> {server: ServerName, authorized: Bool, error: Text} = LookupPresence
398 POST {pubkeys: [PubkeyHex]} /lookup/batch headers {authorization: Text, clientIp: Text} -> {results: [{pubkey: PubkeyHex, server: ServerName}], authorized: Bool, error: Text} = BatchLookupPresence
397 POST {pubkeys: [PubkeyHex]} /lookup/batch headers {authorization: Text, clientIp: Text} -> {results: [{pubkey: PubkeyHex, server: ServerName}], authorized: Bool, error: Text} = BatchLookupPresence
@@ -1472,40 +1471,6 @@ handleMessageStatus = \to messageId authorization clientIp -> do
1472 else if any fwds blobMatches then "forwarding" else "unknown"
1471 else if any fwds blobMatches then "forwarding" else "unknown"
1473 yield {status: s, authorized: True {}})
1472 yield {status: s, authorized: True {}})
1475−-- ============================================
1477−-- ============================================
1478−-- Read-only mailbox peek by the recipient.
1480−-- POST /messages/list: paginated peek at the caller's mailbox without acking.
1481−-- Returns metadata only (no ciphertext) plus the total count.
1482−handleListMessages : Int -> Text -> Text -> IO {rw *globalRateCount, rw *globalRateWindowStart, r *sessions, clock} {authorized: Bool, count: Int, error: Text, messages: [{messageId: MessageIdHex, receivedAt: Int<Ms>}]}
1483−handleListMessages = \offset authorization clientIp -> do
1485− let mkErr = \e -> {
1487− authorized: False {},
1489− error: if e == "rate_limited" then e else ""
1491− gateAuth authorization clientIp t mkErr (\pubkey -> do
1493− rates <- *lookupRates
1494− let r = checkRate rates pubkey maxLookupRatePerKey lookupRateWindowMs t
1495− *lookupRates = r.entries
1498− then yield {messages: [], authorized: True {}, count: 0, error: "rate_limited"}
1501− let myMsgs = sortBy (\m -> m.receivedAt) (filter msgs (\m -> m.toKey == pubkey && t - m.receivedAt < messageMaxAge))
1502− let total = count myMsgs
1503− let page = pluck (takeRelation maxPollEvents (dropRelation (clamp 0 total offset) myMsgs)) (\m -> {
1504− messageId: messageIdOf m.encryptedBlob,
1507− yield {messages: page, authorized: True {}, count: total, error: ""})
1509-- ============================================
1474-- ============================================
1510-- Handler: federation receive
1475-- Handler: federation receive
1511-- ============================================
1476-- ============================================
@@ -2214,7 +2179,6 @@ api = serve Api where
2214 Poll = \{cursor, generation, authorization, clientIp} -> handlePoll cursor generation authorization clientIp
2179 Poll = \{cursor, generation, authorization, clientIp} -> handlePoll cursor generation authorization clientIp
2215 SendMessages = \{messages, authorization, clientIp} -> handleSendMessages messages authorization clientIp
2180 SendMessages = \{messages, authorization, clientIp} -> handleSendMessages messages authorization clientIp
2216 MessageStatus = \{to, messageId, authorization, clientIp} -> handleMessageStatus to messageId authorization clientIp
2181 MessageStatus = \{to, messageId, authorization, clientIp} -> handleMessageStatus to messageId authorization clientIp
2217− ListMessages = \{offset, authorization, clientIp} -> handleListMessages offset authorization clientIp
2218 LookupPresence = \{pubkey, authorization, clientIp} -> handleLookupPresence pubkey authorization clientIp
2182 LookupPresence = \{pubkey, authorization, clientIp} -> handleLookupPresence pubkey authorization clientIp
2219 BatchLookupPresence = \{pubkeys, authorization, clientIp} -> handleBatchLookupPresence pubkeys authorization clientIp
2183 BatchLookupPresence = \{pubkeys, authorization, clientIp} -> handleBatchLookupPresence pubkeys authorization clientIp
2220 RecvGossip = \{events, fromServer, requestTimestamp, authorization} -> handleRecvGossip events fromServer requestTimestamp authorization
2184 RecvGossip = \{events, fromServer, requestTimestamp, authorization} -> handleRecvGossip events fromServer requestTimestamp authorization