SSH: markPushed arrives on the REST API
Clearing a host's Stale principals flag was the last SSH operation that existed only as a tRPC procedure. A REST-driven onboarding — scripts, pki-manager-cli, Ansible — could register a host, issue its certificate, create principals and map them entirely through /api/v1, and then had to break out to a hand-crafted tRPC POST for that single final step, or leave the host marked Stale forever.
New endpoint
POST /api/v1/ssh/hosts/:id/auth-principals/pushed
→ { "hostId": "…", "fqdn": "…", "lastPrincipalPushAt": "2026-08-25T05:38:15.216Z" }
It is the counterpart to the existing GET /api/v1/ssh/hosts/:id/auth-principals: render the files, push them to the host, then mark them pushed. Documented in the OpenAPI spec and callable from Swagger at /api/docs.
Bug fix: a typo'd host id reported success
markPushed previously ran a bare UPDATE … WHERE id = ? with no existence check. An unknown id updated zero rows and still returned success, so an automation script with a typo looked like it had cleared the flag while the host stayed Stale indefinitely, with nothing in the audit trail.
It now looks the host up first, returns 404 for an unknown id, and writes an audit_log row (ssh.principal.mark_pushed) on both success and failure. tRPC returns the service result verbatim, so both APIs are byte-identical.
SSH UI: reusable principal components
The principal picker is extracted into PrincipalSelect and the per-host mapping UI into HostPrincipalMappingCard. The Principals page is rebuilt around them instead of stacking every host's mapping card on a single page (−90 lines there).
Docs
DEVELOPMENT.md gains a Launching the Dev Stack section. It previously documented ports 3000/5173 and predated mprocs, Keycloak and Orca. Now it carries the real port table (52080/52081/42998/42997/6430), the fact that pnpm dev runs mprocs and dies with Stdin is not a tty when backgrounded, the Orca terminal recipe, the WSL2 IPv4-only port-forwarding gotcha, and cleanup-by-PID.
Upgrade notes: no database migration, no configuration change, no breaking API change — the new route is purely additive.
Full backend suite: 61 files / 625 tests. Frontend: 52 tests.