| 1 | # Census: console email sign-in |
| 2 | |
| 3 | Status: APPROVED by Paul (census owner), 2026-10-24, recorded on #280 with |
| 4 | main at 65ca4aca. The rows keep `rev3=PROPOSED-email-signin`: under the #369 |
| 5 | process nothing is flipped, and later overlays re-froze units at these |
| 6 | dispositions, so a flip would move their fingerprints. |
| 7 | |
| 8 | Branch `email-signin`, cut from main at 4d6f0e6c. Design: |
| 9 | `docs/superpowers/specs/2026-10-21-console-email-signin-design.md`. |
| 10 | |
| 11 | ## What changed |
| 12 | |
| 13 | The front page has one address box. An address with a Keymail mailbox goes |
| 14 | to Keymail's sign-in as before. Any other address gets an emailed link and a |
| 15 | six-digit code. A session that signed in by email re-confirms risky actions |
| 16 | by email instead of a passkey. The old Apps-inbox confirmation link is |
| 17 | retired: its route stays, but only says the link has expired. |
| 18 | |
| 19 | None of this runs as root. Every unit below runs in the console |
| 20 | (cmd/console serve under console.service, user carlos-console). The root |
| 21 | `carlos` binary links neither `internal/console` nor `internal/mail`, and |
| 22 | `TestRootBinaryLinksNoConsoleOrMail` |
| 23 | (`internal/provision/root_binary_deps_test.go`) pins that with |
| 24 | `go list -deps`. No root read is added. |
| 25 | |
| 26 | ## Root-read census (TestPlan14RootReadCensus) |
| 27 | |
| 28 | Overlay `internal/provision/testdata/plan14-root-reads-email-signin.tsv`, |
| 29 | loaded last, after `plan14-root-reads-claim-merge-main-4.tsv`. Without it the |
| 30 | test reports 47 discrepancies; with it, none. |
| 31 | |
| 32 | ### Changed units, re-frozen at main's disposition |
| 33 | |
| 34 | Each keeps main's basis, with this branch's identity and change appended |
| 35 | (the run-lifecycle-logs-325 form). |
| 36 | |
| 37 | | unit | change | sites | |
| 38 | |---|---|---| |
| 39 | | `cmd/console/main.go` `serve` | passes `CARLOS_CONSOLE_NAME` to NewServer as `Config.ConsoleName` | same three `/data` literals, same offsets | |
| 40 | | `server.go` `NewServer` | sets `consoleNameCfg` and the `challengeSem` send bound; registers the four `/auth/email` routes | the logs/data route pattern, moved | |
| 41 | | `authcli.go` `Server.handleCLIApprovePage` | signed out, renders the front page with `next` back to `/auth/cli` instead of redirecting | adds `r.URL.Query()` (a URL, not a file); old site moves | |
| 42 | | `server.go` `Server.handleCallback` | session minting moves into `finishSignin`; reads the `next` cookie | `r.URL.Query()`, unchanged | |
| 43 | | `store.go` `Store.PurgeExpired` | also deletes expired `email_challenges` rows | one more SQLite `Exec` on CONSOLE_DB | |
| 44 | | `store.go` `Store.migrate` | adds `sessions.method` (default `keymail`) and calls `migrateEmailSignin` | SQLite DDL; old site moves | |
| 45 | | templates `check`, `home`, `layout`, `team` | approved copy, the address and code forms, CSS for email inputs | embedded UI input | |
| 46 | |
| 47 | ### New units, accepted legacy at their siblings' class |
| 48 | |
| 49 | All J4 root-input, the class of the console's other store methods, |
| 50 | URL-query handlers and templates. |
| 51 | |
| 52 | | unit | what it reads | |
| 53 | |---|---| |
| 54 | | `emailsignin_store.go`: `Store.HoldEmailChallenge`, `LiveEmailChallenge`, `PeekEmailLink`, `TakeEmailCode`, `TakeEmailLink`, `insertEmailChallenge`, `migrateEmailSignin`, `deleteBound` | SQLite on the console's own `email_challenges` table | |
| 55 | | `store.go` `Store.SessionInfo` | one `QueryRow` on `sessions`, as `SessionAddress` | |
| 56 | | `store.go` `Store.PendingInviteAddress` | one `QueryRow` on `invites`, without consuming it, as `PendingInviteFor` | |
| 57 | | `store.go` `Store.CreateSessionMethod` | `CreateSession`'s `INSERT`, moved, plus the method column | |
| 58 | | `stepup.go` `Store.takePendingActionByHash` | `TakePendingAction`'s body and both sites, moved unchanged, keyed by hash | |
| 59 | | `store.go` `newStore` | two `crypto/rand.Read` calls: an in-memory 32-byte HMAC key for codes and an 8-byte epoch label, never written down; as `Keymail.sealEnvelope` | |
| 60 | | `server.go` `Server.handleHome`, `Server.handleSignin`; `emailsignin.go` `Server.handleEmailLinkPage` | `r.URL.Query()` of the request URL (`next`, `t`), not a file; as `handleCallback` | |
| 61 | | templates `email_link.html`, `stepup_email.html` | embedded UI input, as their sibling templates | |
| 62 | |
| 63 | ### Retired units |
| 64 | |
| 65 | Each is still declared and reads nothing now, so it is retired by an H3 |
| 66 | removal. The loader accepts an H3 removal only with a `census-ruling-` |
| 67 | basis, so the rows read `census-ruling-email-signin (PROPOSED, awaiting the |
| 68 | census owner on #280): ...`. That is the form console-invite-delivery used |
| 69 | for `Keymail.SendApp` before its ruling. A removal is not in the seal. |
| 70 | |
| 71 | - `server.go` `Server.handleConfirm`: renders the expired message only. Its |
| 72 | `TakeConfirmation`, `EnsureUser`, `GrantToken` and `CreateSession` calls |
| 73 | are gone. |
| 74 | - `stepup.go` `Store.TakePendingAction`: calls `takePendingActionByHash`. |
| 75 | - `store.go` `Store.CreateSession`: calls `CreateSessionMethod` with |
| 76 | `MethodKeymail`. |
| 77 | |
| 78 | ### Envelopes and seal |
| 79 | |
| 80 | `cmd/console`, `internal/console` and `internal/console/templates` are |
| 81 | re-pinned. The scan finds no other read-site change in them. Twenty-eight |
| 82 | frozen declarations are added, so the seal moves from securelisten-337's |
| 83 | `33a8ff46…c430` to `b25dfda5a68d2183af759b42398f79bb471749d46e68d267545647d1a2e6046b`. |
| 84 | |
| 85 | ## Record-write census (TestPlan9B8RecordWriteCensus) |
| 86 | |
| 87 | `internal/console/server.go:NewServer` is re-digested to `9a49c1fc…8169`. The |
| 88 | effective entry is the replacement in `scanDemandWriteCensusInventory` |
| 89 | (`scan_demand_census_test.go`), which overrides the `plan9B8CensusInventory` |
| 90 | row, so that is the entry changed. Its kinds are still `lit:instances`. The |
| 91 | diff adds two struct fields and four route registrations. No record write is |
| 92 | added or moved. |
| 93 | |
| 94 | ## Questions for the census owner |
| 95 | |
| 96 | 1. Accept the ten changed and eighteen new units as accepted legacy under the |
| 97 | console's identity, with seal `1221062f…1d00`? |
| 98 | 2. Retire `handleConfirm`, `TakePendingAction` and `CreateSession` by H3 |
| 99 | removal? If ruled, the marker becomes `census-ruling-email-signin` in |
| 100 | every row and the seal is recomputed. |
| 101 | |