New head 0e5421b. The queue now matches Slides after Astra's rounds 3 and 4 on oficina/slides #20. Only a log pump writes to the logger, and logf never blocks. close never waits on the pump; the limit of a stalled sink is documented and asserted while the sink is still stalled. TestMain also fails the run if a queue worker or pump leaks. In addition, a refused Keymail sign-in (verified, but not admitted) now gets the door rendered in a 403 instead of rastrillo's bare text, and the door isn't reachable by typing ?err=refused. The OAuth integration test's refused case asserts this and failed before. go test ./internal/... and -race pass on the queue tests.
Send sign-in mail after the response, so its timing says nothing open
On branch fix/keymail-signin
This is part of the Keymail sign-in sweep (Paul, 2026-10-06). Docs already chooses Keymail or a link by the address's domain, and it already uses a continuation page for the Keymail hop, so it has neither of the two defects the other apps had.
It does have the timing oracle Astra found on oficina/slides #20. admissionMailer dropped refused addresses inside the request, so a permitted address waited for SMTP before its ?sent=1 while a refused one returned at once. On Slides that measured 252 ms against 0.35 ms, and forced posts showed the same split.
Change
rauth's Mailer is now mailQueue (internal/docs/mailqueue.go). It is the same queue as in Slides, Sheets, Calendar and Calls, and it includes Astra's round-2 fixes:
Sendqueues the link and returns at once, with no I/O at all. Drops are counted in an atomic and the worker logs them.- Every address's link is queued.
- One worker asks
listAdmits, which refuses on a failed grant lookup as before. Only then does it send. - Delivery failures are logged, never answered.
- A panic in the mailer is recovered per message.
Closedrains the queue first, while the database the allowlist reads is still open. It has a deadline, then cancels.
Tests
TestSigninNeverWaitsForTheMailerfails on origin/main: "POST map[address:[member@example.test]] waited on the mailer".- I put each defect back to show the queue tests catch it: -
TestMailQueueSendNeverBlocksWhenFull(log on the request path) -TestMailQueueSurvivesAPanic(panic) -TestMailQueueCloseIsBoundedAndStopsTheWorker(5.05 s close) TestMailQueueQueuesEveryAddressandTestAppCloseStopsTheMailWorkerare new.TestAdmissionMailerFailsClosednow drives the queue.TestSignInand the tests that read the recorder now wait for the queue first.
go vet and gofmt are clean. go test ./internal/... passes. On my Mac, cmd/docs TestReleaseBinaryReportsItsVersion fails with "bind: invalid argument" because the unix-socket path is too long. That is environmental and pre-existing; CI runs on Linux.
Still open in Docs: a non-admitted address that Keymail has verified still gets rastrillo's bare-text 403. The other branches render the door with ?err=refused (doorOnRefusal) until rastrillo has a hook. I left that out here and recorded it in the report.
VERDICT: CHANGES-REQUIRED — needs changes
Reviewer: GPT-6-Astra (Codex), reviewing for session 21a32889.
First whole-branch review of Docs' fix/keymail-signin at 0e5421b707f788b9112773994044facc8f094a68, against fetched origin/main / merge base d72b1d0839b6fa7a675c769c2fc4fc9c2261d5d2. Read AGENTS.md, the review brief and this discussion; compared the final queue with Slides main cda997f. Detached /tmp worktree; no source edits, commits, pushes, merges or deployments.
- P2 — Make the new timing regression hermetic.
internal/docs/signin_timing_test.go:53includes two unforced POSTs, butinternal/docs/mailqueue_test.go:272's new TestMain only checks goroutines and installs no DNS/HTTP stub. The overrides in authkeymailtest.go run solely inside TestKeymailOAuthActualIntegration's child process; they cannot protect this test. Consequently the first two POSTs query the real resolver for_keymail.example.testand probe HTTPS after NXDOMAIN. A targeted Make run withGODEBUG=netdns=go+2confirmedhostLookupOrder(example.test) = files,dns. The test's two-second timeout can therefore report “waited on the mailer” because discovery was slow, even while the mail queue is correct. Fix: install/restore the sibling apps' keymailtest stub in TestMain, or isolate this test with equivalent in-process discovery fixtures, while retaining both unforced and forced cases. Keep the worker/pump leak check. This violates the explicit hermetic-test requirement and needs fixing before merge.
The production changes reviewed cleanly: every address's link reaches the same nonblocking enqueue, the worker applies listAdmits and fails closed on grant-lookup errors, and callback/per-request admission remains in place. The queue matches Slides' reference after normalizing the app name. Saturation, panic recovery, cooperative cancellation and the stalled-log-pump limitation are tested. Close drains before closing the database, and constructor failure closes the new queue.
The previous bare-text admission refusal now renders the sign-in door inside its 403. The interceptor requires both the exact admission sentence and 403, preserves cookies, and sits inside LocalGuard. A typed ?err=refused cannot assert verified refusal. Existing OAuth continuation and explicit force fallback are retained. Keymail's own per-address federation lookup is permitted, as Paul clarified during this review. No migrations or unrelated-main reversions were found.
Validation, all with GOTOOLCHAIN=go1.26.5 and invoked through Make:
- Docs has no test/ci Make target (only release). A supplemental Makefile supplied on stdin invoked the repository's
.amadan/ci.d/10-gateand its Go checks without editing the repository. - The gate completed fixture JS tests, bundle freshness, Rust/wasm checks and wire compatibility. Its Go run failed only at the reported macOS TestReleaseBinaryReportsItsVersion socket-path limit; all internal Go packages passed. A separate full Go run showed the same result.
- Go vet (including browser-tagged Docs tests), formatting and build: PASS.
- Targeted auth/queue/timing/admission/OAuth race checks: PASS (17.949 s), including worker/pump leak checks.
- Re-ran TestReleaseBinaryReportsItsVersion through Make with
TMPDIR=/tmp: PASS (1.21 s), confirming the environment cause. - The wasm32 target was available in this run; it did not block the gate.
- Amadan CI reports passed at 0e5421b (exit 0).
Existing regression tests were read and run; no mutation testing or deployed-canary/Safari validation under this review-only instruction. Logs: /tmp/21a32889-docs-{gate,dns,version-shorttmp}.log.
New head 7144e410f68add1a40afc832f9c8e4416940d168 fixes Astra's finding (the timing test lacked hermetic discovery stubs).
- keymailtest.
internal/docs/keymailtestis Slides'/Calendar's stub. The TestMain inmailqueue_test.goinstalls it for the wholeinternal/docsbinary and restores it afterwards. TestMain still fails the run on a leaked worker or log pump.TestSigninNeverWaitsForTheMailerkeeps its two unforced and two forced POSTs; the unforced ones now get NXDOMAIN in-process. - Change from Slides' copy. With the stub's wrapper type in place,
TestTeamConfigPinsAndColdMarkerpanicked.home/roster.Newcloneshttp.DefaultTransport.(*http.Transport)when a team runtime has no transport of its own. So in Docs,Installinstalls an*http.Transportinstead: an HTTP/1-only clone with the fixture router registered for"https"viaRegisterProtocol. Unregistered hosts returnErrSkipAltProtocoland are dialled as usual, so the loopback resolver makes names unreachable. HTTP/1-only is needed because the bundled HTTP/2 otherwise claims thehttpsslot on first use. The package comment explains this. - Test. New
TestKeymailDiscoveryStaysInProcessrequiresexample.com, which public DNS resolves, to come back NXDOMAIN. It failed before TestMain installed the stub.
The repo gate .amadan/ci.d/10-gate passes locally with GOTOOLCHAIN=go1.26.5 TMPDIR=/tmp. Without the short TMPDIR, the only failure is the known macOS socket-path one, TestReleaseBinaryReportsItsVersion. Hub CI passed at 7144e41.
VERDICT: APPROVE — ready to merge
Reviewer: GPT-6-Astra (Codex), reviewing for session 21a32889.
Whole-branch review of fix/keymail-signin at 7144e410f68add1a40afc832f9c8e4416940d168, against fetched origin/main / merge base d72b1d0839b6fa7a675c769c2fc4fc9c2261d5d2. Read AGENTS.md, the review brief and this discussion. Reviewed in a detached /tmp worktree; no source edits, commits, pushes, merges or deployments.
- No P0, P1 or P2 findings.
The previous P2 is resolved. TestMain now installs and restores the discovery stub for the entire internal/docs test binary while retaining the worker/pump leak check. The timing regression still exercises both unforced and forced submissions. The new resolver regression requires NXDOMAIN for example.com.
The transport difference from Slides is appropriate here: the stub preserves the concrete *http.Transport required by Home roster's Clone call, explicitly selects HTTP/1 so automatic HTTP/2 setup cannot take the registered HTTPS protocol slot, and routes registered hosts to in-process fixtures. Unregistered hostnames use the replacement resolver; loopback httptest servers remain usable. I read the actual OAuth integration child's separate overrides too; the new TestMain does not remove that coverage. The complete gate passes the team configuration/roster test as well as OAuth and timing tests.
The full production diff also reviews cleanly. Calendar's and Docs' queues are identical after normalizing app names. Delivery admission and SMTP are off the response path; failed grant lookups suppress mail. Verification and per-request admission remain authoritative. Close drains before closing the database. The refusal interceptor requires the exact 403/body and renders the door with a request-context marker, so typing ?err=refused cannot claim a verified refusal. Existing continuation, explicit email fallback and Home guards remain in place. No migrations, dependency changes or unrelated-main reversions.
Validation: GOTOOLCHAIN=go1.26.5 TMPDIR=/tmp .amadan/ci.d/10-gate PASS, including fixture JavaScript tests, bundle freshness, Rust/wasm, wire compatibility, Go vet (including browser-tagged tests), all Go tests, formatting and build. Amadan CI passed at 7144e41. Existing regression tests were read and run; no source mutations or deployed/Safari validation in this review. Log: /tmp/review-21a32889-docs-gate.log.