Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
3.9 KiB
R-35 — a config apply ends the household's dashboard session: a one-page design (2026-10-08)
Status: design only. Read in source today (controller main a0370b4ed8ef). Architecture documents:
architecture/02-controller-module-map.md (config refresh), 07-backup-architecture.md §5 (the whole-guest archive is
plaintext on the household's premises), 09-update-architecture.md §3 decisions 63-64 (the family gate).
The problem, as measured
2026-07-21: the hub raised config_version 10→11 at 16:54:58; the controller self-restarted (StartedAt 16:54:59Z, up
16:55:02); the household was logged out mid-flow (row evidence controller-log-full.txt). Still true in source:
internal/report/config_refresh.go:33-71— anyconfig_versionchange: re-pullcontroller.yaml, record, thenRestart=api.GracefulSelfRestart(cmd/controller/main.go:1100-1109). No per-setting choice.internal/web/auth.go:15-18, 250-270— dashboard sessions live only ins.sessions map[string]*session(token, expiry, CSRF token). Any process exit ends every session. This is not only a config-apply problem: every controller update (the floor, the household's own update press, a crash restart) logs the household out too.
New since the row's last note (2026-10-05): the family gate (decisions 63-64, controller v0.287.0) already persists
its sessions to family.json (0600, tmp+fsync+rename) in the data dir „so a restart keeps every session"
(internal/family/family.go:11-12, 45-50, 267-290). It stores the session ID itself. So „login tokens on disk" is
already the product's state for family sign-ins; the dashboard is the odd one out.
Options
| What | Costs | Risk | |
|---|---|---|---|
| A — hot-apply | Adopt changed controller.yaml fields in the running process; restart only for fields that need it. |
A per-field ruling over the whole config (hub, offbox, cloudflare, paths…), a reload path per module. ~2-3 sessions. | A field applied half-way; fixes config apply only, not updates. |
| B — persist sessions, token on disk | Write the session map to sessions.json (0600) like family.json. |
~½ session. | A copy of the archive (plaintext by design, 07 §5) holds live 7-day dashboard tokens. |
| C — persist sessions, fingerprint on disk | As B, but the file holds sha256(token) → {expiry, CSRF token}; lookup hashes the cookie. Cleared by invalidateAllSessions (password change) and logout as today; expired rows dropped at load and at the 15-min cleanup. |
~½ session + tests. | A stolen archive gives a fingerprint that cannot be turned back into a cookie. A box restored from an archive keeps that day's sessions valid until they expire (≤7 days) — the same as family sessions today. |
Pick: C. It ends the logout for every restart, not just config apply, at the smallest cost, and it removes the one cost the row named (tokens in the archive). A stays a later refinement if restarts themselves become a problem.
First slice, with its red test: internal/web/session_store.go (load at NewServer, save under sessionsMu on
create/delete/invalidate, atomic write as in family.saveLocked). Tests: (1) create a session, build a NEW Server on
the same data dir, the cookie is still valid and returns the same CSRF token — red today (map is fresh); (2) the file
contains no token bytes (grep the file for the token: must be absent); (3) after invalidateAllSessions, a new
Server rejects the old cookie; (4) an expired row is not loaded. Live check on scratch 9202: sign in, trigger a
controller restart, the next page load needs no login.
One question for the operator
May the box remember a dashboard sign-in across its own restarts, keeping on its disk only a fingerprint that cannot be used to sign in? If you do nothing: the household is logged out at every settings push and every controller update, as today.