6.0 KiB
6.0 KiB
TODO — migadu_counters
State as of 2026-10-15. Review findings refer to review/README.md; the proof tests live in
review/_proof_tests/ and should become regression tests as each item is fixed (round 1 and items
7–8 are mapped at the end of review/_proof_tests/README.md).
Done
- Review round 1: #1–#6 (queue + 202, lenient validation + IDN, token logins, INCR with per-key
results and master re-read, CDB parse cache, mandatory token / port 5555), plus
contrib/pull-domains.shandcounterctl --in/--out. - #7 plus-addressing counts against the mailbox (
domains.Mailbox). - #8 malformed CDB row is "not hosted" + warning (
domains.ErrMalformed).
Review round 2 — remaining findings
Suggested order: #10, #15, #9, then the rest in one sweep.
Can stop the daemon or lose counts
- #10 SIGHUP during startup kills the daemon. The signal handler is installed after the Redis
ping (up to 2 s). Install
signal.Notifybefore anything slow incmd/migadu_counters/main.go(run), and queue a reload that arrives before the app is ready. Evidence:review/_proof_tests/a/run/. - #15 Sentinel mode starts without a Redis password. Every write fails; only
/healthand metrics show it. Make an emptyredis.passwordfatal inconfig.Validatewhensentinelsis set (allow an explicit opt-out such aspassword_optional = truefor dev). Proof:b/internal/config/review_b_test.go, case "sentinel without password". - Persistent / recoverable counting on Redis outage (deferred from round 1, #4). A Redis stall
longer than a few seconds fills the queue and new requests are dropped (503,
dropped_total). Options: marker key per transaction for exact lost-reply recovery; a disk-backed queue. Decide whether the drop-and-alert behaviour is acceptable before building anything.
Correctness
- #9 Notification retries can send duplicate customer emails. The web app's mailbox-level
handler (
core/email.ex:3572-3584) emails on every call; the org handler is guarded once per day. Ininternal/notify: treat a slow-but-2xx attempt as success (do not retry after the client timeout if the request was sent — or make the retry budget shorter than the web app's handling time), do not keep the concurrency slot while backing off, let Shutdown finish pending retries up to its deadline, and fix the "after N attempts" count in the failure line. - #12 Override keys are not lowercased.
Info@/o=5would be ignored and the domain default used instead. Lowercase the key indomains.ParseLimits; add a test row. No such keys in today's data. - #16 counterctl warns one day too early. A date exactly 31 days back is reported as expired.
Compare against the key TTL in whole days (
now.UTCday minus date > 31). Proof:b/cmd/counterctl/review_b_test.goTestReviewB_31DayWarningOffByOne. - #18 Work keeps running after a handler timeout. Not reproduced by the reviewer and largely
covered by the queue change (the synchronous part only validates). Re-check that
http.TimeoutHandlercannot leave a goroutine writing after the 503, then close.
Operability / security hygiene
- #11 Rejected requests leave no trace. 4xx rejections write nothing to the counting log and no
trace_idto the app log. Decodetrace_idbest-effort before rejecting; add arejected: <status> <reason>line to the counting log andtrace_idto the warn line. Proof:b/internal/server/review_b_test.goTestReviewB_RejectLogHasNoTraceID. - #13 Credentials in the notify URL are written to the log. Redact
user:pass@from URLs in the txlogsent/failed notificationlines; warn at startup when a notify URL is plainhttp://with basic auth. Proof:b/internal/server/review_b3_test.go. - #14 Error responses expose internals. 503 bodies carry Redis addresses and file paths; keep
the detail in the log and answer a generic message. Default
admin_addrto127.0.0.1:9095(currently:9095, all interfaces, no auth). Proof:TestReviewB_ErrorLeak. - #17 A recovered panic is not counted in
requests_total. The recoverer sits outsideobserve; swap the order or record the 500 explicitly. Proof:TestReviewB_PanicNotInMetrics. - Test gaps (B F13). Watcher tests can occasionally pass on a stale signal — make them wait for
the specific reload they triggered. Add a test for
handler_timeoutvs queue timeout interplay.
Outside this repo (deployment prerequisites)
- mailqueuer client:
[counters]section and a fire-and-forget POST/in|/outper recipient inside the publish loop ofmailqueuer/internal/handler/ingest.go(after a successful publish;/outwhenX-Auth-Useris set). Reply is202 {"accepted":true}; counted values only in the counting log andGET /counters. - Ansible role in
ansible-freebsd3, cloned fromroles/rcptd: user/group,config.toml.j2(auth_tokenis mandatory, port 5555), rc.d script underdaemon(8), newsyslog entries forapp.log,counting.log,pull.log, templatedcontrib/pull-domains.sh+contrib/migadu_counters-pull.cron(seecontrib/README.md), agent host key acceptance, firewall: mailqueuer hosts → :5555, service → Sentinel :26379/:6379 and admin.migadu.com:443. - migadu_web
/api/limitsDbranch (domain level); until thennotify.domain_urlstays empty. - Limits re-tuning: outgoing is counted per recipient (Lua counted per message), so existing
*/olimits are stricter. Review before relying onoutgoing_limit_exceeded!. - Retire stats-merger's quota module once this service is the source (only fernrohr reads it).
- Dependencies:
golang.org/x/netis pinned to v0.43.0 because the offline module cache had it; rungo get -u golang.org/x/net && go mod tidywith network access. - git init the repo (never asked for so far).