Stop reporting delivery we cannot evidence, and stop letting our own writes hide a dead link
Two defects in the Enclave bridge, measured 2026-08-04 against an independent observer on a healthy connection. They compose, so they are fixed together: the second supplies the signal the first needs.
BLOCKER 2 -- the idle clock was reset by OUTBOUND traffic.
enclave-conn-touch-activity! was called from the enclave-post* helpers as well as from the inbound handlers, and its docstring described that as the design: "any traffic on the wire -- in either direction -- resets the idle clock". A write succeeds into the local kernel send buffer and proves nothing whatever about the peer, so a bot posting more often than the 60s idle threshold kept resetting its own timer with writes that went nowhere, never PINGed, and never noticed the link was dead. The busier the bot, the blinder it was. Matched pair, same proxy, frozen at the same instant, differing only in whether they posted: the silent one hit its PONG timeout at t+96s and recovered, the chatty one never fired at all.
The clock is now inbound-only, and renamed to say so -- last-inbound-ts, enclave-conn-note-inbound! -- so that a future outbound caller reads as obviously wrong. The old name described the mechanism while its docstring described the bug as though it were intended. Seeding the clock at connect and after a successful reconnect stays: completing registration means the server sent us 001, which is genuine inbound evidence.
BLOCKER 1 -- send-channel reported success for messages that vanished.
With a socket frozen, 14 consecutive posts each returned "Broadcast to <channel> (1 line)" and none arrived. That is worse than a wedge: a wedge is visibly stuck, this lies, and it lies in the transport proposed for carrying worker status.
A post is now followed by a PING carrying a fresh random token, and the result string depends on whether that exact token comes back. TCP is ordered, so a PONG for a token sent AFTER the message means the peer's parser has already consumed the message. That is evidence, not a freshness heuristic -- a reading of the idle clock taken BEFORE the write says nothing about the write.
confirmed "Broadcast to #x (1 line)" (unchanged)
otherwise "Wrote to #x (1 line) - UNCONFIRMED: ..."Deliberately not the word "queued". Nothing is queued: no buffer holds the message and no retry will send it. A caller told its message was queued would wait instead of re-sending -- a fresh false claim in the opposite direction, which is the defect being removed, one layer up.
What this proves is receipt and parsing by the server, NOT fan-out to the channel or to any member. That is a large improvement over "we wrote to a socket" and it is still short of a delivery receipt; the code says so where somebody might over-read it.
Two structural guards, because the obvious implementation of blocker 1 reintroduces blocker 2 inside itself:
- Outstanding PINGs are a LIST of (token . sent-ts), and confirmation can only ADD. There is deliberately no "update the pending ping" operation. The PONG timeout is measured from the OLDEST entry, so a caller posting frequently can never postpone its own dead-link deadline. With a single re-stamped slot it could -- which is exactly blocker 2 moved one function over.
- A PING already outstanding past the budget settles the question, so confirmation answers immediately rather than sending another and spending the budget again. Only the first post in an outage waits.
Useful side effect: a post-time PING seeds the keepalive, so a busy bot now reaches its PONG timeout from the moment it posted into a dead link instead of never reaching it at all.
Tests: the inbound-only guard is aimed at the product, not at a mirror of it. enclave-post-multiline with no lines returns 'sent-empty without touching the irc connection, and the old code refreshed the clock above that check -- so the test fails against the pre-fix source and passes after.
src/apiary/enclave.sgl | 423 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
src/apiary/tools.sgl | 59 ++++++++++++++++++++----
test/test-enclave.sgl | 162 +++++++++++++++++++++++++++++++++++++++++++++++++----------------
test/test-tools.sgl | 25 ++++++++++
4 files changed, 534 insertions(+), 135 deletions(-)src/apiary/enclave.sglmodified
set-enclave-conn-caps-acked! enclave-conn-last-batch-fail set-enclave-conn-last-batch-fail! enclave-conn-last-activity-ts set-enclave-conn-last-activity-ts! enclave-conn-pending-ping-token set-enclave-conn-pending-ping-token! enclave-conn-pending-ping-sent-ts set-enclave-conn-pending-ping-sent-ts! enclave-conn-last-inbound-ts set-enclave-conn-last-inbound-ts! enclave-conn-pending-pings set-enclave-conn-pending-pings! enclave-conn-ping-add! enclave-conn-ping-ack! enclave-conn-ping-waiting? enclave-conn-pings-clear! enclave-conn-ping-outstanding? enclave-conn-oldest-ping-ts enclave-conn-reconnecting? set-enclave-conn-reconnecting?! enclave-conn-reconnect-attempt set-enclave-conn-reconnect-attempt! enclave-conn-shutdown? set-enclave-conn-shutdown?! enclave-conn-touch-activity! enclave-conn-note-inbound! enclave-conn-keepalive-tick! enclave-confirm-delivery! +post-confirm-timeout+ await-registration! enclave-connect enclave-disconnect (last-batch-fail default: #f mutable: #t) ;; Client-side PING/PONG keepalive bookkeeping, paired with ;; the enclave-server side in [[topics/enclave-deployment]]. ;; `last-activity-ts` is the epoch second of the most recent ;; outbound or inbound IRC traffic — refreshed on every send ;; and every receive. The keepalive tick uses ;; `(now - last-activity-ts) < idle-threshold` to decide ;; whether to PING. (last-activity-ts default: #f mutable: #t) ;; Pending client→server PING bookkeeping. `token` is what we ;; sent in the trailing param; the PONG handler clears both ;; slots on a token match. When `(now - sent-ts) > pong- ;; timeout` and the slots are still set, the connection is ;; presumed dead and the reconnect path fires. (pending-ping-token default: #f mutable: #t) (pending-ping-sent-ts default: #f mutable: #t) ;; ;; `last-inbound-ts` is the epoch second of the most recent ;; INBOUND IRC traffic. INBOUND ONLY — this is load-bearing ;; and it used to be wrong. ;; ;; An outbound write succeeds into the local kernel send ;; buffer and proves NOTHING about the peer: on a blackholed ;; route every send still "succeeds" while nothing arrives. ;; When outbound traffic refreshed this clock, a bot that ;; posted more often than `+keepalive-idle-threshold+` kept ;; resetting its own idle timer with writes that went nowhere ;; and so never PINGed and never detected the dead link. The ;; busier the bot, the blinder it was. Measured 2026-08-04 as ;; a matched pair: a silent bot hit its PONG timeout at t+96s ;; and recovered; a bot posting every 20s never fired at all. ;; ;; Only inbound bytes are evidence that the peer is alive. (last-inbound-ts default: #f mutable: #t) ;; Outstanding client→server PINGs as a list of ;; `(token . sent-ts)`, most recent first. The PONG handler ;; drops the entry whose token matches; the keepalive tick ;; measures its timeout from the OLDEST outstanding entry. ;; ;; A LIST rather than a single slot, deliberately: post-time ;; delivery confirmation (`enclave-confirm-delivery!`) also ;; emits PINGs, and if it could re-stamp an already-pending ;; one, a bot posting faster than `+keepalive-pong-timeout+` ;; would keep pushing its own deadline out — reintroducing ;; exactly the defect above in a new place. Entries can only ;; be ADDED, each keeps its own sent-ts, and the deadline is ;; always measured from the oldest, so that is structurally ;; impossible rather than merely avoided by care. (pending-pings default: '() mutable: #t) ;; Throttle the keepalive sweep — the event loop ticks every ;; 50ms but the keepalive logic only needs to run every few ;; seconds, so we record when we last ran the sweep and skip ;; connection is reaped + reconnected before the next send- ;; channel call sails into the bit-bucket. (define +keepalive-poll-interval+ 5.0) ;; sweep cadence (seconds) (define +keepalive-idle-threshold+ 60.0) ;; ping if no traffic in last N seconds (define +keepalive-idle-threshold+ 60.0) ;; ping if no INBOUND traffic in last N seconds (define +keepalive-pong-timeout+ 30.0) ;; reconnect if PONG hasn't arrived after N seconds ;; How long `enclave-confirm-delivery!` waits for the PONG that ;; proves the peer consumed the bytes we just wrote. ;; ;; MEASURED, not chosen. Against a local enclave-server the ;; observed confirmation latency is in `test/harness/` (see ;; `exp_confirm_latency.py`, which prints p50/p95/max over N ;; posts). The budget is set far above the measured worst case ;; on purpose: a false UNCONFIRMED on a merely slow link is ;; worse than a slow post, because the first person who sees a ;; spurious one starts ignoring the warning, and then the ;; signal this whole change exists to create is gone. ;; ;; It also stays well under `+keepalive-pong-timeout+` (30 s), ;; so a confirmation wait can never outlive the window in which ;; the keepalive would declare the same link dead. (define +post-confirm-timeout+ 3.0) ;; Poll step while waiting. Sleeping yields to the cooperative ;; scheduler so the event-loop goroutine can read the socket and ;; clear our token — VERIFIED by running it, not assumed. If ;; nothing else ran during this sleep, every post would cost the ;; full budget and confirm nothing. (define +post-confirm-poll+ 0.02) ;; Hard cap on outstanding PINGs so a burst of posts inside one ;; confirmation budget cannot grow the list without bound. (define +max-outstanding-pings+ 4) (define +reconnect-backoff-min+ 1.0) (define +reconnect-backoff-max+ 60.0) ;; sigil-irc completes its handshake (TCP connect → CAP LS → (: enclave-conn? -> boolean?) (irc-connected? (enclave-conn-irc conn))) ;;; Refresh the keepalive activity timestamp. Called from the ;;; PRIVMSG / BATCH / CAP / FAIL inbound handlers and from the ;;; outbound `enclave-post*` helpers, so any traffic on the ;;; wire — in either direction — resets the idle clock. (define (enclave-conn-touch-activity! conn) ;;; Record that INBOUND traffic just arrived, resetting the ;;; keepalive idle clock. ;;; ;;; Call this ONLY from inbound handlers, and only when bytes ;;; genuinely arrived from the peer. It must never be called ;;; from an outbound path: a successful write proves the local ;;; kernel accepted the bytes, not that anybody received them, ;;; and treating it as liveness evidence is what let a busy bot ;;; suppress its own dead-connection detection. The one other ;;; legitimate caller is a freshly established connection, ;;; where completing registration IS inbound evidence. ;;; ;;; Named for what it means rather than for what it touches, so ;;; that a future outbound caller reads as obviously wrong. The ;;; previous name (`touch-activity!`) described the mechanism ;;; and its docstring described the bug as though it were the ;;; design. (define (enclave-conn-note-inbound! conn) (: enclave-conn? -> any?) (set-enclave-conn-last-inbound-ts! conn (current-second))) ;; ---- outstanding PING bookkeeping --------------------------- ;; ;; Entries are `(token . sent-ts)`, most recent first. See the ;; `pending-pings` field comment for why this is a list. ;;; Record a PING we just put on the wire. ADD-only by design — ;;; there is deliberately no "update the pending ping" operation, ;;; because that is precisely how a frequent caller would push ;;; its own dead-link deadline out indefinitely. (define (enclave-conn-ping-add! conn token sent-ts) (: enclave-conn? string? number? -> any?) (set-enclave-conn-pending-pings! conn (cons (cons token sent-ts) (enclave-conn-pending-pings conn)))) ;;; #t when at least one PING is still unanswered. (define (enclave-conn-ping-outstanding? conn) (: enclave-conn? -> boolean?) (pair? (enclave-conn-pending-pings conn))) ;;; #t when THIS token specifically is still unanswered. (define (enclave-conn-ping-waiting? conn token) (: enclave-conn? string? -> boolean?) (and (assoc token (enclave-conn-pending-pings conn)) #t)) ;;; Epoch second of the OLDEST unanswered PING, or #f if none. ;;; The keepalive measures its PONG timeout from this, so a ;;; newer PING can never postpone an older one's deadline. (define (enclave-conn-oldest-ping-ts conn) (: enclave-conn? -> any?) (set-enclave-conn-last-activity-ts! conn (current-second))) (let loop ((entries (enclave-conn-pending-pings conn)) (oldest #f)) (cond ((null? entries) oldest) ((or (not oldest) (< (cdar entries) oldest)) (loop (cdr entries) (cdar entries))) (else (loop (cdr entries) oldest))))) ;;; Drop the entry matching `token`. Returns #t if one was ;;; removed, #f if the token was unknown (a stale or forged ;;; PONG, which must not clear anybody else's entry). (define (enclave-conn-ping-ack! conn token) (: enclave-conn? string? -> boolean?) (let ((entries (enclave-conn-pending-pings conn))) (cond ((not (assoc token entries)) #f) (else (set-enclave-conn-pending-pings! conn (filter (lambda (e) (not (equal? (car e) token))) entries)) #t)))) ;;; Forget every outstanding PING — used when the socket the ;;; PINGs were written to is being abandoned. (define (enclave-conn-pings-clear! conn) (: enclave-conn? -> any?) (set-enclave-conn-pending-pings! conn '())) ;; ============================================================ ;; Internal: PRIVMSG dispatcher + watch tables entry)))) (define (handle-incoming-batch conn msg) (enclave-conn-touch-activity! conn) (enclave-conn-note-inbound! conn) (let* ((params (irc-message-params msg)) (first (and (pair? params) (car params))) (rest (if (pair? params) (cdr params) '()))) (define (handle-incoming-privmsg conn msg) ;; Any inbound PRIVMSG (chat, services reply, react) means ;; the connection is alive — refresh the keepalive idle ;; clock before dispatch so a chatty conn never PINGs even ;; if the apiary itself isn't sending. (enclave-conn-touch-activity! conn) ;; clock before dispatch. (enclave-conn-note-inbound! conn) (let ((sender (or (irc-message-nick msg) "?")) (target (or (irc-message-target msg) "?")) (text (or (irc-message-text msg) "")) ;; ============================================================ (define (handle-cap-message conn msg) (enclave-conn-touch-activity! conn) (enclave-conn-note-inbound! conn) (let* ((params (irc-message-params msg)) ;; `:server CAP <client> <subcmd> :<caps>` (subcmd (and (pair? params) (pair? (cdr params)) ;; fall back to per-line PRIVMSG. Other FAIL kinds (PRIVMSG, ;; etc.) just log; the handler is intentionally narrow. (define (handle-fail-message conn msg) (enclave-conn-touch-activity! conn) (enclave-conn-note-inbound! conn) (let* ((params (irc-message-params msg)) (command (and (pair? params) (car params))) (code (and (pair? params) (pair? (cdr params)) ;;; trailing param back as `PONG :<token>`. The enclave-side ;;; keepalive treats this as proof the client is alive. (define (handle-server-ping conn msg) (enclave-conn-touch-activity! conn) (enclave-conn-note-inbound! conn) (let* ((params (irc-message-params msg)) (token (or (irc-message-trailing msg) (and (pair? params) (car params)) (log-debug "Apiary responded to server PING"))) ;;; Inbound `PONG :<token>` clearing our pending client-side ;;; PING. Token mismatch is logged and ignored — the activity ;;; touch above still happens (any inbound line counts as ;;; "still alive"), so a mismatch isn't fatal — it just means ;;; the prior PING is left pending until the next sweep. ;;; Inbound `PONG :<token>` clearing the matching outstanding ;;; PING. Token mismatch is logged and ignored — the inbound ;;; note above still happens (any inbound line counts as "still ;;; alive"), so a mismatch isn't fatal — it just means that PING ;;; stays outstanding until the next sweep. ;;; ;;; The match must stay EXACT: `enclave-confirm-delivery!` reads ;;; "my specific token was acked" as proof that the peer consumed ;;; the bytes written before it. A PONG that cleared some other ;;; token would turn that into a claim about a different write. (define (handle-server-pong conn msg) (enclave-conn-touch-activity! conn) (enclave-conn-note-inbound! conn) (let* ((params (irc-message-params msg)) (token (or (irc-message-trailing msg) ;; Some servers reply `PONG <server> :<token>` (cond ((null? (cdr params)) (car params)) (else (cadr params)))) "")) (pending (enclave-conn-pending-ping-token conn))) ""))) (cond ((and pending (string? token) (string=? pending token)) (set-enclave-conn-pending-ping-token! conn #f) (set-enclave-conn-pending-ping-sent-ts! conn #f) ((and (string? token) (enclave-conn-ping-ack! conn token)) (log-debug "Apiary keepalive PONG ack")) (else (log-debug "Apiary keepalive PONG stale-or-mismatched"))))) ;;; Single keepalive sweep over a single connection. Pulled ;;; out of the throttle wrapper so tests can drive it ;;; synchronously by setting `last-activity-ts` / ;;; `pending-ping-sent-ts` and calling this directly. ;;; synchronously by setting `last-inbound-ts` / adding a ;;; pending PING and calling this directly. (define (enclave-conn-keepalive-tick! conn now) (: enclave-conn? number? -> any?) (cond ((enclave-conn-shutdown? conn) #f) ((enclave-conn-reconnecting? conn) #f) (else (let ((pending (enclave-conn-pending-ping-token conn)) (pending-ts (enclave-conn-pending-ping-sent-ts conn)) (last-act (enclave-conn-last-activity-ts conn))) (let ((oldest-ping (enclave-conn-oldest-ping-ts conn)) (last-in (enclave-conn-last-inbound-ts conn))) (cond ;; PING in flight and the PONG window has expired. ;; A PING has been outstanding past the PONG window. ;; Measured from the OLDEST outstanding entry, so a ;; later PING (from a post-time delivery confirmation, ;; say) can never push this deadline out. ;; ;; Loud log + reconnect — the brief insists on INFO- ;; visible failure so an operator reading apiary's ;; log can correlate "send-channel didn't arrive" to ;; "the keepalive timed out N seconds before." ((and pending pending-ts (> (- now pending-ts) +keepalive-pong-timeout+)) ((and oldest-ping (> (- now oldest-ping) +keepalive-pong-timeout+)) (log-warn "Apiary keepalive PONG timeout — connection appears dead, reconnecting" sent-sec-ago: (number->string (- now pending-ts))) sent-sec-ago: (number->string (- now oldest-ping))) (trigger-reconnect! conn)) ;; No PING in flight and the activity clock has ;; crossed the idle threshold — emit a fresh PING. ((and (not pending) last-act (> (- now last-act) +keepalive-idle-threshold+)) ;; Nothing in flight and no INBOUND traffic for longer ;; than the idle threshold — emit a fresh PING. Note ;; this is deliberately blind to how much WE have sent: ;; our own writes say nothing about the peer. ((and (not oldest-ping) last-in (> (- now last-in) +keepalive-idle-threshold+)) (emit-keepalive-ping! conn now)) (else #f)))))) ;;; Send a client→server PING, stash the pending token, and ;;; refresh the activity clock so the next sweep doesn't ;;; re-PING immediately. (define (emit-keepalive-ping! conn now) ;;; Put a client→server PING on the wire and record it as ;;; outstanding. Returns the token on success, #f if the write ;;; itself failed (in which case a reconnect has been triggered). ;;; ;;; Deliberately does NOT refresh the inbound clock: we just ;;; WROTE, which is not evidence of anything. The reason the ;;; keepalive does not immediately re-PING is that the entry is ;;; now outstanding, and the idle branch requires an empty list. (define (send-ping! conn now) (: enclave-conn? number? -> any?) (let ((token (generate-keepalive-token)) (irc (enclave-conn-irc conn))) (set-enclave-conn-pending-ping-token! conn token) (set-enclave-conn-pending-ping-sent-ts! conn now) (irc (enclave-conn-irc conn)) (sent? #f)) ;; Record BEFORE the write. If the write half-succeeds and ;; then raises, the peer may still answer, and an entry we ;; never recorded could not be acked — leaving a PONG that ;; matches nothing and, worse, no deadline on the link. (enclave-conn-ping-add! conn token now) (guard (e (else (log-warn "Apiary keepalive PING send failed — triggering reconnect" error: (format "~a" e)) (enclave-conn-ping-ack! conn token) (trigger-reconnect! conn))) (irc-send-raw irc (string-append "PING :" token "\r\n")) (enclave-conn-touch-activity! conn)) (set! sent? #t)) ;; Token never logged at INFO — defense-in-depth so a ;; misbehaved peer reading log tail can't forge a PONG. (log-debug "Apiary keepalive PING out" token-prefix: (safe-token-prefix token)))) token-prefix: (safe-token-prefix token) outstanding: (number->string (length (enclave-conn-pending-pings conn)))) (and sent? token))) ;;; Keepalive's own PING. Thin wrapper so the sweep reads as ;;; intent rather than mechanism. (define (emit-keepalive-ping! conn now) (send-ping! conn now)) ;;; Trigger the reconnect path. Idempotent on consecutive ;; ONLY on success; preserve it across multiple kicks of ;; this function within a single outage so the backoff ;; correctly compounds. (set-enclave-conn-pending-ping-token! conn #f) (set-enclave-conn-pending-ping-sent-ts! conn #f) (enclave-conn-pings-clear! conn) ;; Best-effort close of the dead irc. If sigil-irc's ;; disconnect path raises (the socket is already half- ;; gone), swallow the exception — we don't want it to channel: ch) (irc-join new-irc ch))) channels)) ;; Refresh the activity clock so the keepalive doesn't ;; immediately re-PING the freshly-reconnected socket. (enclave-conn-touch-activity! conn) ;; Seed the inbound clock. This is NOT an outbound ;; touch in disguise: completing registration means ;; the server sent us 001, so inbound evidence ;; genuinely just arrived. Seeding it also stops the ;; keepalive re-PINGing a socket that is one second ;; old. (enclave-conn-note-inbound! conn) (connect-outcome kind: 'ok)))))))) ;; immediately PING (the 60s idle threshold runs ;; from "now" not from "1970"). (conn (enclave-conn irc: irc config: config last-activity-ts: (current-second)))) last-inbound-ts: (current-second)))) (install-handlers! conn) ;; Switch TLS to non-blocking so `tls-read` returns "" ;; immediately when no data is available — the polling (define (enclave-disconnect conn . quit-message) (: enclave-conn? string? ... -> void?) (set-enclave-conn-shutdown?! conn #t) (set-enclave-conn-pending-ping-token! conn #f) (set-enclave-conn-pending-ping-sent-ts! conn #f) (enclave-conn-pings-clear! conn) (let ((irc (enclave-conn-irc conn))) (when (and irc (irc-connection-socket irc)) (apply irc-disconnect irc quit-message)))) ;; Public: channel posting + watchers ;; ============================================================ ;;; Ask the peer to prove it consumed what we just wrote. ;;; ;;; Returns `'confirmed`, `'unconfirmed`, or `'not-ready`. ;;; ;;; WHY THIS WORKS, and what it does and does not prove. ;;; ;;; A successful `irc-send-raw` means the local kernel accepted ;;; the bytes. On a blackholed route every write "succeeds" ;;; forever while nothing arrives, which is how `send-channel` ;;; came to report `Broadcast to <channel> (1 line)` for 14 ;;; consecutive posts that were never delivered (measured ;;; 2026-08-04, against an independent observer on a healthy ;;; connection). ;;; ;;; So: write the message, THEN send a PING carrying a fresh ;;; random token, and wait for that exact token to come back. ;;; TCP is ordered, so a PONG for a token sent AFTER the message ;;; means the peer's parser has already consumed the message. ;;; The ordering is what makes this evidence rather than a ;;; freshness heuristic — a reading of the idle clock taken ;;; BEFORE the write says nothing whatsoever about the write. ;;; ;;; What it does NOT prove: that the server DELIVERED the message ;;; to the channel or to any member. It proves receipt and ;;; parsing, not fan-out. That is a large improvement over "we ;;; wrote to a socket" and it is still short of a delivery ;;; receipt; callers must not describe it as one. ;;; ;;; Two structural guards, both load-bearing: ;;; ;;; 1. This only ever ADDS to the outstanding-PING list. If it ;;; could re-stamp an existing entry, a caller posting more ;;; often than `+keepalive-pong-timeout+` would postpone its ;;; own dead-link deadline indefinitely — the exact defect ;;; this change removes from the idle clock, moved one ;;; function over. ;;; 2. If a PING has ALREADY been outstanding longer than the ;;; budget, the answer is known: there is no liveness ;;; evidence. Say so immediately rather than sending another ;;; PING and spending the budget again. That also bounds the ;;; list during a long outage — only the first post pays the ;;; wait. ;;; ;;; Useful side effect: a PING sent at post time seeds theShowing the first 500 of 601 diff lines for this file. This diff is INCOMPLETE; read the file or clone the repository for the rest.
src/apiary/tools.sglmodified
set-enclave-bridge-state-stop?! set-link-status! bridge-unavailable-message qualify-post-result enclave-bridge-start! enclave-bridge-run! (let* ((lines (apply-mention-to-first-line mention (split-lines text))) (outcome (enclave-post-multiline conn primary lines)) (style (post-outcome-label outcome (length lines)))) (style (post-outcome-label outcome (length lines))) ;; Ask the peer to prove it took those bytes BEFORE we ;; describe them as posted. See ;; `enclave-confirm-delivery!`. (confirmed (enclave-confirm-delivery! conn))) (cond ((and (string? mention) (> (string-length mention) 0)) (string-append "Posted to " primary " mentioning " mention " (" style ")")) (qualify-post-result confirmed (string-append "Posted to " primary " mentioning " mention " (" style ")") (string-append "Wrote to " primary " mentioning " mention " (" style ")"))) (else (string-append "Broadcast to " primary " (" style ")"))))) (qualify-post-result confirmed (string-append "Broadcast to " primary " (" style ")") (string-append "Wrote to " primary " (" style ")")))))) ;;; Choose between the confirmed and the unconfirmed rendering of ;;; a post result. ;;; ;;; The verbs are chosen with care and are not interchangeable. ;;; "Broadcast"/"Posted"/"DM to" are delivery claims and are only ;;; used when a PONG proved the peer consumed the bytes. Anything ;;; else says "Wrote", which is the whole of what is known. ;;; ;;; It deliberately does NOT say "queued". Nothing is queued: ;;; there is no buffer holding the message and no retry that will ;;; send it again. A caller told its message was queued would ;;; reasonably wait instead of repeating itself — a new false ;;; claim in the opposite direction, which is the same defect ;;; this change exists to remove. (define (qualify-post-result confirmed ok-text unconfirmed-text) (: symbol? string? string? -> string?) (case confirmed ((confirmed) ok-text) (else (string-append unconfirmed-text " — UNCONFIRMED: the server did not answer within " (number->string +post-confirm-timeout+) "s, so the link may be dead and this message may not have " "been delivered. Re-send once the bridge reports " "connected; nothing is holding a copy.")))) ;; Translate enclave-post-multiline's outcome symbol into a ;; tool-call status string. The string ships back to the agent (markdown->irc normalized))) (lines (split-lines formatted)) (outcome (enclave-post-multiline conn nick lines)) (style (post-outcome-label outcome (length lines)))) (string-append "DM to " nick ": " style)))))) (style (post-outcome-label outcome (length lines))) (confirmed (enclave-confirm-delivery! conn))) (qualify-post-result confirmed (string-append "DM to " nick ": " style) (string-append "Wrote DM to " nick ": " style))))))) ;;; Send a `+draft/react` reaction PRIVMSG to a target msgid. ;;; Returns a human-readable status string for the MCP caller.test/test-enclave.sglmodified
(test "fresh enclave-conn defaults" (let ((c (enclave-conn irc: #f config: (enclave-config)))) (assert-false (enclave-conn-last-activity-ts c)) (assert-false (enclave-conn-pending-ping-token c)) (assert-false (enclave-conn-pending-ping-sent-ts c)) (assert-false (enclave-conn-last-inbound-ts c)) (assert-false (enclave-conn-ping-outstanding? c)) (assert-false (enclave-conn-oldest-ping-ts c)) (assert-false (enclave-conn-reconnecting? c)) (assert-false (enclave-conn-shutdown? c)) (assert-equal 0 (enclave-conn-reconnect-attempt c)))) (test "enclave-conn-touch-activity! advances last-activity-ts" (test "enclave-conn-note-inbound! advances last-inbound-ts" (let ((c (enclave-conn irc: #f config: (enclave-config)))) (set-enclave-conn-last-activity-ts! c 100) (enclave-conn-touch-activity! c) (assert-true (> (enclave-conn-last-activity-ts c) 100)))) (set-enclave-conn-last-inbound-ts! c 100) (enclave-conn-note-inbound! c) (assert-true (> (enclave-conn-last-inbound-ts c) 100)))) ;; ---- the idle clock is INBOUND-ONLY ------------------------- ;; ;; This is the blocker-2 regression guard and it is deliberately ;; aimed at the PRODUCT, not at a mirror of it. ;; ;; `enclave-post-multiline` with no lines returns 'sent-empty ;; without touching the irc connection at all, so it runs fine on ;; a bare conn. The old code refreshed the activity clock at the ;; TOP of that procedure, before the empty check — so this test ;; FAILS against the pre-fix source and passes after. A test that ;; could not distinguish the two would be worth nothing here. (test "an outbound post does NOT refresh the idle clock" (let ((c (enclave-conn irc: #f config: (enclave-config)))) (set-enclave-conn-last-inbound-ts! c 100) (assert-equal 'sent-empty (enclave-post-multiline c "#chan" '())) (assert-equal 100 (enclave-conn-last-inbound-ts c)))) (test "keepalive-tick: idle conn under threshold does NOT PING" ;; No pending PING, but last activity is only 30s ago — the ;; idle threshold is 60s, so we should sit tight. ;; No outstanding PING, but inbound traffic was only 30s ago — ;; the idle threshold is 60s, so we should sit tight. (let ((c (enclave-conn irc: #f config: (enclave-config)))) (let* ((now 1000) (last-act (- now 30))) (set-enclave-conn-last-activity-ts! c last-act) (last-in (- now 30))) (set-enclave-conn-last-inbound-ts! c last-in) (enclave-conn-keepalive-tick! c now) ;; No PING was emitted (we have no irc, so an attempted ;; emit would have raised — the lack of a pending token ;; emit would have raised — the empty outstanding list ;; confirms emit was skipped). (assert-false (enclave-conn-pending-ping-token c)) (assert-false (enclave-conn-pending-ping-sent-ts c))))) (assert-false (enclave-conn-ping-outstanding? c))))) (test "keepalive-tick: an OUTSTANDING ping suppresses a new one" ;; Idle well past the threshold, but a PING is already in ;; flight and inside its PONG window. Emitting another would be ;; pointless; more importantly the tick must fall through to ;; neither branch rather than piling on. (let ((c (enclave-conn irc: #f config: (enclave-config)))) (set-enclave-conn-last-inbound-ts! c (- 1000 500)) (enclave-conn-ping-add! c "apk-inflight" (- 1000 5)) (enclave-conn-keepalive-tick! c 1000) (assert-equal 1 (length (enclave-conn-pending-pings c))) (assert-false (enclave-conn-reconnecting? c)))) (test "keepalive-tick: shutdown? short-circuits" (let ((c (enclave-conn irc: #f config: (enclave-config)))) (set-enclave-conn-shutdown?! c #t) ;; No irc, but shutdown? short-circuits before touching it. (enclave-conn-keepalive-tick! c 1000) (assert-false (enclave-conn-pending-ping-token c)))) (assert-false (enclave-conn-ping-outstanding? c)))) (test "keepalive-tick: reconnecting? short-circuits" (let ((c (enclave-conn irc: #f config: (enclave-config)))) (set-enclave-conn-reconnecting?! c #t) (enclave-conn-keepalive-tick! c 1000) (assert-false (enclave-conn-pending-ping-token c)))) (assert-false (enclave-conn-ping-outstanding? c)))) ;; ---- outstanding-PING bookkeeping --------------------------- (test "ping-ack! clears only the matching token" (let ((c (enclave-conn irc: #f config: (enclave-config)))) (enclave-conn-ping-add! c "apk-a" 100) (enclave-conn-ping-add! c "apk-b" 110) (assert-false (enclave-conn-ping-ack! c "apk-unknown")) (assert-equal 2 (length (enclave-conn-pending-pings c))) (assert-true (enclave-conn-ping-ack! c "apk-b")) (assert-equal 1 (length (enclave-conn-pending-pings c))) (assert-equal 100 (enclave-conn-oldest-ping-ts c)))) (test "oldest-ping-ts tracks the OLDEST, not the newest" ;; The guard that makes blocker 2 structurally unable to ;; reappear inside the delivery-confirmation path: a later PING ;; must never postpone an earlier one's deadline. (let ((c (enclave-conn irc: #f config: (enclave-config)))) (enclave-conn-ping-add! c "apk-old" 100) (enclave-conn-ping-add! c "apk-new" 900) (assert-equal 100 (enclave-conn-oldest-ping-ts c)))) (test "a NEWER ping cannot postpone an older ping's PONG timeout" ;; Same property, scored where it matters: through the tick. ;; The old entry is 31s old (past the 30s timeout); the new one ;; is 1s old. If the timeout were measured from the newest — ;; which is exactly what re-stamping a single slot would do — ;; this conn would look healthy forever under a steady stream ;; of posts. (with-async (let ((c (enclave-conn irc: #f config: (enclave-config)))) (set-enclave-conn-last-inbound-ts! c (- 1000 100)) (enclave-conn-ping-add! c "apk-old" (- 1000 31)) (enclave-conn-ping-add! c "apk-new" (- 1000 1)) (enclave-conn-keepalive-tick! c 1000) (assert-true (enclave-conn-reconnecting? c)) (assert-false (enclave-conn-ping-outstanding? c)) (set-enclave-conn-shutdown?! c #t)))) (test "keepalive-tick: pending-ping past timeout flips reconnecting?" ;; Simulate a PING sent 31s ago (>30s timeout). Shutdown? is (let ((c (enclave-conn irc: #f config: (enclave-config)))) (let* ((now 1000) (sent-ts (- now 31))) (set-enclave-conn-last-activity-ts! c (- now 100)) (set-enclave-conn-pending-ping-token! c "apk-test-token") (set-enclave-conn-pending-ping-sent-ts! c sent-ts) (set-enclave-conn-last-inbound-ts! c (- now 100)) (enclave-conn-ping-add! c "apk-test-token" sent-ts) (enclave-conn-keepalive-tick! c now) (assert-true (enclave-conn-reconnecting? c)) (assert-false (enclave-conn-pending-ping-token c)) (assert-false (enclave-conn-pending-ping-sent-ts c)) (assert-false (enclave-conn-ping-outstanding? c)) ;; Flag the conn as shutdown so the spawned reconnect ;; goroutine bails on its next sleep wakeup, otherwise ;; the with-async block waits forever for it. (set-enclave-conn-shutdown?! c #t))))) (test "generate-keepalive-token returns a non-empty unique string" ;; Two consecutive calls should produce different tokens — ;; jiffy + random combined gives well over 32 bits of entropy. (let ((t1 (begin (set-enclave-conn-pending-ping-token! (enclave-conn irc: #f config: (enclave-config)) "ignored") "first")) (a (number->string (current-jiffy)))) ;; Indirect: the public API doesn't expose generate-token, ;; but the post-tick token must include the "apk-" prefix. ;; Hard to test without an irc-connection in scope. Skip ;; this — covered by the smoke harness. (assert-true (string? a)) (assert-true (> (string-length a) 0)))) (test "shutdown? prevents reconnecting when set first" (let ((c (enclave-conn irc: #f config: (enclave-config)))) (set-enclave-conn-shutdown?! c #t) ;; Force a "PING timed out" condition into the conn but ;; with shutdown? already set — keepalive-tick should ;; observe shutdown? and skip the trigger entirely. (set-enclave-conn-pending-ping-token! c "stale-token") (set-enclave-conn-pending-ping-sent-ts! c (- 1000 100)) (enclave-conn-ping-add! c "stale-token" (- 1000 100)) (enclave-conn-keepalive-tick! c 1000) (assert-false (enclave-conn-reconnecting? c)))) ;; Indirectly assert via the threshold behavior. (let ((c (enclave-conn irc: #f config: (enclave-config)))) ;; 59s of idle should NOT trigger a PING. (set-enclave-conn-last-activity-ts! c (- 1000 59)) (set-enclave-conn-last-inbound-ts! c (- 1000 59)) (enclave-conn-keepalive-tick! c 1000) (assert-false (enclave-conn-pending-ping-token c))))) (assert-false (enclave-conn-ping-outstanding? c)))) ;; ---- delivery confirmation --------------------------------- (test "confirm-delivery! on a shut-down conn is 'not-ready" (let ((c (enclave-conn irc: #f config: (enclave-config)))) (set-enclave-conn-shutdown?! c #t) (assert-equal 'not-ready (enclave-confirm-delivery! c)))) (test "confirm-delivery! while reconnecting is 'unconfirmed" (let ((c (enclave-conn irc: #f config: (enclave-config)))) (set-enclave-conn-reconnecting?! c #t) (assert-equal 'unconfirmed (enclave-confirm-delivery! c)))) (test "confirm-delivery! answers immediately when a ping is already overdue" ;; Guard 2, scored through the real entry point. ;; ;; An outstanding PING older than the budget already settles the ;; question, so the call must return 'unconfirmed WITHOUT ;; sending another PING and without spending the budget again. ;; ;; This conn has irc: #f, which is what makes the test sharp: ;; if the guard were removed the call would fall through to the ;; ready? check and then to `send-ping!`, both of which reach ;; into a #f connection. The test would fail rather than pass ;; quietly. It also asserts the list did not grow, so a guard ;; that returned the right symbol after emitting a PING anyway ;; would still be caught. (let ((c (enclave-conn irc: #f config: (enclave-config)))) (enclave-conn-ping-add! c "apk-overdue" (- (current-second) (+ +post-confirm-timeout+ 1))) (assert-equal 'unconfirmed (enclave-confirm-delivery! c)) (assert-equal 1 (length (enclave-conn-pending-pings c))))));; ============================================================test/test-tools.sglmodified
'leader #f #f))) (assert-false result) (assert-equal 'terminal (enclave-bridge-state-link-state s))))));; ============================================================;; Post-result vocabulary;; ============================================================(test-group "post-result vocabulary" (test "qualify-post-result says nothing about delivery when unconfirmed" ;; The tool-facing vocabulary is part of the contract. The ;; confirmed rendering may claim delivery; the unconfirmed one ;; must not, and specifically must not say "queued" — nothing ;; holds a copy, so a caller told that would wait instead of ;; re-sending. (assert-equal "Broadcast to #chan (1 line)" (qualify-post-result 'confirmed "Broadcast to #chan (1 line)" "Wrote to #chan (1 line)")) (let ((out (qualify-post-result 'unconfirmed "Broadcast to #chan (1 line)" "Wrote to #chan (1 line)"))) (assert-true (string-contains? out "UNCONFIRMED")) (assert-true (string-contains? out "Wrote to #chan")) (assert-false (string-contains? out "Broadcast")) (assert-false (string-contains? out "ueued")))))