# Plan 14 census overlay console-invite-delivery (console invite delivery: Keymail federation first, then the console's own email, then the hand-off link; docs/superpowers/specs/2026-09-28-console-invite-delivery-design.md), loaded last, after box-publish-record. RULED by platform-1e as census-ruling-console-invite-delivery (R1-R5), on 5aef01db; the ruling and its reasons are docs/superpowers/notes/census-proposal-console-invite-delivery.md. # R1: every unit here runs in the console (cmd/console serve under console.service, user carlos-console) or in tofu on an operator's machine, and each is accepted legacy under that identity. The root carlos binary links neither internal/console nor internal/mail; TestRootBinaryLinksNoConsoleOrMail (root_binary_deps_test.go) pins it with go list -deps. This ruling is the separate scope ruling the legacy freeze needs, and the seal moves. # R2: Keymail.SendApp is retired by an H3 census-ruling removal (the carlos-run getJSON form): its one read moved unchanged into Keymail.sealEnvelope, the same class. R3: the host role's ses:SendEmail grant is outside this census; Paul approves the host-role SES grant (or a console-only role) before console_mail_from is set. R4: the rev3 tag is census-ruling-console-invite-delivery, as box-publish-record and the step 6 overlays did. R5: not a merge blocker; with CARLOS_HOST_CLAIM on, the console mailer must go through the claim mail gate before console mail is enabled (#250). # Five changed units are re-frozen with their read sites unchanged (serve, NewServer, Keymail.do, and the two region-host files). Two units are new: Keymail.sealEnvelope (crypto/rand.Read, the site SendApp held) and Store.CountPendingInvites (one SQLite QueryRow). @legacy 56fd2bdf3b9f60d83217e5b9dc5aba1618d1187f523f01e7d9e3ab3a67c0be98 cmd/console/main.go serve legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=the console (cmd/console serve under console.service, user carlos-console): main's body with the console mailer built once (consoleMailer, over the release store, mail provisioner and DNS writer serve already holds), handed to NewServer as Config.ConsoleMail, and its identity ensure started in a goroutine bounded at two minutes beside the alert watcher; read sites unchanged: the same three /data literals (the registry and console database defaults), each moved again by the HostClaim guard (0da9b965, #250): hostClaim read once and passed as a new argument to consoleMailer, no new read site data-literal@3439,data-literal@4540,data-literal@4777 24101dff120a7c6086e39f84949a04e9f16d4d2b54bfffc9210783fd38909c44 cmd/console/main.go serve legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=the console (cmd/console serve under console.service, user carlos-console): main's body with the console mailer built once (consoleMailer, over the release store, mail provisioner and DNS writer serve already holds), handed to NewServer as Config.ConsoleMail, and its identity ensure started in a goroutine bounded at two minutes beside the alert watcher; read sites unchanged: the same three /data literals (the registry and console database defaults), each moved again by the HostClaim guard (0da9b965, #250): hostClaim read once and passed as a new argument to consoleMailer, no new read site data-literal@3439,data-literal@4540,data-literal@4777 24101dff120a7c6086e39f84949a04e9f16d4d2b54bfffc9210783fd38909c44 @legacy 2a3abb0b02d1f9b17ba5d964cdb9703fa962099560ad94d9fb725c169cfe9aee infra/modules/region-host/main.tf external legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=tofu on an operator's machine with the operator's AWS credentials (no box unit; no binary embeds the file): main's module with one optional aws_iam_role_policy, host_console_mail, count 0 unless console_mail_from is set; it grants the host role ses:SendEmail on the one sending identity, the carlos-bab configuration set and its tenants, conditioned on ses:FromAddress equal to console_mail_from; no read is added and nothing on a box reads the file file:external@0 6ae47ec38e0dbace114634c73575dc00970461521c3bce3e36baeb7942229c5b infra/modules/region-host/main.tf external legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=tofu on an operator's machine with the operator's AWS credentials (no box unit; no binary embeds the file): main's module with one optional aws_iam_role_policy, host_console_mail, count 0 unless console_mail_from is set; it grants the host role ses:SendEmail on the one sending identity, the carlos-bab configuration set and its tenants, conditioned on ses:FromAddress equal to console_mail_from; no read is added and nothing on a box reads the file file:external@0 6ae47ec38e0dbace114634c73575dc00970461521c3bce3e36baeb7942229c5b @legacy 1507a927643a2e468dd76599f7be431bc6c1ac4669d374c1774ccad1185bac46 infra/modules/region-host/variables.tf external legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=tofu on an operator's machine with the operator's AWS credentials (no box unit; no binary embeds the file): main's variables with console_mail_from (validated as one lowercase address) and console_mail_region, both empty by default; no read is added and nothing on a box reads the file file:external@0 ffdad585e18d905f0f4a97b9b666777200fd505868bcd5bde0cfeae216b6250e infra/modules/region-host/variables.tf external legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=tofu on an operator's machine with the operator's AWS credentials (no box unit; no binary embeds the file): main's variables with console_mail_from (validated as one lowercase address) and console_mail_region, both empty by default; no read is added and nothing on a box reads the file file:external@0 ffdad585e18d905f0f4a97b9b666777200fd505868bcd5bde0cfeae216b6250e @legacy 0a5b5cdd3c191bd712520c32910dc813906bb049b241eea61e40c13202fda136 internal/console/keymail.go Keymail.do legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=the console (cmd/console serve under console.service, user carlos-console): main's body with a non-200 answer returned as the typed keymailError (the same Error text, plus Status and Msg for IsKeymailBlocked); its one read, io.ReadAll of Keymail's HTTP response body capped at 1 MiB, unchanged file:ReadAll@594 e2f193e479472dff43846bb8d212124a1439b5765684648d499e607e30ede7be internal/console/keymail.go Keymail.do legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=the console (cmd/console serve under console.service, user carlos-console): main's body with a non-200 answer returned as the typed keymailError (the same Error text, plus Status and Msg for IsKeymailBlocked); its one read, io.ReadAll of Keymail's HTTP response body capped at 1 MiB, unchanged file:ReadAll@594 e2f193e479472dff43846bb8d212124a1439b5765684648d499e607e30ede7be @legacy 58d32b4b120d00d668186e0fb2f4e645fb363216cbc7bbc6581f0c58bc6c9af4 internal/console/keymail.go Keymail.sealEnvelope legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=the console (cmd/console serve under console.service, user carlos-console): new: SendApp's envelope sealing moved unchanged into its own method, which SendApp and SendFederated share; its one read is crypto/rand.Read of the 16-byte envelope id from the kernel's random source, the site SendApp held as file:Read@949 (accepted-legacy, J4 root-input, on main); no file, no store, no box state file:Read@387 b140fde1ca8b9b45403e55e72eebfb922ad5847c43c2ca836324ad6b46d77658 internal/console/keymail.go Keymail.sealEnvelope legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=the console (cmd/console serve under console.service, user carlos-console): new: SendApp's envelope sealing moved unchanged into its own method, which SendApp and SendFederated share; its one read is crypto/rand.Read of the 16-byte envelope id from the kernel's random source, the site SendApp held as file:Read@949 (accepted-legacy, J4 root-input, on main); no file, no store, no box state file:Read@387 b140fde1ca8b9b45403e55e72eebfb922ad5847c43c2ca836324ad6b46d77658 @legacy c2fa720d59cc3927e15b859e52a3241f9d6b007a5fb8a038fe7fad7be78e4d41 internal/console/server.go NewServer legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=the console (cmd/console serve under console.service, user carlos-console): main's body with the invite delivery fields set (consoleMail from Config.ConsoleMail, inviteTimeout, inviteKeymailShare) and sendFederated bound to km.SendFederated beside sendApp; no goroutine; read sites unchanged: the one data-literal is the route pattern GET /a/{sqid}/apps/{app}/logs/data, not a path data-literal@9108 4d82a68616a6754966ced46d8d12541fcda64de9e1856c3440a127a9c533b032 internal/console/server.go NewServer legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=the console (cmd/console serve under console.service, user carlos-console): main's body with the invite delivery fields set (consoleMail from Config.ConsoleMail, inviteTimeout, inviteKeymailShare) and sendFederated bound to km.SendFederated beside sendApp; no goroutine; read sites unchanged: the one data-literal is the route pattern GET /a/{sqid}/apps/{app}/logs/data, not a path data-literal@9108 4d82a68616a6754966ced46d8d12541fcda64de9e1856c3440a127a9c533b032 @legacy 2c2e5edb430e5d8a9f42c77f8cb9cdb875b896caaf022fef3871e8849166fedf internal/console/store.go Store.CountPendingInvites legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=the console (cmd/console serve under console.service, user carlos-console): new: one SQLite QueryRow, a COUNT(*) of an account's unaccepted, unexpired rows in the console's own invites table (CONSOLE_DB), for handleInvite's pending cap; it only refuses an invite; the same class as Store.PendingInvites, CreateInvite and RevokeInvite (accepted-legacy, J4 root-input, on main) file:QueryRow@102 1733078d53728ce629a956cf10c77e43ce9fe4fab21b1a45c408743bddb818e5 internal/console/store.go Store.CountPendingInvites legacy accepted-legacy accepted-legacy;rev3=census-ruling-console-invite-delivery;basis=the console (cmd/console serve under console.service, user carlos-console): new: one SQLite QueryRow, a COUNT(*) of an account's unaccepted, unexpired rows in the console's own invites table (CONSOLE_DB), for handleInvite's pending cap; it only refuses an invite; the same class as Store.PendingInvites, CreateInvite and RevokeInvite (accepted-legacy, J4 root-input, on main) file:QueryRow@102 1733078d53728ce629a956cf10c77e43ce9fe4fab21b1a45c408743bddb818e5 # R2: Keymail.SendApp retired (H3, the carlos-run form). A removal is not in the seal. @remove internal/console/keymail.go Keymail.SendApp @removed internal/console/keymail.go Keymail.SendApp H3 census-ruling-console-invite-delivery: still declared, and reads nothing now: its one read, crypto/rand.Read of the envelope id, moved unchanged into Keymail.sealEnvelope, an accepted-legacy row in this overlay. It runs in the console as carlos-console. # Envelopes re-pinned. cmd/console: main.go (serve, consoleMailer). infra/modules/region-host: main.tf and variables.tf (rows above). internal/console: consolemail.go, keymailfed.go and keymailpow.go are new and the scan finds no read site in them; appsettings.go, keymail.go, server.go and store.go changed, and their only read-site changes are the rows above. internal/mail: consolemail.go and sessend.go are new, walk.go gains the console-mail guards; the scan finds no read site in any of them (NewSESEmailSender's SDK config load is the console's default credential chain, the host role, and is in TestClaimAuthoritySourceCensus as a send-only client). @coverage cmd/console b22983f8973baaef9f8598af4669684fee7078a08b570f88dfdf90b61f15a89e @coverage infra/modules/region-host 82e90b1086ae4b88cc311f7cac50074de1c1636252bf5b6acdc15cc393d856e7 @coverage internal/console 18fc22e075cea71c813348099c6698d6f73b2047ff92fc5b2f2cd52408255756 @coverage internal/mail c45dd340c6db8c403fb002e98386c812d848dc5a4dbc526c586e3571bbf6e317