Commit21ffda50Recorded6 Jul 2026Repositorycourier
Simplify dedup: use stdlib assoc/filter, rename state file
Message
Post-review cleanup (no behavior change): replace the hand-rolled has-key?/remove-key/dedup-prune recursions with (sigil core) assoc and filter, and skip the redundant filter on check-and-record!'s hot 'proceed path (a freshly-pruned non-dup list can't contain the key). Rename the persisted file send-dedup.log -> send-dedup.state since it is rewritten in place, not appended like courier.log.
Full suite 69 pass; regression harness still shows fixed=1 vs control=4.
Changed
repro/send_driver.py | 2 +-
src/courier/dedup.sgl | 42 +++++++++++++++++++-----------------------
2 files changed, 20 insertions(+), 24 deletions(-)Diff
repro/send_driver.pymodified
@@ -156,7 +156,7 @@ def main():
156
# Sends ENABLED against the mock (this is the whole point). 157
env.pop("COURIER_DISABLE_TELEGRAM_SEND", None) 158
env["CLAUDE_OPS_LOG_DIR"] = args.logdir−159
# Isolate persistent state (relay dir + the send-dedup.log that the+159
# Isolate persistent state (relay dir + the send-dedup.state that the 160
# fix writes beside it) to this run's logdir, so runs don't cross- 161
# contaminate. Every restarted courier gen shares this dir -- exactly 162
# how the real leader's restarts share ~/.courier -- so the persistentsrc/courier/dedup.sglmodified
@@ -63,7 +63,9 @@
63
(let ((override (getenv "COURIER_SEND_DEDUP_FILE"))) 64
(if (and override (not (string=? override ""))) 65
override−66
(path-join (path-dirname (default-relay-dir)) "send-dedup.log"))))+66
;; ".state", not ".log": this is a rewritten-in-place state set,+67
;; not an append log like courier.log.+68
(path-join (path-dirname (default-relay-dir)) "send-dedup.state")))) 69
70
;; ---- FNV-1a 64-bit key hashing (no plaintext on disk) ---- 71
(define *fnv-offset* 14695981039346656037)@@ -118,25 +120,11 @@
120
;; Drop entries older than the window. 121
(define (dedup-prune entries now window) 122
(: list? number? number? -> list?)−121
(cond−122
((null? entries) '())−123
((< (- now (cdr (car entries))) window)−124
(cons (car entries) (dedup-prune (cdr entries) now window)))−125
(else (dedup-prune (cdr entries) now window))))+123
(filter (lambda (e) (< (- now (cdr e)) window)) entries)) 124
−127
;; Remove any entry with the given key.−128
(define (remove-key entries key)−129
(cond−130
((null? entries) '())−131
((string=? (car (car entries)) key)−132
(remove-key (cdr entries) key))−133
(else (cons (car entries) (remove-key (cdr entries) key)))))−134
−135
(define (has-key? entries key)−136
(cond−137
((null? entries) #f)−138
((string=? (car (car entries)) key) #t)−139
(else (has-key? (cdr entries) key))))+125
;; All entries except the one with `key`.+126
(define (without-key entries key)+127
(filter (lambda (e) (not (string=? (car e) key))) entries)) 128
129
(define (save-entries! path entries) 130
(ensure-directory (path-dirname path))@@ -157,9 +145,17 @@
145
(define (dedup-check-and-record! path key now window) 146
(: string? string? number? number? -> symbol?) 147
(let* ((entries (dedup-prune (dedup-load-entries path) now window))−160
(dup (has-key? entries key)))−161
(save-entries! path (cons (cons key now) (remove-key entries key)))−162
(if dup 'suppress 'proceed)))+148
(dup (assoc key entries)))+149
(if dup+150
;; Refresh the timestamp (sliding window) so an ongoing retry+151
;; loop stays suppressed as long as it keeps firing.+152
(begin (save-entries! path+153
(cons (cons key now) (without-key entries key)))+154
'suppress)+155
;; New key: record it (a freshly-pruned non-dup list cannot+156
;; already contain it, so no removal needed) BEFORE delivery.+157
(begin (save-entries! path (cons (cons key now) entries))+158
'proceed)))) 159
160
;;; Remove a previously-recorded key. Called when the caller learns 161
;;; the send definitely did NOT reach Telegram, so a genuine retry is@@ -168,4 +164,4 @@
164
(: string? string? -> void?) 165
(when (file-exists? path) 166
(guard (e (else (values)))−171
(save-entries! path (remove-key (dedup-load-entries path) key)))))))+167
(save-entries! path (without-key (dedup-load-entries path) key)))))))