@@ -411,6 +411,20 @@ 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+
414428-- True when `s` refers to this server.
415429isLocalServer : Text -> Bool
416430isLocalServer = \s -> s == serverHost
@@ -1196,10 +1210,11 @@ handleRecvGossip = \events fromServer -> do
11961210 else if e.eventType == "offline"
11971211 then {acc.added, touched: union acc.touched [pubkey]}
11981212 else acc
1199− Nothing {} -> acc) {touched: [], added: []} events
1200− let kept = filter (\p -> p.server != fromServer || not elem p.pubkey collected.touched) pres
1213+ Nothing {} -> acc) {touched: ([] : [Text]), added: ([] : [PresenceEntry])} events
1214+ let kept = filter (\p -> p.server != fromServer || not textElem p.pubkey collected.touched) pres
12011215 *presence = union kept collected.added
1202− yield (filter (\pk -> not any (\p -> p.pubkey == pk && p.server == fromServer && p.expiresAt > t) pres) (map (\p -> p.pubkey) 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))
12031218 fork (forEach newlyOnline (\pk -> forwardQueuedToServer pk fromServer))
12041219 yield (Ok {value: {}})
12051220
@@ -1302,7 +1317,7 @@ backgroundRetryForwards = do
13021317-- Fork the background prune loop (which runs an immediate sweep on its
13031318-- first tick) and start the HTTP listener. The forward-retry loop only
13041319-- runs when federation is enabled.
1305−main : IO {network, console, clock, random} {}
1320+main : IO {network, console, clock, random, r *challenges, r *forwardFailures, r *forwards, r *messages, r *presence, r *seqCounter, r *sessions, w *challenges, w *forwardFailures, w *forwards, w *messages, w *presence, w *seqCounter, w *sessions} {}
13061321main = do
13071322 logInfo ("Skrepka relay server starting on " ++ bindHost ++ ":" ++ show listenPort ++ " ...")
13081323 logInfo (" federation: " ++ (if federationEnabled then "enabled" else "disabled"))