on master help sign in
Remove dead ack scaffolding: ackGracePeriod constant and stats fields
469939diko
server.knot+10−18
@@ -182,10 +182,6 @@ maxPollsPerKey = 3
182presenceRefreshInterval : Int<Ms>
182presenceRefreshInterval : Int<Ms>
183presenceRefreshInterval = 300000
183presenceRefreshInterval = 300000
184
184
185−-- Window during which a delivered-but-unacked message is retained for re-delivery (2 min).
186−ackGracePeriod : Int<Ms>
187−ackGracePeriod = 120000
188−
189-- Human-readable build identifier returned by the /health endpoint.
185-- Human-readable build identifier returned by the /health endpoint.
190serverBuildVersion : Text
186serverBuildVersion : Text
191serverBuildVersion = "2026-04-27-simple"
187serverBuildVersion = "2026-04-27-simple"
@@ -299,7 +295,7 @@ type ActivePoll = {pubkey: PubkeyHex, startedAt: Timestamp}
299-- ============================================
295-- ============================================
300-- Mutable refs (`*x`) holding the entire server state in memory. Every list
296-- Mutable refs (`*x`) holding the entire server state in memory. Every list
301-- scan is O(n); scaling is the first thing the production version replaces.
297-- scan is O(n); scaling is the first thing the production version replaces.
302−-- Grouped here: auth (challenges/sessions), mailbox (messages/forwards/acks),
298+-- Grouped here: auth (challenges/sessions), mailbox (messages/forwards),
303-- presence, rate-limit windows, and bookkeeping counters.
299-- presence, rate-limit windows, and bookkeeping counters.
304-- The Knot runtime persists every `*ref` declared here through restarts (see
300-- The Knot runtime persists every `*ref` declared here through restarts (see
305-- the project memory note about SQLite-backed refs); nothing in this file is
301-- the project memory note about SQLite-backed refs); nothing in this file is
@@ -381,8 +377,8 @@ type ActivePoll = {pubkey: PubkeyHex, startedAt: Timestamp}
381-- ============================================
377-- ============================================
382-- Public HTTP surface, grouped by audience:
378-- Public HTTP surface, grouped by audience:
383-- AuthApi -- challenge/verify (client identity)
379-- AuthApi -- challenge/verify (client identity)
384−-- ClientApi -- /poll, send/batch/list/ack/delete, presence lookup
380+-- ClientApi -- /poll, /messages (send), /messages/list, /messages/status, presence lookup
385−-- FederationApi -- inter-server gossip, forward, ack, delivery receipts
381+-- FederationApi -- inter-server gossip, forward (single + batch)
386-- FederationPubkey -- exposes this server's federation Ed25519 public key
382-- FederationPubkey -- exposes this server's federation Ed25519 public key
387-- MaintenanceApi -- operator endpoints (prune, peer allow/block, admin)
383-- MaintenanceApi -- operator endpoints (prune, peer allow/block, admin)
388-- HealthApi/StatsApi/MetricsApi/ReadyApi -- observability
384-- HealthApi/StatsApi/MetricsApi/ReadyApi -- observability
@@ -426,7 +422,7 @@ route MaintenanceApi where
426 POST {server: ServerName} /maintenance/unallow headers {authorization: Text} -> {ok: Bool, removed: Bool} = UnallowPeer
422 POST {server: ServerName} /maintenance/unallow headers {authorization: Text} -> {ok: Bool, removed: Bool} = UnallowPeer
427 GET /maintenance/allowed headers {authorization: Text} -> {peers: [Text], ok: Bool} = ListAllowed
423 GET /maintenance/allowed headers {authorization: Text} -> {peers: [Text], ok: Bool} = ListAllowed
428 POST /maintenance/reconcile headers {authorization: Text} -> {ok: Bool, messageDrift: Int, presenceDrift: Int, sessionDrift: Int} = Reconcile
424 POST /maintenance/reconcile headers {authorization: Text} -> {ok: Bool, messageDrift: Int, presenceDrift: Int, sessionDrift: Int} = Reconcile
429− GET /maintenance/config headers {authorization: Text} -> {ok: Bool, pollTimeoutMs: Int, ackGracePeriod: Int} = GetConfig
425+ GET /maintenance/config headers {authorization: Text} -> {ok: Bool, pollTimeoutMs: Int} = GetConfig
430
426
431route HealthApi where
427route HealthApi where
432 GET /health headers {authorization: Text} -> {ok: Bool, version: Text, pruneHealthy: Bool, heartbeatHealthy: Bool, capacityHealthy: Bool, capacityWarning: Bool, federationHealthy: Bool, watchdogHealthy: Bool, nonceHealthy: Bool, forwardQueueHealthy: Bool, requestPressureHealthy: Bool, degradedPeers: Int} = Health
428 GET /health headers {authorization: Text} -> {ok: Bool, version: Text, pruneHealthy: Bool, heartbeatHealthy: Bool, capacityHealthy: Bool, capacityWarning: Bool, federationHealthy: Bool, watchdogHealthy: Bool, nonceHealthy: Bool, forwardQueueHealthy: Bool, requestPressureHealthy: Bool, degradedPeers: Int} = Health
@@ -452,7 +448,7 @@ type KeyPresenceEntry = {server: Text, expiresAt: Timestamp}
452type RecipientCount = {pubkey: PubkeyHex, count: Int}
448type RecipientCount = {pubkey: PubkeyHex, count: Int}
453
449
454route StatsApi where
450route StatsApi where
455− GET /stats headers {authorization: Text} -> {ok: Bool, version: Text, capacity: {sessions: Int, sessionsMax: Int, sessionsPct: Int, messages: Int, totalMessageCount: Int, presence: Int, forwards: Int, challenges: Int, challengesMax: Int, forwardNonces: Int, forwardNoncesMax: Int, nonceEvictions: Int, activePolls: 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, 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, rejectedGlobal: Int, rejectedIp: Int, rejectedAuth: Int, rejectedPerKey: Int, rejectedFederation: Int}, timing: {pruneCount: Int, pruneRunning: Timestamp, heartbeatCount: Int, uptime: Int, lastPruneAt: Timestamp, lastHeartbeatAt: Timestamp, 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
451+ GET /stats headers {authorization: Text} -> {ok: Bool, version: Text, capacity: {sessions: Int, sessionsMax: Int, sessionsPct: Int, messages: Int, totalMessageCount: Int, presence: Int, forwards: Int, challenges: Int, challengesMax: Int, forwardNonces: Int, forwardNoncesMax: Int, nonceEvictions: Int, activePolls: 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, recentGossip: Int, blockedPeers: Int, 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, rejectedGlobal: Int, rejectedIp: Int, rejectedAuth: Int, rejectedPerKey: Int, rejectedFederation: Int}, timing: {pruneCount: Int, pruneRunning: Timestamp, heartbeatCount: Int, uptime: Int, lastPruneAt: Timestamp, lastHeartbeatAt: Timestamp, 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
456 GET /stats/peers headers {authorization: Text} -> {peers: [PeerStatus], ok: Bool} = StatsPeers
452 GET /stats/peers headers {authorization: Text} -> {peers: [PeerStatus], ok: Bool} = StatsPeers
457 GET /stats/key/{pubkey: PubkeyHex} headers {authorization: Text} -> {messages: Int, presence: [KeyPresenceEntry], sessions: Int, forwards: Int, forwardDetails: [{server: Text, retries: Int}], activePolls: Int, ok: Bool} = StatsKey
453 GET /stats/key/{pubkey: PubkeyHex} headers {authorization: Text} -> {messages: Int, presence: [KeyPresenceEntry], sessions: Int, forwards: Int, forwardDetails: [{server: Text, retries: Int}], activePolls: Int, ok: Bool} = StatsKey
458 POST {limit: Int} /stats/top-recipients headers {authorization: Text} -> {recipients: [RecipientCount], ok: Bool, total: Int} = TopRecipients
454 POST {limit: Int} /stats/top-recipients headers {authorization: Text} -> {recipients: [RecipientCount], ok: Bool, total: Int} = TopRecipients
@@ -1869,17 +1865,17 @@ handleFlushNonces = \authorization -> do
1869 yield {ok: authorized, flushedCount: 0}
1865 yield {ok: authorized, flushedCount: 0}
1870
1866
1871-- GET /maintenance/config: expose a small set of operationally-relevant
1867-- GET /maintenance/config: expose a small set of operationally-relevant
1872−-- compile-time constants (poll timeout, ack grace).
1868+-- compile-time constants (poll timeout).
1873−handleGetConfig : Text -> IO {r *runtimeMaintenanceSecret} {ackGracePeriod: Int, ok: Bool, pollTimeoutMs: Int}
1869+handleGetConfig : Text -> IO {r *runtimeMaintenanceSecret} {ok: Bool, pollTimeoutMs: Int}
1874handleGetConfig = \authorization -> do
1870handleGetConfig = \authorization -> do
1875 ok <- verifyMaintenanceAuth authorization
1871 ok <- verifyMaintenanceAuth authorization
1876 let v = \x -> if ok then x else 0
1872 let v = \x -> if ok then x else 0
1877− yield {ok, pollTimeoutMs: v pollTimeoutMs, ackGracePeriod: v ackGracePeriod}
1873+ yield {ok, pollTimeoutMs: v pollTimeoutMs}
1878
1874
1879-- GET /stats: capacity gauges + minimal federation/timing snapshot. Most of
1875-- GET /stats: capacity gauges + minimal federation/timing snapshot. Most of
1880-- the historical metric fields are zero-filled (see `zeroStats`) — only the
1876-- the historical metric fields are zero-filled (see `zeroStats`) — only the
1881-- counts that come straight from the state refs are populated.
1877-- counts that come straight from the state refs are populated.
1882−handleStats : Text -> IO {r *activePolls, r *blockedPeers, r *challenges, r *forwards, r *heartbeatCount, r *lastHeartbeatAt, r *lastPrunedAt, r *messages, r *presence, r *pruneCount, r *runtimeMaintenanceSecret, r *sessions, r *startedAt, clock} {capacity: {activePolls: Int, challenges: Int, challengesMax: Int, forwardNonces: Int, forwardNoncesMax: Int, forwards: Int, messageBucketMax: Int, messageBucketMin: Int, messages: Int, nonceEvictions: Int, presence: Int, recipientCounts: Int, sessions: Int, sessionsMax: Int, sessionsPct: Int, totalMessageCount: Int}, federation: {avgLatencyMs: Int, blockedPeers: Int, callCount: Int, 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, uniqueKeyIp: Int, verifyIp: Int}, timing: {activeRequests: Int, authAvgMs: Int, authCount: Int, authMaxMs: Int, 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}
1878+handleStats : Text -> IO {r *activePolls, r *blockedPeers, r *challenges, r *forwards, r *heartbeatCount, r *lastHeartbeatAt, r *lastPrunedAt, r *messages, r *presence, r *pruneCount, r *runtimeMaintenanceSecret, r *sessions, r *startedAt, clock} {capacity: {activePolls: Int, challenges: Int, challengesMax: Int, forwardNonces: Int, forwardNoncesMax: Int, forwards: Int, messageBucketMax: Int, messageBucketMin: Int, messages: Int, nonceEvictions: Int, presence: Int, recipientCounts: Int, sessions: Int, sessionsMax: Int, sessionsPct: Int, totalMessageCount: Int}, federation: {avgLatencyMs: Int, blockedPeers: Int, callCount: Int, 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, 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, recv: Int, rejectedAuth: Int, rejectedFederation: Int, rejectedGlobal: Int, rejectedIp: Int, rejectedPerKey: Int, send: Int, uniqueKeyIp: Int, verifyIp: Int}, timing: {activeRequests: Int, authAvgMs: Int, authCount: Int, authMaxMs: Int, 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}
1883handleStats = \authorization -> withMaintAuth authorization (yield zeroStats) (do
1879handleStats = \authorization -> withMaintAuth authorization (yield zeroStats) (do
1884 t <- now
1880 t <- now
1885 sa <- *startedAt
1881 sa <- *startedAt
@@ -1930,7 +1926,6 @@ handleStats = \authorization -> withMaintAuth authorization (yield zeroStats) (d
1930 verifyFailures: 0,
1926 verifyFailures: 0,
1931 forwardFailures: 0,
1927 forwardFailures: 0,
1932 oldestForwardAge: 0,
1928 oldestForwardAge: 0,
1933− oldestAckAge: 0,
1934 recentGossip: 0,
1929 recentGossip: 0,
1935 blockedPeers: count bp,
1930 blockedPeers: count bp,
1936 forwardServers: 0,
1931 forwardServers: 0,
@@ -1953,7 +1948,6 @@ handleStats = \authorization -> withMaintAuth authorization (yield zeroStats) (d
1953 gossipEvent: 0,
1948 gossipEvent: 0,
1954 verifyIp: 0,
1949 verifyIp: 0,
1955 uniqueKeyIp: 0,
1950 uniqueKeyIp: 0,
1956− pendingAckCleanups: 0,
1957 rejectedGlobal: 0,
1951 rejectedGlobal: 0,
1958 rejectedIp: 0,
1952 rejectedIp: 0,
1959 rejectedAuth: 0,
1953 rejectedAuth: 0,
@@ -1987,7 +1981,7 @@ handleStats = \authorization -> withMaintAuth authorization (yield zeroStats) (d
1987-- All-zero /stats payload returned to unauthorized callers. The shape is kept
1981-- All-zero /stats payload returned to unauthorized callers. The shape is kept
1988-- in lockstep with the response in `handleStats` so older operator tooling
1982-- in lockstep with the response in `handleStats` so older operator tooling
1989-- never sees a missing field.
1983-- never sees a missing field.
1990−zeroStats : {capacity: {activePolls: Int, challenges: Int, challengesMax: Int, forwardNonces: Int, forwardNoncesMax: Int, forwards: Int, messageBucketMax: Int, messageBucketMin: Int, messages: Int, nonceEvictions: Int, presence: Int, recipientCounts: Int, sessions: Int, sessionsMax: Int, sessionsPct: Int, totalMessageCount: Int}, federation: {avgLatencyMs: Int, blockedPeers: Int, callCount: Int, 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, uniqueKeyIp: Int, verifyIp: Int}, timing: {activeRequests: Int, authAvgMs: Int, authCount: Int, authMaxMs: Int, 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}
1984+zeroStats : {capacity: {activePolls: Int, challenges: Int, challengesMax: Int, forwardNonces: Int, forwardNoncesMax: Int, forwards: Int, messageBucketMax: Int, messageBucketMin: Int, messages: Int, nonceEvictions: Int, presence: Int, recipientCounts: Int, sessions: Int, sessionsMax: Int, sessionsPct: Int, totalMessageCount: Int}, federation: {avgLatencyMs: Int, blockedPeers: Int, callCount: Int, 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, 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, recv: Int, rejectedAuth: Int, rejectedFederation: Int, rejectedGlobal: Int, rejectedIp: Int, rejectedPerKey: Int, send: Int, uniqueKeyIp: Int, verifyIp: Int}, timing: {activeRequests: Int, authAvgMs: Int, authCount: Int, authMaxMs: Int, 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}
1991zeroStats = {
1985zeroStats = {
1992 ok: False {},
1986 ok: False {},
1993 version: serverBuildVersion,
1987 version: serverBuildVersion,
@@ -2020,7 +2014,6 @@ zeroStats = {
2020 verifyFailures: 0,
2014 verifyFailures: 0,
2021 forwardFailures: 0,
2015 forwardFailures: 0,
2022 oldestForwardAge: 0,
2016 oldestForwardAge: 0,
2023− oldestAckAge: 0,
2024 recentGossip: 0,
2017 recentGossip: 0,
2025 blockedPeers: 0,
2018 blockedPeers: 0,
2026 forwardServers: 0,
2019 forwardServers: 0,
@@ -2043,7 +2036,6 @@ zeroStats = {
2043 gossipEvent: 0,
2036 gossipEvent: 0,
2044 verifyIp: 0,
2037 verifyIp: 0,
2045 uniqueKeyIp: 0,
2038 uniqueKeyIp: 0,
2046− pendingAckCleanups: 0,
2047 rejectedGlobal: 0,
2039 rejectedGlobal: 0,
2048 rejectedIp: 0,
2040 rejectedIp: 0,
2049 rejectedAuth: 0,
2041 rejectedAuth: 0,