Consolidate Phase 8 documentation

Mark web administration complete, centralize current contracts and
acceptance evidence, and remove superseded slice records. Update
roadmap,
architecture notes, and test references without changing firmware
sources.
This commit is contained in:
2026-09-13 22:27:10 +02:00
parent 1608641d50
commit 91267b371e
39 changed files with 376 additions and 3489 deletions
+30 -56
View File
@@ -34,12 +34,10 @@ This is a semantic map, not a complete file inventory. Start here, then read the
## Session broker
- **8D.16 management:** `session_broker_get_management_snapshot()` copies compact eight-client rows plus writer/lease generation atomically with zero wait; `session_broker_assign_writer_current()` compares generation and target under the force-writer lock. 29-bit client generations retire slots instead of wrapping; saturating lease generation fences ABA, survives counter clear, and leaves ordinary recovery available. Actual canonical regressions extend `tests/session_broker_diagnostics/run.py`. Full contracts/wrap analysis: `docs/phase8d16_implementation.md`.
**Responsibility:** mediate all transport access to the serial service; provide one writer lease and multiple isolated observers.
- Files: `src/session_broker.{h,c}`, `src/session_console.{h,c}`
- Interfaces: connect/disconnect, request/release/force writer, nonblocking read/write/event APIs, snapshots and counters
- Interfaces: connect/disconnect, request/release/force writer, nonblocking read/write/event APIs, snapshots and counters; `session_broker_get_management_snapshot()` and `session_broker_assign_writer_current()` atomically project/compare target and lease generation. Exhausted 29-bit client generations retire; saturated lease generation preserves ordinary recovery. Tests: `tests/session_broker_diagnostics/run.py`.
- Called by: USB, web serial, role-`user` SSH, console tests, local UI snapshots/actions
- Dependencies: `serial_service`
- Data path: `transport -> broker -> serial service -> UART1`; reverse data is fanned out per client.
@@ -57,36 +55,20 @@ This is a semantic map, not a complete file inventory. Start here, then read the
- Dependencies: TinyUSB, broker, serial service
- Flow: `USB host <-> USB task <-> broker`
- Lifecycle: permanent owner task; broker client exists only while attached with host DTR asserted.
- Constraint: callbacks retain the latest host line coding only for diagnostics; it never reconfigures UART1. UART1 framing and speed remain controlled by the serial configuration and its explicit persistence commands.
- Constraint: callbacks retain the latest host line coding only for diagnostics; it never reconfigures UART1. UART1 framing and speed remain controlled by the serial configuration and its explicit persistence commands.
## Web and WebSocket serial
- **8D.21 HTTPS-only slice (2026-09-13):** `web_security.{c,h}` owns zero-wait fingerprint/generation projection and nonreused identity reservation shared by canonical rotate/reset; replacement generates/commits outside locks, publishes only after commit. `web_server_replace_identity()` reserves service before identity checks and retains both through commit/stop/start, shared by `web_console` and browser-shell owner; no mutation before stale service rejection, no rollback after commit. `web_lifecycle_settings.c`/`web_ui.c` extend the existing 8D.20 slot/routes/controller with confirmed `rotate` plus required identity generation and seven-field public metadata snapshot (320-byte bound); ACK/original-login and pending/no-replay rules unchanged. No new route/task/timer/queue/assets/config/SSH identity work. PASS security17, lifecycle44+two real-crypto/NVS integration groups, cookie lifecycle8+shared/all variants, UI156+HTML/CSP and broad regressions. Final pio100,532 RAM/1,825,073 flash (+24/+3,568 vs audited baseline), CPU160 verified. Chosen HTTPS slice implemented; parent review/target pending, full21 incomplete pending separately requested SSH identity work. Exact ownership/API/failure/wrap/test/resource contracts: `docs/phase8d21_implementation.md`.
- **8D.20 end-to-end (supersedes prerequisite-only entry below):** `web_lifecycle_settings.{c,h}` owns three optional current-admin lifecycle snapshot/operation routes,256-byte typed stop/restart/reboot, one original-login slot and bounded send-return→HTTPD ID callback→existing dispatcher handoff. One lost ACK reservation survives until callback/successful HTTPD destruction; nonreused IDs and owner generation prevent late/same-pointer ABA. `web_server` conditional reboot uses canonical `esp_restart()` outside locks, stop/restart retains owner reservation. `web_ui.c` adds confirmed HTTPS/Reboot,15-second requests/manual results, pending uncertainty/no replay/relogin, link to existing Network with accurate UART0/SSH/USB recovery.39 handlers/six sockets; no tasks/timers/queue growth/assets/config/identity work. PASS lifecycle41, cookie lifecycle8+shared, UI153+HTML/CSP, dispatcher and broad regressions. Final pio100,508 RAM/1,821,505 flash (+104/+13,064 vs pre-phase100,404/1,808,441); CPU160 preserved. Host/build complete, independent parent review and target/runtime checks pending. Exact bounds/failure/reboot audit/resources/checklist: `docs/phase8d20_implementation.md`.
- **Historical 8D.20 internal HTTPS owner prerequisite (superseded by integration above):** `web_server.{c,h}` owns zero-wait `web_server_get_management_snapshot()` plus off-HTTPD `web_server_stop_current()`/`web_server_restart_current()`. Canonical mutex compares saturated lifecycle generation; restart retains transition reservation across stop/start, failures preserve canonical cleanup/recovery. Repeated init preserves lifecycle failure. No HTTP/UI/ACK/reboot integration or new production caller yet. Existing lifecycle harness now34 groups including ABA, saturation, gap interleavings and failure recovery. Baseline100,404/1,808,441 →100,404/1,808,589 B (+0/+148); unused public entry points linker-discarded. Parent/target review pending. Contract/split/remaining authorized20 and target checklist: `docs/phase8d20_implementation.md`. Do not implement unrelated19 or duplicate existing Network controls.
- **8D.18 client/writer contexts (2026-09-13):** `web_ui.c` extends8D.17's sole quick host with admin-only Broker clients/Active writer entrances to the existing Broker controller/native confirmation. One5-second-after-success live timer and5-second whole-read deadline; explicit identity/lease token retained across refresh, sticky stale/absence latches prevent rebasing/resurrection. Full-page drafts untouched by new triggers; focused controls retain focus with guarded aria-disabled state. No backend/policy/assets/CPU/transport changes. `tests/web_ui_session/{broker.cjs,layout.py}`:135 UI groups+renderer/HTML/CSP; broad broker/auth/lifecycle/transport regressions PASS. Baseline100,300/1,788,661 → final100,300/1,793,301 RAM/flash (+0/+4,640 B), CPU160 verified. Independent parent review and target sign-off pending. Exact contracts/tests/limits/checklist: `docs/phase8d18_implementation.md`.
- **8D.17 quick settings (2026-09-13):** `web_ui.c` owns Serial/Wi-Fi status-trigger nonmodal popovers using the single existing settings DOM/controller, no parallel drafts/endpoints. Secret-free Network quick mode excludes password controls; full-page promotion preserves controller/nonsecret draft, dismissal fences reads/polling without replay. Hover/focus/click/tap, Escape/outside dismiss; full-page drafts protected from hover. Tests `tests/web_ui_session/{browser,network}.cjs`, `layout.py`:126 UI groups + renderer/HTML/CSP PASS; optional Chromium geometry blocked by sandbox, target/independent parent review pending. Baseline100,300/1,782,613 → final100,300/1,788,629 RAM/flash (+0/+6,016 B). CPU160/combined WS send/Broker/Display unchanged. Contract, exact tests and checklist: `docs/phase8d17_implementation.md`.
- **8D.16 Broker (2026-09-13):** `web_broker_settings.{c,h}` owns optional admin-only GET `/api/settings/broker`, GET/POST `/api/settings/broker-operation`; existing dispatcher queues only IDs. `web_ui.c` adds Serial/Display-style full-page rows and explicit confirmed assignment, no mutation on view/selection. 256-byte/four-receive request,2048-byte snapshot,96-byte result, one login-bound slot/no timer;33 handlers/six sockets, unchanged tasks/stacks/queue/assets/CPU160/combined WS send. Tests: cookie `--broker`6+shared, broker management/wrap, dispatcher, lifecycle25, UI119+HTML/CSP and broad regressions. Baseline100,196/1,765,233 B → final100,300/1,782,613 RAM/flash. Independent parent review and target sign-off pending. Contracts/resources/checklist: `docs/phase8d16_implementation.md`.
- **8D.14 Display (2026-09-09):** `web_display_settings.{c,h}` owns optional admin-only GET `/api/settings/display`, GET/POST `/api/settings/display-operation`; `web_ui.c` supplies Serial-style dim/off settings and bounded completion checks. `local_status_ui` owns generation-safe config/storage reservation shared with CLI; buttons do not edit timeouts. No I2C changes. 256-byte/four-receive request,128-byte snapshot,96-byte result, one slot/no timer;30 handlers/six sockets, unchanged tasks/stacks/queue/schema. Tests: cookie `--display` (7+shared), UI111, lifecycle23, dispatcher and broad regressions. Actual baseline100,100/1,748,513 B → final100,196 RAM/1,765,233 flash at160MHz. Target pending; exact API, reset ordering, deadlines, resource/validation limits: `docs/phase8d14_implementation.md`.
- **Current 8D.12/8D.13 — user functional sign-off 2026-09-08, including Settings presentation:** `web_network_settings.{c,h}` owns optional admin-only GET `/api/settings/network` and GET/POST `/api/settings/network-operation`; `web_ui.c` supplies Network, UTF-8/hex SSID editing and explicit transient-secret/connection controls. `wifi_manager` owns generation-checked secret-free snapshots/patch/save/stored-only load and radio transitions; `mdns_service` owns independent conditional hostname persistence, with manager reannouncement. Existing dispatcher receives IDs only. 768-byte request/2,048-byte snapshot/128-byte result, one slot/one-second timer with 30-second queued expiry plus scheduling latency; no hard cancellation. 27 handlers/six sockets, no task/stack/queue/schema growth. Parent integrated tests/build PASS; latest styling UI100 + renderer/CSP/Chromium checks, 99,548 B RAM / 1,744,325 B flash. User full-mix evidence accepted; loaded internal/DMA minima2,276/156 B remain resource follow-ups, not reserve approval. Full contract/exclusions/checklist: `docs/phase8d12_13_implementation.md`. Both phases user-authorized together; no 8D.14/M3 claim. Older next-phase statements below are historical.
- **8D.11:** `web_account_settings.{c,h}` extends Accounts with fingerprint-only POST `/api/settings/accounts/keys` and key-add/key-delete/key-clear on the existing operation endpoint/dispatcher. `user_database.{c,h}` owns zero-wait target-checked snapshots and canonical conditional key mutations. `web_ui.c` handles confirmations, sparse stable indices and self-revocation uncertainty. 24 handlers, six sockets; no new task/stack/queue depth. Host-tested/build-verified, target pending. Contracts/tests/checklist: `docs/phase8d11_implementation.md`.
**Responsibility:** serve authenticated HTTPS UI/API, issue WebSocket tickets, and adapt browser serial sessions to broker clients.
- Files: `src/web_server.{h,c}`, `src/web_serial_transport.{h,c}`, `src/web_ui.{h,c}`, `src/web_console.{h,c}`
- Ordinary HTTPS idle cleanup: `src/web_httpd_idle.{c,h}`, owner sweep in `web_httpd_adapter.{c,h}`, lifecycle/TLS composition in `web_server.c`; `tests/web_httpd_idle/run.py`. Independent of diagnostics/optional transports: 15-second observed idle, one-second timer/one queued probe, six rows, actual WS/async/pending exemptions, safe current-owner shutdown and stop/restart fencing. No LRU/socket/timeout/stack increase. SDK queue/owner-delay limits and target checklist: `docs/https_idle_cleanup.md`.
- Ordinary HTTPS idle cleanup: `src/web_httpd_idle.{c,h}`, owner sweep in `web_httpd_adapter.{c,h}`, lifecycle/TLS composition in `web_server.c`; `tests/web_httpd_idle/run.py`. Independent of diagnostics/optional transports: 15-second observed idle, one-second timer/one queued probe, six rows, actual WS/async/pending exemptions, safe current-owner shutdown and stop/restart fencing. No LRU/socket/timeout/stack increase. SDK queue/owner-delay and regression contract: `docs/https_idle_cleanup.md`.
- Independent throughput diagnostics: `web_serial_transport.{c,h}` owns two fixed per-slot binary-TX aggregates and epoch fences; `web_console.c` exposes default-disabled `web performance enable|disable|show|clear`. Queue-entry/callback-entry, synchronous-send and completion/drain-return estimates, not peer receipt or scheduler-only latency. `tests/web_serial_performance/run.py`; resource/evidence limits and UART0 paired capture: `docs/web_throughput_diagnostics.md`.
- Opt-in admission diagnostics: `src/web_diagnostics.{c,h}`, `tests/web_diagnostics/run.py`. Public synchronous HTTPS create/close callbacks publish six post-TLS connection records; four ticket/upgrade wrappers feed a 32-entry numeric ring. UART0/admin SSH `web diagnostics enable|disable|show|clear`; no queue/task/cleanup override or capacity change. Full bounds, SDK semantics and preaccept/TLS blind spots: `docs/phase8d11_implementation.md`.
- Legacy removal **user-signed-off 2026-09-08** (unchanged certificate fingerprint, preexisting users usable, full-mix evidence): `user_database` persists missing storage empty and preserves valid v1 user bytes; private derived `v1_admin_marker`, no public bootstrap/migration/sync APIs. `web_security` privately migrates v1 1392-byte material to TLS-only v2 1340-byte material, exact identity/generation retained, commit before publish, fail closed without fallback overwrite. Credential commands removed; user generated passwords and TLS rotation remain. Contracts, downgrade and evidence limits: `docs/legacy_credential_removal.md`.
- Storage compatibility: `user_database` persists missing storage empty and preserves valid v1 user bytes; private derived `v1_admin_marker`, no public bootstrap/migration/sync APIs. `web_security` privately migrates v1 1392-byte material to TLS-only v2 1340-byte material, exact identity/generation retained, commit before publish, fail closed without fallback overwrite. Credential commands removed; user generated passwords and TLS rotation remain. Contracts, downgrade and evidence limits: `docs/legacy_credential_removal.md`.
- Security files: `src/web_security.{h,c}`, `src/web_cookie_auth.{h,c}`, `src/web_session_store.{h,c}`, `src/web_auth_parse.{h,c}`. Private IDF boundary: `src/web_httpd_adapter.{h,c}`.
- HTTP policy/UI: `web_cookie_auth` + `web_auth_parse` enforce bounded cookie/Origin/CSRF/admin admission; `web_login_ui.{c,h}` serves login, `web_ui.c` owns session-fenced Serial/Admin/Settings and shared quick controllers. Tests: `tests/web_cookie_auth/run.py` (domain variants), `tests/web_auth_parse/run.py`, `tests/web_login_ui/run.py`, `tests/web_ui_session/run.py`.
- Admission diagnostics: `web_diagnostics.{c,h}`, `tests/web_diagnostics/run.py`; six post-TLS records/32-event opt-in ring, no HTTPD off-owner inspection. [Contract](../web_admission_diagnostics.md).
- Identity/lifecycle: `web_server_replace_identity()` + `web_security` reserve service before identity; commit before reserved stop/start, no rollback after commit. `web_lifecycle_settings.{c,h}` owns original-login ID/ACK handoff. Tests: `tests/web_security/run.py`, `tests/web_admin_transport/server_lifecycle.py`.
- Asset files: authored/generated boundary in `src/web_assets_data.{h,c}`, `web_assets/SOURCES.md`, `web_assets/generate_embedded_assets.py`
- Interfaces: web init/start/stop/snapshots; HTTP handlers; ticket mint/consume; attach/detach; targeted session revocation
- Called by: startup, ESP-IDF HTTPS server, user administration revocation, console/local UI
@@ -95,43 +77,37 @@ This is a semantic map, not a complete file inventory. Start here, then read the
- Ownership: HTTPD owns socket send/close work; transport task owns broker mediation; two fixed WebSocket slots and four outstanding tickets.
- Security constraints: Basic/cache removed; four absolute one-hour cookie sessions revalidate principal currentness. Four pre-login challenges (120 s), five credential attempts/60 s globally, no live session/challenge/ticket eviction. Origin/CSRF required for mutations; Origin/cookie/ticket before upgrade. Disconnect pauses reconnect but retains login; Sign out invalidates its session. Authored loader changes must update their hard-coded CSP hashes atomically.
- Session-store boundary: admitted HTTPS start initializes records; auth-init failure gates HTTPS. Failed start/accepted stop disables and wipes state. Tickets/slots require nonzero non-reused session IDs; session/account/global revocation invalidates store records before socket cleanup. RNG/SHA/database calls run outside short portMUX sections; ID/expiry/epoch checks reject stale work. Run `python3 tests/web_session_store/run.py` and its `--serial` integration mode.
- 8D.3 HTTP policy: `web_cookie_auth` owns public login/challenge/login POST/session/logout routes and protected-route checks; `web_auth_parse` handles bounded values/JSON. `web_httpd_adapter` alone reads private IDF 5.5.0 header scratch, rejects duplicate fields, defers 101 until transport admission and wipes consumed scratch while preserving right-aligned pending bytes. No SDK patch. `src/CMakeLists.txt` supplies private includes and compiles HTTPD warning/debug logs out. Test with `python3 tests/web_cookie_auth/run.py` and `python3 tests/web_auth_parse/run.py`.
- 8D.3 UI: `src/web_login_ui.{c,h}` serves standalone `/login`; `web_ui.c` validates session before serial connect/restore and handles logout/401 safely. Both scripts hash-bound, auth documents/app no-store. Tests: `python3 tests/web_login_ui/run.py` and `python3 tests/web_ui_session/run.py`. Live cutover host-tested/build-verified, M1 validated by user sign-off (numeric reserves open): `docs/phase8d3_implementation.md`.
- Asset constraint: `web_assets_data.c` is checked-in generated input to the build; do not hand-edit or regenerate casually.
- 8D.6 UI: `web_ui.c` adds admin-only Serial/Admin selection and explicit admin open/close through existing endpoints. Serial socket/client/lease survives mode switches; hidden output drains into independent 5,000-line/64 KiB-pending terminals with visible browser-drop counts. Selected keyboard only; logout/expiry/pagehide closes both with handler cleanup. Session identity changes require a clean document before adopting the view; same-session restore retains hidden-until-validated buffers. Fit readiness retries are bounded to three and cache only success. Focused `tests/web_ui_session/run.py` has 17 groups plus toolbar-order/CSP checks. 8D.6 is user-validated; telemetry, evidence limits and 8D.7 handoff are in `docs/phase8d6_implementation.md`. Numeric reserves remain open; no 8D.7 restriction change.
- **8D.88D.10 target sign-off (2026-09-08):** User reports thorough implemented Serial/account settings tests, supplies settled boot/full-mix telemetry and signs implemented work off. Covers both 8D.10 slices and 8D.9 UX. Supersedes target-pending/exclusion status in historical summaries below; exact scope/evidence/counters/limits: `docs/phase8d10_implementation.md`. No unreported checklist passes, reserve approval or M3 completion. Next 8D.11 only on separate request; no source change from sign-off.
- 8D.8: `web_ui.c` adds admin-only Settings/Serial without socket/lease changes. `web_server.c` exposes optional admin-only bodyless `GET /api/settings/serial`, eight working serial values, 256-byte response, no writes/NVS. `serial_service_get_snapshot()` is a zero-wait consistent config/running copy. `web_httpd_register_optional_get()` stages both new-route allocations before table publication (installed IDF public registration leaves a dangling descriptor on name-allocation failure); only Settings uses this startup/exact-GET adapter. 17 URI slots, six sockets/no LRU, no new task. Tests: cookie auth `--settings` (5 groups), UI (21 groups), lifecycle (12 groups). Implemented/build-verified, target/signoff pending; exact accounting and inherited registration-audit followup: `docs/phase8d8_implementation.md`. M2 remains signed off; no 8D.9.
- 8D.9: `web_serial_settings.{c,h}` owns strict 256-byte typed mutation admission and one session-bound pending/result slot. Existing `admin_ssh_console` dispatcher consumes only an ID, revalidates currentness/dequeue deadline and calls canonical serial APIs. `web_server.c` adds optional GET/POST `/api/settings/serial-operation` (19 handlers total); `web_cookie_auth_require_json()` retains Origin/CSRF/admin policy, private optional registration supports exact GET/POST. UI adds explicit framing/lifecycle/persistence with automatic completion checks (1 s, at most 10 GETs/15 s overall), refresh on known terminal results and manual uncertainty recovery without socket/lease changes. Settings stay visible/stale while pending; only Reset confirms NVS overwrite; selecting the current view is a no-op. `/api/status` uses a consistent zero-wait serial snapshot (`running:null` when unavailable). Tests: cookie `--serial-settings` (10 groups), `--settings` (6), UI (35 after UX refinement), console boundary and lifecycle (13). Build verified, target/signoff pending; bounds and failure contracts: `docs/phase8d9_implementation.md`. Supersedes 8D.8's no-8D.9 status above.
- 8D.10 first slice: `web_account_settings.{c,h}` owns compact admin account list and one session-bound other-account role/delete operation slot. `user_database_get_accounts()` is a zero-wait key/secret-free projection; `*_current()` role/delete wrappers compare target ID/auth generation under the canonical mutation lock. Existing dispatcher routes IDs; successful calls target-revoke web/SSH. Optional GET `/api/settings/accounts`, GET/POST `/api/settings/account-operation` raise handlers to 22, sockets/tasks/stacks/queue depth unchanged. UI Accounts subview preserves terminal/lease semantics, confirms mutations and auto-checks/refreshes with manual uncertainty recovery. Tests: cookie `--accounts` (5), canonical accounts, dispatcher, lifecycle (14), UI (41 + CSP). Target pending; create/password/generated-secret/self changes remain next slice, 8D.10 incomplete. Record: `docs/phase8d10_implementation.md`.
- **Current 8D.10 slice 2 (supersedes first-slice exclusions above):** `web_account_settings.{c,h}` adds create/password/self and separate bodyless POST `/api/settings/accounts/generate-password`; `user_database_set_password_current()` shares mutation-lock target checks and canonical commit logic, `user_database_generate_password_value()` generates without mutation. 768-byte/four-receive admission, 96-byte secret-free results; one-second periodic timer cancels/wipes queued non-executing credentials after 30 seconds plus scheduling latency, while dispatcher wipes executing locals on return. Generation has no retained retrieval; UI uses 60-second context-bound acknowledgement before separate submission. Self revocation can deny result retrieval; 401/disconnect is uncertain. Browser-shell restrictions unchanged. Missing generated-route registration found in review is fixed: independent optional endpoint, 23 handlers, failure isolation/restart coverage. Implementation complete, host-tested/build-verified; target/signoff pending. Parent PASS canonical accounts/boundary, parser 294, cookie accounts 9/shared and serial-settings 10, transport 25/tickets 12, store/serial and diff check; UI agent PASS 57/CSP, route agent lifecycle 15. Parent build 25.61 s, 95,908 B RAM / 1,694,237 B flash (+80/+9,880 vs slice 1; +200/+25,400 vs final 8D.9 UX). Timer runtime costs/stack margins remain unmeasured; no 8D.11. Exact evidence attribution: `docs/phase8d10_implementation.md`.
### Browser admin backend (8D.5)
- **8D.7 current status (2026-09-07): implemented scope validated; M2 explicitly signed off by the user ("Jupp, sign M2 off").** Supersedes M2-open/target-pending/continuation statements in the historical slices below; accepted M2 does not require revalidation. User verified certificate rotation and web start/stop via UART0/SSH admin/web admin, restarting after browser stop via another route; full mix without broker drops up to 230400 baud after external adapter correction is user-reported. Intermittent supported two serial + one admin admission failures, recently not recurring, are accepted nonblocking, not fixed. Browser self/generated/key/legacy-credential and other owner command restrictions remain deferred; bootstrap/recovery remain permanently UART0-only. Numeric memory reserves/stack margins remain unapproved; no full parity or individual unreported checklist passes. Next: separately requested 8D.8 read-only settings entry and Serial page; sign-off alone authorizes no implementation. Evidence: `docs/phase8d7_implementation.md`.
- 8D.7 third account slice: `admin_ssh_console` shares parsed browser other-account policy with `user_console`; interactive add/password and forced delete/role now allowed, self/generated/key/bootstrap/recovery still blocked. Post-prompt/pre-DB-API currentness is operation admission, not cancellation of admitted derivation/commit. Existing target-only notifications follow success. Review has no actionable findings; `python3 tests/admin_console_boundary/accounts.py` adds deterministic handler/database failure and stale-next-operation regressions. Target/M2 pending; see `docs/phase8d7_implementation.md`.
- 8D.7 second slice: exact parsed browser `web certificate rotate --force`; `admin_ssh_console.{c,h}` supplies the typed request union/owner `dispatcher_actions` mask, bounded drain/200 ms handoff to the existing 12 KiB dispatcher, persistent pending gate and revalidated executing-slot reservation. `web_console.c` schedules; `web_admin_transport.c` revalidates then calls transactional `web_security_rotate_certificate()``web_server_stop()``web_server_start()`, short-circuiting errors and retaining ownership on failed stop. SSH/UART0 unchanged. No tasks/depth/routes/assets/stacks added; target stack margins unknown. Boundary `run.py` includes `certificate.c`; lifecycle/policy and transport 25/tickets 12 host groups pass as reported. Credential/account then other owner slices remain; user authorized stacking, not target/M2 sign-off. See `docs/phase8d7_implementation.md`.
- 8D.7 first-slice history: browser `reboot`/`web stop` defer via `admin_ssh_console` control task; WEB owner revalidates cookie/principal/token before lifecycle calls. Pending console input is discarded (incoming-frame disposition latched before receive). `web_console.c` defers stop only for browser origin; other restrictions remain. Tests additionally include `python3 tests/admin_console_boundary/lifecycle.py`; handoff: `docs/phase8d7_implementation.md`. No 8D.7/M2 acceptance yet.
### Browser admin backend
- Files: `src/web_admin_transport.{c,h}`, `src/web_admin_tickets.{c,h}`, protected registration/lifecycle in `web_server.c`, revocation through `web_serial_transport_revoke_*`, diagnostics in `web_console.c`.
- Routes: CSRF-protected admin-only `POST /api/admin/ws-ticket`; ordinary `GET /ws/admin` with cookie/Origin/ticket/shared-console admission before explicit 101. No UI entry or broker client. One socket, two tickets, existing two shared console slots; six total HTTPD sockets, LRU disabled, 16 URI handlers.
- Routes: CSRF-protected admin-only `POST /api/admin/ws-ticket`; ordinary `GET /ws/admin` with cookie/Origin/ticket/shared-console admission before explicit 101. Admin UI entry is explicit; no admin broker client. One socket, two tickets, existing two shared console slots; six total HTTPD sockets, LRU disabled; current overall capacity is 39 URI handlers.
- Currentness/policy: `admin_ssh_console_open_available()` shares two slots with runtime SSH; transport-qualified tokens and owner adapters revalidate outside console locks before commands/prompts. Parsed browser policy remains narrower than typed Settings; [shell contract](../web_administration.md#browser-shell-policy). Tests: `tests/admin_console_boundary/{run,accounts,lifecycle}.py`, `tests/admin_ssh_policy/run.py`, `tests/web_admin_transport/run.py --tickets`, `tests/web_cookie_auth/run.py --admin`.
- Ownership: 20 ms ESP timer queues at most one HTTPD poll, no new task; HTTPD owns 1,552 B PSRAM-only payload and IO. Closure uses HTTPD-owned `shutdown`, not IDF's reusable-pointer queued close. Detach fences submitters; only successful HTTPD stop retires queued state before restart. Session/principal currentness and generation checks protect all sensitive boundaries.
- Tests: `python3 tests/web_admin_transport/run.py --tickets`, `python3 tests/web_admin_transport/server_lifecycle.py`, `python3 tests/web_cookie_auth/run.py --admin`; manual smoke client/procedure in `tests/web_admin_transport/README.md` and `docs/phase8d5_implementation.md`. Final shutdown fix is host-tested and build-verified by the parent's sequential final `pio run`; target validation remains pending.
## Typed settings source and regression map
HTTPD reads zero-wait projections and queues only IDs to the existing dispatcher. One original-login slot per domain; canonical owners compare/reserve at execution. [API/lifetime and failure contracts](../web_administration.md#typed-settings-api-and-operation-lifetime).
| Domain | API / canonical owner | Focused source tests |
|---|---|---|
| Serial | `web_serial_settings.{c,h}` / `serial_service` | `tests/web_cookie_auth/run.py --serial-settings`, `tests/admin_console_boundary/run.py` |
| Accounts / keys | `web_account_settings.{c,h}` / `user_database` | `tests/web_cookie_auth/run.py --accounts`, `tests/admin_console_boundary/accounts.py` |
| Network | `web_network_settings.{c,h}` / `wifi_manager`, `mdns_service` | `tests/web_cookie_auth/run.py --network`, `tests/web_network_settings/run.py` |
| Display | `web_display_settings.{c,h}` / `local_status_ui` | `tests/web_cookie_auth/run.py --display` |
| Broker | `web_broker_settings.{c,h}` / `session_broker` | `tests/web_cookie_auth/run.py --broker`, `tests/session_broker_diagnostics/run.py` |
| SSH | `web_ssh_settings.{c,h}` / `ssh_transport`, `ssh_security` | `tests/web_cookie_auth/run.py --ssh`, `tests/ssh_management/run.py`, `tests/ssh_management/security.py` |
| HTTPS / reboot | `web_lifecycle_settings.{c,h}` / `web_server`, `web_security` | `tests/web_cookie_auth/run.py --lifecycle`, `tests/web_admin_transport/server_lifecycle.py` |
Shared UI regression: `tests/web_ui_session/run.py` and its domain `.cjs` fixtures / `layout.py`. These are navigation pointers, not claims of test execution.
## SSH
- **8D.21 SSH identity extension:** `ssh_security.{c,h}` owns zero-wait atomic public metadata and task-bound/nonreused identity reservations shared by direct rotate/reset, crypto/NVS outside security locks. `ssh_transport_replace_identity()` shares canonical CLI/deferred SSH combined service-before-identity admission, existing command-mutex reservation and stop→commit→conditional restart; failed stop skips mutation/start, context retained until owner retires every slot, start rejects orphan handles. `web_ssh_settings.c`/existing `web_ui.c` SSH controller/routes add P-256 fingerprint/algorithm/both generations and confirmed rotation (same256/768/96-byte bounds, manual15-second/no-replay result flow); no browser Reset/recovery/export or shell-policy change. PASS real-security+combined-owner5, runtime-retention, management5, cookie SSH7+shared/all variants, UI158 and broad HTTPS/console/transport regressions. Final100,556 RAM/1,828,573 flash (+24/+3,500), CPU160 unchanged. Full21 allowed HTTPS+SSH implementation complete; independent SSH parent review and target acceptance pending. Contracts/tests/lifetime/failure/limits: `docs/phase8d21_implementation.md`.
- **8D.19 first service slice:** `web_ssh_settings.{c,h}` adds optional admin-only GET `/api/settings/ssh`, GET/POST `/api/settings/ssh-operation`; existing dispatcher queues only IDs to one login-bound slot. `ssh_transport_get_management_snapshot()` copies published state without owner wait/stack scan; `ssh_transport_manage_current()` checks saturated service generation under existing command mutex and exact session ID under SSH lock before canonical lifecycle/external-close admission. Exhausted session slots retire instead of wrapping. `web_ui.c` adds confirmed SSH-only Settings, sticky stale selection,15-second requests/manual Check Result/Refresh.36 handlers/six sockets/no new tasks/timers/depth/stacks/assets; CPU160 and8D.18 preserved. Tests `tests/ssh_management/run.py`, cookie `--ssh`, dispatcher, lifecycle27 and UI143. Contracts/resources/target checks (unimplemented remainder dropped by user2026-09-13; existing SSH retained): `docs/phase8d19_implementation.md`. SSH slice implemented/host/build verified and independently reviewed; target sign-off pending. No additional8D.19 expansion planned.
**Responsibility:** authenticate SSH, route users to serial and administrators to the command dispatcher, and own wolfSSH lifecycle.
- Files: `src/ssh_transport.{h,c}`, `src/ssh_security.{h,c}`, `src/ssh_console.{h,c}`
- Interfaces: init/start/stop, session snapshots/disconnect/revocation, host-key replacement, counters
- Interfaces: init/start/stop, session snapshots/disconnect/revocation, host-key replacement, counters; `ssh_transport_get_management_snapshot()` / `ssh_transport_manage_current()` fence lifecycle and exact session admission. `ssh_transport_replace_identity()` reserves service before task-bound security identity across stop/commit/restart, retains context until all slots retire and rejects orphan starts. Tests: `tests/ssh_management/run.py`, `tests/ssh_management/security.py`, `tests/ssh_management/runtime.py`.
- Called by: startup, network clients, user revocation, console/local UI
- Dependencies: user database, broker, admin SSH console, secure random, wolfSSH/wolfSSL; boot start gate requires Wi-Fi and SSH security/runtime readiness, independently of HTTPS identity readiness (verified in `main.c` after accepted legacy cleanup).
- Flow: role `user` -> broker; role `admin` -> `admin_ssh_console`
@@ -152,17 +128,16 @@ This is a semantic map, not a complete file inventory. Start here, then read the
## Administration console infrastructure
**Responsibility:** provide one canonical command registry and serialized execution for UART0 and admin SSH.
**Responsibility:** provide one canonical command registry and serialized execution for UART0, admin SSH and browser admin.
- Files: `src/admin_ssh_console.{h,c}`, `src/console_input.{h,c}`, `src/console_completion.{h,c}`, `src/system_console.{h,c}`, `src/network_console.{h,c}` and all `*_console.{h,c}` modules
- Entry points: `admin_ssh_console_init()`, `admin_ssh_console_start_uart_frontend()`, command registration functions
- Called by: startup, UART0 frontend, role-`admin` SSH transport
- Called by: startup, UART0 frontend, role-`admin` SSH transport, browser admin transport
- Dependencies: ESP-IDF console/linenoise, all command handlers, user-principal currentness
- Flow: `UART0/admin SSH -> bounded request queue -> one dispatcher -> esp_console_run()`
- Flow: `UART0/admin SSH/browser admin -> bounded request queue -> one dispatcher -> esp_console_run()`
- Ownership: dispatcher is sole `esp_console_run()` caller; the SSH owner exclusively performs post-initialization wolfSSH runtime calls.
- Lifecycle: remote session tokens include slot generation; fixed output/history/prompt state is wiped immediately on idle close or after an executing handler returns. Admin SSH `exit` and Ctrl+D on an empty command line request bounded deferred self-disconnect after best-effort output draining.
- Constraint: one slow command or prompt serializes all administration. Admin SSH is unavailable until command registration and UART frontend creation complete; supported deferred actions wait only for a bounded application-buffer drain heuristic.
- 8D.4/8D.5 boundary: `admin_ssh_console_open_owned()` retains explicit-index admission; runtime SSH and browser owners use `admin_ssh_console_open_available()` for the same two slots. Copied transport-qualified identity and immutable firmware-lifetime currentness/drain/lifecycle adapters; SSH publishes its allocated console index separately from its physical SSH slot. Owners handle liveness/output; dispatcher and prompt waits additionally require owner currentness (250 ms polling plus check/scheduling latency). SSH publishes locked principal copies; consumed console output is wiped. Completion scratch is nonblockingly serialized. Browser unsupported lifecycle/account mutations are rejected before execution. Focused host command: `python3 tests/admin_console_boundary/run.py`.
## Wi-Fi
@@ -170,7 +145,6 @@ This is a semantic map, not a complete file inventory. Start here, then read the
- Files: `src/wifi_config.{h,c}`, `src/wifi_manager.{h,c}`, `src/wifi_console.{h,c}`, `src/mdns_config.{h,c}`, `src/mdns_service.{h,c}`, `src/mdns_console.{h,c}`, `src/network_console.{h,c}`
- Interfaces: config defaults/validate/load/save; manager init/start/stop/apply/reconnect/next-profile/snapshot
- Called by: startup, console, local UI, ESP event callbacks; typed Network settings uses secret-free zero-wait projections and dispatcher-owned canonical conditional mutations (8D.12/8D.13).
- Dependencies: secure random for default AP password, NVS, ESP-NETIF/Wi-Fi/events, Espressif mDNS, lwIP diagnostics
- Lifecycle: permanent manager task and bounded queue; callbacks enqueue compact events only.
- Constraint: application NVS is authoritative (`WIFI_STORAGE_RAM`); working edits are not persisted until save. Start/stop, including local controls, intentionally update the RAM `enabled_at_boot` field. Working-config copies contain PSKs and must be tightly scoped and wiped; routine status/local UI must use secret-free snapshots.