VERDICT: CHANGES-REQUIRED
Reviewer: Astra (GPT-6-Astra via Codex), posting as the configured agent account. Whole-branch review of fix/keymail-signin at c066739ceede83011758451cff9dac4de420f895, against fetched origin/main / merge base 65fdc072c3a06d70b8380be3c0100582ca68728f. Read AGENTS.md, the audit briefs, this discussion, and Slides #20 first. Review only; no branch edits, commits, pushes, merges, or deployments.
- P1 — The guest-budget path still exposes membership before verification.
internal/calls/auth.go:148–160, reached synchronously fromgateBegin:97. With a live invitation cookie, members skip outsider budgets and write a pending row; an outsider over budget instead logs a synchronous warning and returns early. Moving SMTP into the queue does not hide this admission-dependent work. I created a real live meeting/link and signed join cookie, exhausted the five-attempt link budget, then alternated member/outsider POSTs with force=1. With a controlled logger taking 75 ms for the budget warning, empty-queue pairs measured 0.381/77.647 ms and 0.578/77.852 ms; with the worker blocked and all 64 waiting slots occupied, 0.499/77.784 ms and 0.647/76.925 ms. All were303 /signin?sent=1. The probe needs possession of a live invitation; it is not a claim about actual deployed logger latency. A blocked sink can hold the outsider request indefinitely, just as in Slides' earlier overflow bug. Fix: remove admission-dependent logging and database work from the response path. Make preparatory live-join/budget work equivalent across candidate addresses, carry the necessary proof/decision forward, and enforce membership/guest admission after verification and in the mail worker. Preserve guest budgets and avoid creating a callback-vs-worker race. Add empty/full-queue HTTP tests with a live join cookie and exhausted budgets. This is an existing gate weakness left unresolved by the branch's explicit no-oracle guarantee, not a claim that SMTP is still synchronous.
- P2 — Shutdown can block forever in its timeout log.
internal/calls/signin_mail.go:189(also:139). A stalled sink holds the worker in reportDrops; after the drain/grace timers expire, close synchronously logs to that same sink. Neither call observes cancellation. With the branch's stuckHandler, one counted drop and drainTimeout reduced to 10 ms, close and the worker remained blocked beyond the five-second grace plus 300 ms; they returned only after releasing the sink. No uncooperative sender was involved. Fix: use bounded nonblocking diagnostic reporting, including the deadline path, and ensure a stalled sink cannot retain the mail worker. The regression must keep the logger stalled through the shutdown check; the current saturation test releases it before close.
- P2 — The admission-refusal page can be forged by URL.
internal/calls/auth.go:319–322. A fresh unauthenticated GET/signin?err=refuseddisplays “That address can’t sign in to Calls” without verification or authenticated refusal state. The Home-local renderer does the same. My fresh-client probe reproduced it. It grants no access and performs no membership lookup, but fails Paul's explicit requirement that err=refused not be spoofable; the earlier Slides presentation limitation is not sufficient for this brief. Fix: render directly at the verified callback or require a short-lived authenticated browser-bound refusal marker; do not let the bare query flag assert verified refusal.
Other requested checks:
- Without a join cookie, the controlled 250 ms SMTP matrix passes: empty-queue allowed/denied times 1.938/0.743 ms without force, 0.201/0.708 ms with force; full-queue times 0.198/0.158 ms and 0.166/0.152 ms. Status, all headers, cookies, body and sent location matched. Saturation was established through the queue seam, and requests ran through the real Home-mode router/auth stack. This does not clear finding 1's distinct invitation path.
- Keymail: permitted/denied addresses get matching 200 continuation pages in both modes. Additional probes compared all headers and cookie attributes after masking randomized cookie values, and matched the five-attempt budget and sixth-attempt rate response. A real invalid-code exchange returned
?force=1&err=keymailand rendered force on the retry form. The ordinary form does not force email. - Destination: both modes invoke connect.OAuthContinuation/keymailDestination. Invalid HTTP, path, duplicate clientid, fragment, userinfo and challenge-method probes returned 502 with no pending-cookie leak. The validator checks app-bound clientid/redirect_uri, identify scope, state and challenge. It trusts the hostname selected through Keymail's existing domain-federation model; it does not pin every destination to keymail.dev. No new free-form return-URL redirect was found.
- Refusal interception: only the exact admission 403 sentence is converted. Probes preserved CSRF-style failures, address-mismatch 403, other statuses and cookies. Outer CSRF and Home LocalGuard protections remain outside the wrapper.
- Home: a linked Home member reauthenticated through Keymail, passed FreshLocal and returned to the linking page. Missing/stale Keymail auth_time and Home-as-local proof failed freshness checks. Existing revoked-member, stale-session, Home identity and native Home-session tests passed.
- Guests: an additional complete Keymail guest flow, carrying a real live join cookie, verified the outsider, traversed LocalComplete and /continue, and reached the correct
/m/<sqid>/joinholding screen. It still denied the guest/account/home. Existing replaced-link and expired-pending tests passed with the refusal door assertions. - room.js: readRefusal still recognizes redirects to another origin/path, 401/403 and unexpected HTML; refused still enters the terminal signed-out state. The door wrapper affects callback/verify only, not room stream endpoints.
- Native: callscore.SignOut still accepts standalone 303 and Home 307 using the client that does not follow the identity-provider redirect. Its tests and the native Home-session route tests passed in make ci. No native UI code changed.
- Queue: saturation, per-job sender panic recovery, admission-panic recovery and cooperative shutdown tests pass; finding 2 is the stalled-log exception. No migrations, dependency changes or unrelated-main reversion were found.
Validation:
GOTOOLCHAIN=go1.26.5 make ciPASSED.GOTOOLCHAIN=go1.26.5 make browser, RASTRILLO_CHROME set to Playwright Chromium 1234, fake camera/microphone enabled by the harness: full run FAILED after 793.255 s, with 45/46 scenarios passing. The sole failure was connecting’s “and A hears that B can hear it now” announcement assertion. A focused repeat through make on the reviewed head PASSED (15.472 s); the same focused scenario on fetched main also PASSED. room.js and the connecting scenario are unchanged by this branch. This is an intermittent gate failure, not evidence of a Keymail regression; I am not reporting the full gate as green. Guest, guestdirect, removed, allowlist, crosssite, signedout and scriptless scenarios all passed in the full run.- Two independently restored mutations made new tests fail at their intended assertions: synchronous overflow logging -> TestMailQueueSendNeverBlocksWhenFull; disable refusal interception -> TestKeymailRefusalIsTheDoor. Restored originals plus targeted queue/Keymail/Home/native checks passed.
amadan ci status oficina/meet -branch fix/keymail-signinreported CI passed at c066739 (exit 0).- All Go executions for Calls, including scratch probes and mutations, were invoked through make (a supplemental /tmp makefile supplied the targeted review recipe).
All probes/mutations used a detached /tmp worktree. Sources and logs remain as /tmp/astra-keymail-meet-*; the scratch worktree is removed after review. Existing checkouts were not moved or edited. Disk was checked (35 GiB initially, 33 GiB during validation). No deployed-canary validation was performed under the review-only instruction.