@@ -203,7 +203,7 @@ type PollEvent = {encryptedBlob: BlobHex}
203203-- kind still parses. `handleRecvGossip` ignores any event whose `eventType` it
204204-- doesn't recognize or that carries no pubkey — that is the forward-compat
205205-- fallback (the old `GossipUnknown` variant).
206−type GossipEvent = {eventType: Text, pubkey: Maybe PubkeyHex}
206+type GossipEvent = {eventType: Text, pubkey: Maybe Text}
207207
208208-- /messages is all-or-nothing per call: either every message in the batch is
209209-- enqueued (delivered, federated, or buffered for federation), or none are
@@ -239,9 +239,9 @@ type ServerName = Text where isValidServerName
239239type Timestamp = Int<Ms>
240240
241241-- Outstanding nonce issued by /auth/challenge; consumed by /auth/verify.
242−type Challenge = {pubkey: PubkeyHex, challenge: Text, expiresAt: Timestamp, ip: Text}
242+type Challenge = {pubkey: Text, challenge: Text, expiresAt: Timestamp, ip: Text}
243243-- An issued bearer token bound to (pubkey, ip, expiry).
244−type Session = {token: Text, pubkey: PubkeyHex, expiresAt: Timestamp, ip: Text}
244+type Session = {token: Text, pubkey: Text, expiresAt: Timestamp, ip: Text}
245245-- Mailbox row: a stored message awaiting recipient pickup or federation forward.
246246-- Storage TTL is fixed at `messageMaxAge`, so expiry is derived from
247247-- `receivedAt`. Any send time the sender wants to convey lives inside
@@ -250,14 +250,14 @@ type Session = {token: Text, pubkey: PubkeyHex, expiresAt: Timestamp, ip: Text}
250250-- presence re-announce of `toKey` doesn't trigger a duplicate delivery.
251251-- (Single-field record rows, not bare names: the Knot runtime cannot persist
252252-- a nested relation of scalars.)
253−type Message = {toKey: PubkeyHex, encryptedBlob: BlobHex, receivedAt: Timestamp, deliveredTo: [{server: ServerName}]}
253+type Message = {toKey: Text, encryptedBlob: BlobHex, receivedAt: Timestamp, deliveredTo: [{server: Text}]}
254254-- Last-known location of a pubkey, either local (`server == serverHost`) or
255255-- learned from a peer via gossip. Multiple entries per pubkey are allowed.
256−type PresenceEntry = {pubkey: PubkeyHex, server: ServerName, expiresAt: Timestamp}
256+type PresenceEntry = {pubkey: Text, server: Text, expiresAt: Timestamp}
257257-- A message queued for cross-server delivery to `toServer`. `retries` is
258258-- incremented by `retryPendingForwards` until `maxForwardRetries` is hit.
259259-- Identity is derived from `encryptedBlob` (see `Message`).
260−type ForwardEntry = {toServer: ServerName, toKey: PubkeyHex, encryptedBlob: BlobHex, retries: Int}
260+type ForwardEntry = {toServer: Text, toKey: Text, encryptedBlob: BlobHex, retries: Int}
261261-- Running failure counter for a peer; keyed by `server`. Drives `shouldRetryServer`.
262262-- `failedAt` is the start time of the last recorded attempt; `failures: 0` is
263263-- the post-success tombstone (no backoff, kept so the timestamp can outrank
@@ -411,20 +411,6 @@ stripTrailingDot = \s -> if hasSuffix "." s
411411then stripTrailingDot (take (length s - 1) s)
412412else s
413413
414−-- Compare two Text values for equality. Works around the refined-type
415−-- subtyping issue where comparing a PubkeyHex with a Text using ==
416−-- requires both sides to share the same type — this helper upcasts both
417−-- to Text before comparing.
418−textEq : Text -> Text -> Bool
419−textEq = \a b -> a == b
420−
421−-- Membership check that works across refined-type boundaries. `elem`
422−-- requires both the needle and the list elements to share a type, but
423−-- `PubkeyHex` and `Text` are related by subtyping, not equality. This
424−-- helper compares as `Text` on both sides.
425−textElem : Text -> [Text] -> Bool
426−textElem = \x xs -> any (\y -> x == y) xs
427−
428414-- True when `s` refers to this server.
429415isLocalServer : Text -> Bool
430416isLocalServer = \s -> s == serverHost
@@ -739,7 +725,7 @@ forwardMatches = \blob server f -> f.encryptedBlob == blob && f.toServer == serv
739725-- Replace any local presence rows for `pubkey` with one fresh row at
740726-- `expiry`, and return the post-update peer broadcast set. Caller wraps in
741727-- `atomic`.
742−refreshLocalPresence : PubkeyHex -> Timestamp -> IO {rw *presence} [Text]
728+refreshLocalPresence : Text -> Timestamp -> IO {rw *presence} [Text]
743729refreshLocalPresence = \pubkey expiry -> do
744730 pres <- *presence
745731 *presence = union (filter (\p -> not (p.pubkey == pubkey && isLocalServer p.server)) pres) [
@@ -757,7 +743,7 @@ refreshLocalPresence = \pubkey expiry -> do
757743-- One sweep over every state ref: drops expired rows in a single atomic block.
758744-- Returns the locally-expired presence rows whose pubkey has no other live
759745-- local session, so the caller can broadcast offline gossip for them.
760−pruneExpired : Int<Ms> -> IO {rw *challenges, rw *forwardFailures, rw *messages, rw *presence, rw *sessions} [PubkeyHex]
746+pruneExpired : Int<Ms> -> IO {rw *challenges, rw *forwardFailures, rw *messages, rw *presence, rw *sessions} [Text]
761747pruneExpired = \t -> atomic do
762748 ch <- *challenges
763749 *challenges = filter (\x -> x.expiresAt > t) ch
@@ -998,7 +984,7 @@ handlePoll = \cursor authorization clientIp -> do
998984-- the wait transaction reads `*messages` and `retry`s until a row matching
999985-- this pubkey/cursor is committed; the timer wins on idle. Either way we
1000986-- re-collect at the new wall time, so a timeout returns an empty page.
1001−doPoll : {clampedCursor: Timestamp, pubkey: PubkeyHex | a} -> Timestamp -> IO {r *messages, clock} {cursor: Timestamp, events: [{encryptedBlob: BlobHex}]}
987+doPoll : {clampedCursor: Timestamp, pubkey: Text | a} -> Timestamp -> IO {r *messages, clock} {cursor: Timestamp, events: [{encryptedBlob: BlobHex}]}
1002988doPoll = \s t -> do
1003989 let collectAt = \tnow -> atomic (do
1004990 msgs <- *messages
@@ -1200,21 +1186,20 @@ handleRecvGossip = \events fromServer -> do
12001186 -- works around with an explicit `isValidExactHex 64` check). Reject a
12011187 -- malformed key here so a peer can't poison `*presence` with non-hex
12021188 -- rows that then spawn bogus-`toKey` forward entries.
1203− Just {value: pubkey} -> if not isValidExactHex 64 pubkey
1189+ Just {value: pk} -> if not isValidExactHex 64 pk
12041190 then acc
12051191 else if e.eventType == "online"
12061192 then {
1207− touched: union acc.touched [pubkey],
1208− added: union acc.added [{pubkey, server: fromServer, expiresAt: t + onlineGossipTtl}]
1193+ touched: union acc.touched [pk],
1194+ added: union acc.added [{pubkey: pk, server: fromServer, expiresAt: t + onlineGossipTtl}]
12091195 }
12101196 else if e.eventType == "offline"
1211− then {acc.added, touched: union acc.touched [pubkey]}
1197+ then {acc.added, touched: union acc.touched [pk]}
12121198 else acc
12131199 Nothing {} -> acc) {touched: ([] : [Text]), added: ([] : [PresenceEntry])} events
1214− let kept = filter (\p -> p.server != fromServer || not textElem p.pubkey collected.touched) pres
1200+ let kept = filter (\p -> p.server != fromServer || not elem p.pubkey collected.touched) pres
12151201 *presence = union kept collected.added
1216− let addedKeys : [Text] = map (\p -> p.pubkey) collected.added
1217− yield (filter (\pk -> not any (\p -> textEq p.pubkey pk && p.server == fromServer && p.expiresAt > t) pres) addedKeys))
1202+ yield (filter (\pk -> not any (\p -> p.pubkey == pk && p.server == fromServer && p.expiresAt > t) pres) (map (\p -> p.pubkey) collected.added)))
12181203 fork (forEach newlyOnline (\pk -> forwardQueuedToServer pk fromServer))
12191204 yield (Ok {value: {}})
12201205