Files
stack/docs/plans/2026-10-11_webui-preview.md
T
jason.woltjeandClaude Opus 5.5 8334a52331 docs: lead decision 94, dogfood cutover rows, Jason starts the Console (#1569, #1570)
Decision 94 records Q94 yes and Mos's sage-0054/0055 directions: no broker
row (the Console already reads as the human), Jason starts the Console,
nine rows deferred, S7's line amended, Gate E partial pass on the dogfood
day, no rollback hostname. New brief for the Console service row and the
send-to-a-role row. Row 74's start line and S7's request line amended.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-11 17:17:35 -05:00

118 lines
6.0 KiB
Markdown

# WebUI preview behind sign-in (2026-10-11)
Status: written by Sage, lead. It follows `docs/plans/BRIEF-TEMPLATE.md`.
One row. Amended the same evening for Jason's Q91 answer "A" (lead
decision 93): a read-only preview at its own hostname, behind forward
auth limited to Jason's group, the old build left up. The identity
provider is Authentik at auth.diversecanvas.com, not Pocket ID as first
relayed; the Console names no provider. Target:
Jason signs in on 2026-10-12, by 2026-10-13T05:00Z at the latest. The
service script is cut from this row for that date; it is a follow-up.
## WebUI preview: a signed-in, read-only Console behind the edge proxy
### Problem
Jason asked for an ETA for the refactor WebUI at mosaic.woltje.com. Today
the Console can't be reached from anywhere but the host it runs on, by
design:
- `startServer` refuses to bind to a non-loopback address
(`packages/webui/src/serve.mjs:82`) and answers 403 to any request whose
Host isn't loopback on its own port (`serve.mjs:92`). A reverse proxy
that forwards the public Host is refused.
- The Origin check (`serve.mjs:96`) compares against `http://<Host>`, so a
browser on `https://<public name>` is refused as cross-origin.
- There is no sign-in. Anything that connects to the port reads what a
reader capability reads (`packages/webui/README.md`, the paragraph on
the bus views), and the README forbids a public proxy.
- Two routes write: POST `/api/seen` and `/api/reply` go to the board.
- Nothing runs the Console as a service. It is a foreground `serve`.
Mos approved these cuts for the preview (2026-10-11): sign-in at the edge
through forward auth, not login code in the Console; a preview
hostname, so the old build at mosaic.woltje.com stays; read-only; not
held for S7 (row 42) or Gate E (row 5). ops-01 owns the Authentik application
(limited to Jason's group), the DNS name, TLS, the proxy and the route
from the edge to this host. The route opens once rev-sol-01 passes
ops-01's proposal (Q91 A).
### Owner and reviewer
Owner: Dewey, who owns the WebUI, starting 2026-10-11 evening. Reviewers:
Darkwing and Filbert, ahead of their own rows.
### Files owned
- `packages/webui/src/serve.mjs`, `packages/webui/src/cli.mjs`,
`packages/webui/README.md` and `packages/webui/tests/`
- `agents/dewey/work/queue-<row>/`
### What ships
A preview mode, off by default. Without its flags the Console behaves
exactly as today, and the existing tests show it.
- `serve --public-host NAME --auth-header HEADER --allow-user ID`, all
three or none. The check is generic: one header name and one user id,
both from the flags, nothing provider-specific in code or tests.
Node lowercases incoming header names, so HEADER matches without
regard to case (`X-authentik-uid` on the flag finds
`x-authentik-uid`), and a test shows it. The value compare is exact.
NAME is a hostname, no port or scheme. The bind stays loopback;
nothing here listens beyond it.
- In preview mode a request's Host must be NAME, or loopback on the
Console's port as today. An Origin, when sent, must be `https://NAME`
for a NAME request, and the loopback form for a loopback request.
- A NAME request must carry HEADER with a value equal to ID. Missing or
different answers 401 with no bus read made. Loopback requests are
unchanged, so the operator's own browser on the host still works.
- In preview mode every POST answers 403 `read-only`, whatever the Host.
`/api/seen` and `/api/reply` don't reach the board.
- Tests for each refusal: wrong Host, wrong Origin, missing header, wrong
user, a POST to each write route, and a partial flag set refused at
start. A test that the loopback path is unchanged in preview mode.
- The minimum for the first sign-in is the inbox, tasks, agents and
trails (`/api/bus/*`) read-only. The other GET routes stay as they are.
The board panel reads the board already running on 7331; if it is down
the panel shows its existing error and the bus views still work.
- The README replaces "Do not expose either unauthenticated server
through a public proxy" with what the preview mode requires of the
proxy: it strips HEADER from every client request and sets it only
after the identity provider signs the user in, it forwards only
requests whose Host is NAME and preserves that Host (a forwarded
loopback Host would skip the header check), it terminates TLS,
and it is the only thing that reaches the port from off the host. It
says plainly that any local process can still connect and send HEADER,
the same exposure the README already describes for the port.
- The packet runs the Console in preview mode with `curl` against
loopback, showing each refusal and one allowed read, with no network
beyond the host.
### Out of scope
- OIDC or login code in the Console; Authentik config; DNS, TLS, the
proxy and the route from the edge (ops-01).
- Resolving decisions or replying from the browser. REQ-DEC-3 keeps
resolution in the CLI; changing that is a ruling and its own row.
- Live-seat conversations (CHAT-07) and any board change.
- A container image, CI deploy, or changes to the bus unit.
- A service script and unit template (`scripts/webui-service.sh` on the
`bus-service.sh` pattern). That is a follow-up row.
- Starting it on dragon-lin. The Console reads the bus through the human
transport, so whoever starts it holds the human's capability. An agent
starting it, even detached through systemd, works around the human
proof (lead decisions 62 and 94). After the row lands, Jason starts it
from his own shell as a transient user unit (`systemd-run --user`,
loopback port 7332; 7330 holds the operator's own Console), with the
command Sage gives him. The edge reaches it once rev-sol-01 passes
ops-01's route.
- The group check. Authentik and the proxy limit sign-in to Jason's
group; the Console checks the header against the one user id.
### Gate
Darkwing and Filbert approve on the row's issue naming the candidate
manifest. The webui, control-board and cli node suites and every
`scripts/test-*.sh` green on Sage's gate rerun.