Files
ESP32_Serial_Swiss_Army_Knife/docs/phase8d_plan.md
T

199 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 8D — Incremental web administration plan
Status: **8D.08D.6 and M1 validated by explicit user sign-off. 8D.7 implemented scope validated and M2 explicitly signed off by the user on 2026-09-07 ("Jupp, sign M2 off"), superseding historical M2-open, target-pending and continuation instructions below without requiring revalidation. Full browser parity is not claimed: self/generated/key/legacy-credential and other owner-specific command restrictions remain deferred; bootstrap/recovery remain permanently UART0-only. Intermittent supported two serial + one admin web admission failures are accepted nonblocking, not fixed. Numeric memory reserves/stack margins remain unapproved. Next is separately requested 8D.8 read-only settings entry and Serial page; sign-off alone authorizes no implementation.** See the [8D.7/M2 sign-off and evidence](phase8d7_implementation.md), [8D.6 implementation record](phase8d6_implementation.md), [8D.5 implementation record](phase8d5_implementation.md), [8D.4 implementation record](phase8d4_implementation.md), [8D.3 implementation record](phase8d3_implementation.md) and [8D.0 baseline/M1 contract](phase8d_baseline.md).
This is the execution plan for [roadmap Phase 8D](roadmap.md#phase-8--role-based-users-and-administrative-access). The roadmap retains the feature/security requirements; this document defines small work units, dependencies, and release gates. The [administration test matrix](user_administration_tests.md#planned-phase-8d-integrated-web-administration) remains the final acceptance checklist.
## Why this phase is split
The previous all-in-one attempt was rolled back from `devel`. The user reports a roughly +10k/-1k-line change, repeated agent/time/context interruptions, incomplete validation, broken browser login, and severe memory pressure. These are reported symptoms, not a diagnosed root cause. The separate experimental branch is not the implementation baseline: do not merge/cherry-pick it wholesale or copy its abstractions without a separately scoped review.
Deliver three independently useful milestones before attempting feature completeness:
1. **M1: reliable browser login/logout and the existing serial UI** — 8D.08D.3.
2. **M2: browser admin shell alongside uninterrupted serial access** — 8D.48D.7.
3. **M3: guided administration, one settings domain at a time** — 8D.88D.21.
8D.22 is final integration acceptance, not the first time anyone builds or tries the firmware. Stopping after M1 or M2 is valid incremental delivery, but does not mean all of Phase 8D is complete.
## Work-unit rules
- **One numbered chunk per implementation request.** Do not interpret “continue Phase 8D” as permission to implement all remaining chunks. Select the first unblocked chunk and state its scope before editing.
- Each chunk should fit one normal agent session, including review, a bounded build, focused validation, and handoff. Plan for roughly 6090 minutes of implementation and reserve at least the final third for validation/documentation. These are scope limits, not runtime guarantees; split further before coding if the estimate does not fit.
- Aim for a few hundred changed authored lines in a small source set. An expected change above roughly 600800 authored lines, more than one new task, or several independent subsystem changes triggers a scope review and another split. This is not an incentive to compress code, omit tests, or hide generated changes.
- Declare allowed files, behavior changes, explicit exclusions, resource deltas, and acceptance checks first. Source sets below are starting points, not permission to refactor every listed module. New files must have a narrow responsibility justified by that chunk.
- Build the smallest complete increment. Internal preparatory chunks may leave unused interfaces, but must not expose half-protected routes. Do not ship an intermediate cookie-authentication route without CSRF/origin protection, currentness checks, expiry, and logout cleanup.
- Reuse subsystem APIs, the existing HTTPD ownership model, the canonical command dispatcher, and authored UI assets. Do not introduce a generic web framework, CLI-over-HTTP endpoint, second command registry, second dispatcher, task-per-request model, or speculative settings infrastructure.
- Do not rename/extract the entire `admin_ssh_console` module just to give it a generic name. Adapt the smallest necessary boundary and retain existing SSH callers. Do not regenerate `src/web_assets_data.*` or update vendored xterm dependencies as incidental work.
- Add focused counters and checks with the feature that needs them, not as a final observability project. New tests must be local/bounded and document their exact command; this repository currently has no automated host test command. Avoid scaffolding a general test platform as part of implementation.
- Run `pio run` with a finite tool timeout. A timeout is **incomplete validation**, not a pass; record it and stop rather than starting overlapping/repeated build jobs. Fix only failures caused by the chunk.
- End with a reviewed diff and handoff: implemented behavior, build result, memory delta, checks actually run, hardware checks pending, and exact next chunk. Keep `docs/agent/current-state.md` current. Never include credentials, cookies, CSRF values, tickets, or verifier material in evidence.
- A chunk is **implemented / build-verified / target-verified** as separate states. If device access is unavailable, provide the small manual checklist and mark it blocked for target validation. Do not proceed past an M1/M2 gate, or stack further runtime-changing chunks on an unverified predecessor, without an explicit user decision. Never silently promote documented tests to passed tests.
- No automatic commits, branch changes, uploads, erase, NVS migration, or imports from the abandoned branch. The user chooses commit/revert checkpoints; preserve independently reviewable diffs.
## Baseline and resource gates
8D.0 must record the actual baseline revision and configuration before resource budgets become acceptance criteria. The current memory notes report 94,532 bytes linked RAM and 1,599,765 bytes flash for an mDNS-enabled build; these are historical reference values, **not a fresh measurement or sufficient runtime headroom**.
For every chunk that changes allocation, concurrency, or routes:
- Inventory static internal RAM, normal and worst-case internal heap, PSRAM, task count/stack sizes, request/response scratch buffers, queue depth, HTTPD handlers, sockets/TLS connections, and session/ticket capacity. Record limits before increasing any of them. Cookie sessions, serial sockets, admin sockets, and outstanding tickets are different resources.
- Use UART0 `memory` to record internal/PSRAM free, minimum-free, and largest-block values at the same defined workload points: settled boot, login, serial connected, admin connected when available, full supported concurrent workload, and after repeated close/logout/reconnect. Compare both each chunk and cumulative growth against the baseline. Collect relevant task stack high-water marks where available; explicitly record missing instrumentation.
- Set numeric internal-heap/largest-block/stack reserve floors and per-chunk incremental budgets from baseline measurements and actual allocation sizes in 8D.0. No invented “safe free heap” constant and no percentage-of-total-RAM substitute. If the floor cannot be measured, mark the gate pending rather than guessing.
- Account for **two simultaneous browser WebSockets** when serial and admin coexist, plus HTTPS requests and existing SSH connections. Capacity rejection must be explicit and must not silently evict an active serial client/writer to admit an admin socket. Do not merely raise HTTPD/lwIP limits until a page happens to work.
- Prefer bounded PSRAM payload storage only where cache-disable/lifetime constraints allow it; retain required internal control structures/stacks. Record fallback behavior: opportunistic internal fallback must not consume the recovery reserve unnoticed. Optional web-admin allocation failure must leave current serial, UART0, USB, and SSH paths usable.
- Do not accept a monotonic heap leak, declining largest-block trend, watchdog/stack fault, unexplained reserve-floor violation, or unrecoverable login failure. Stop and fix/split the current chunk instead of borrowing from future budgets. Supported baud rates and client capacities must not be silently reduced to pass.
### Always-on regression smoke check
After each runtime-changing chunk: build; boot and read UART0 status/`memory`; log in through the currently supported browser authentication path; open serial, explicitly disconnect and reconnect; exercise native USB UART1 access and existing user/admin SSH routes. Add the chunk-specific checks below. Use a fixed small repetition count selected in 8D.0 for routine lifecycle checks and a longer bounded soak at milestone gates; record actual counts and durations, not just “stable.”
## M1 — Authentication without changing the rest of the UI
### 8D.0 — Baseline, browser contract, and resource budget
**Scope:** Documentation and measurement only. Read the relevant `web_server`, `web_serial_transport`, `web_ui`, user-principal, and memory-reporting paths. Confirm current login/serial operation on rolled-back `devel`; no investigation of the experimental branch is required.
**Deliver:** A short baseline record, measured resource table, supported concurrency/socket budget, and exact M1 browser contract: public login assets; protected routes; session/ticket capacities and lifetimes; absolute/idle expiry rules; capacity rejection; cookie renewal; pre-login CSRF bootstrap; strict origin policy; no-store responses; logout-versus-account-wide revocation; and browser error handling. Preserve mDNS and direct-IP access as separate host-only cookie origins. Choose the simplest bounded policy, with no Basic compatibility path by default. Explicitly list the few authentication request/response fields rather than designing all future settings APIs.
**Gate:** Existing admin and user browser login, serial data, USB, and SSH work; baseline `pio run` and target memory evidence are recorded. If the rolled-back baseline already fails login, diagnose that in a separate task before changing authentication. If target evidence is unavailable, the budget and runtime gate remain pending.
**Record:** [8D.0 baseline, browser contract, resource inventory and target checklist](phase8d_baseline.md). **Validated by user sign-off on 2026-09-05**, with tested source identified as `d8999cd4a96e477fabd392ced02d810c3cd22d0f`. Historical baseline build: 94,532 B linked RAM / 1,599,973 B flash. User-provided boot, browser, lifecycle, mixed-client and 15-minute soak/60-second cleanup samples include heap and SSH stack measurements. The user attributes SSH I/O errors to out-of-spec 460400-baud testing; see sign-off for distinction from reported UART configuration. Numeric reserve floors and incremental budgets remain open, without blocking user-approved 8D.0 closure. Do not treat the documented contract as implemented behavior.
### 8D.1 — Bounded server-side session primitives, not yet browser-facing
**Start in:** `src/web_server.{c,h}`, `src/user_database.h`, `src/secure_random.h`; add a narrowly owned session module only if needed.
**Scope:** Fixed-capacity session issue/lookup/expiry/invalidation with digest-only token storage, copied principal and authentication-generation binding, CSRF state, and secret-free capacity/expiry/invalidation counters. Decide ownership/locking explicitly because future console revocation is not necessarily on the HTTPD task. Wipe transient secrets and use existing secure randomness. No login page, HTTP auth cutover, admin route, new permanent task, or settings work.
**Gate:** Focused local/component checks for lifecycle, capacity, slot reuse, stale principals, and failed initialization; bounded storage accounting and build. Existing Basic-auth behavior remains unchanged. If no executable harness is practical, distinguish code review from target execution and carry the missing checks into 8D.3; do not claim unused code was exercised by a boot test.
**Record:** Implemented `web_session_store` plus admitted-start/stop lifecycle hooks; no route uses it yet. `python3 tests/web_session_store/run.py` and `pio run` pass. Linked RAM **95,204 B (+672 B)**, flash **1,600,505 B (+532 B)** versus 8D.0; static store/lock symbols total 664 B, no module heap allocation/new task/routes/sockets. Only init/stop are currently retained in the firmware link; host tests exercise the full production module. See the [8D.1 implementation record](phase8d_baseline.md#8d1-implementation-and-validation-record) for exact accounting, review limits and target evidence. **Target validated by user sign-off on 2026-09-05**, following boot/full-client-mix samples and a reported long-lasting command at full 115200-baud line speed with no dropped broker packets. Numeric reserve gates remain open. Stop before 8D.2 until requested.
### 8D.2 — Bind existing serial tickets and sockets to a web-session identity
**Start in:** `src/web_serial_transport.{c,h}`, `src/web_server.{c,h}`, the session module from 8D.1, and the existing revocation call sites in `src/user_console.c`.
**Scope:** Add generation-safe originating web-session identity to serial tickets/slots, specific-session cleanup, and account-wide invalidation hooks. Keep web-session identity distinct from account authentication generation and transport slot generation. Preserve authoritative currentness checks when best-effort notification fails. Keep the current Basic path working until the atomic cutover; no public cookie-auth route yet.
**Gate:** Build and existing serial regression. Exercise session-specific versus account-wide cleanup through focused checks where available: one session's logout must not disconnect another session for the same unchanged account; account mutation must invalidate all affected account sessions/tickets. Stale cleanup cannot close a reused slot. Record any dormant paths that require the next chunk's browser validation.
**Record:** [8D.2 implementation, accounting and target checklist](phase8d2_implementation.md). Distinct session IDs now bind tickets/slots; internal cleanup and account-notification hooks are present, while Basic remains the only public authentication path. Both focused host modes and `pio run` pass: **95,260 B linked RAM (+56 B)**, **1,601,925 B flash (+1,420 B)** versus 8D.1. No capacity/task/route/asset changes. **Target validated by user sign-off on 2026-09-05**, following post-flash boot and full-client-mix samples. Sanitizer execution is blocked by missing host runtime libraries; numeric reserve gates remain pending. Stop before 8D.3 until separately requested.
### 8D.3 — Atomic login/logout cutover with the unchanged serial application
**Start in:** `src/web_server.{c,h}`, `src/web_ui.{c,h}`, session primitives, and the session-bound serial integration.
**Scope:** Minimal same-origin login page, session status, explicit logout, bounded login throttling, and cookie-based authorization of the existing app/status/ticket routes. Use a host-only `__Host-` cookie with `Secure`, `HttpOnly`, `SameSite=Strict`, `Path=/`, no `Domain`, and explicit lifetime. Enforce the agreed pre-login CSRF protection plus strict origin checks on login, and session CSRF plus strict origin checks on every authenticated mutation including logout and ticket issuance. Keep passwords/tokens transient, preserve security headers/CSP, update the loader hash atomically if it changes, and handle JSON/text safely. Remove browser Basic challenges/cache authorization so stale credentials cannot bypass logout. No admin shell, settings, visual redesign, or new serial protocol.
**Gate — M1 (mandatory target/browser pause):** Both roles log in; incorrect credentials give a usable error; logout and account switching need no browser credential-cache reset. Refresh/reboot/expiry return to a usable login; no redirect loop or missing login asset. Test fresh and previously Basic-authenticated browser profiles, direct IP and mDNS where available, bounded capacity/backoff, CSRF/origin rejection, current-session logout cleanup, password/role/key changes and deletion/recreation via UART0, and unrelated-session/account isolation. Existing serial data, writer ownership, and explicit reconnect still work. Record memory before/during/after repeated login/serial/logout and simultaneous SSH/USB operation. Stop here for user confirmation before M2.
If 8D.3 exceeds the work-unit limit, first split out inert login-page rendering or private request-parsing helpers. Do not split the live security cutover into an insecure intermediate deployment.
**M1 sign-off (2026-09-06):** User explicitly completed M1 after successful both-role login, full-client-mix operation and post-soak telemetry. See [sign-off/evidence](phase8d3_implementation.md#m1-validation-sign-off-and-post-soak-evidence-2026-09-06). Minima/largest blocks are unchanged from the earlier loaded sample, with no reported transport failures. Clients remain connected; exact soak duration/revision and disconnected cleanup are not claimed. Numeric reserves stay open; unrecorded detailed checks are evidence limitations, not blockers to the user-approved milestone closure. **Wait for a separate 8D.4 request.** Older checkpoint notes below are historical.
**Live cutover checkpoint (2026-09-05):** [Implementation, HTTPD boundary, resource accounting and M1 handoff](phase8d3_implementation.md). Cookie login/logout replaces Basic for app/status/ticket routes; explicit pre-101 admission and strict header/Origin/CSRF policy use an isolated version-checked private IDF adapter, **not an SDK patch**. Resumed another agent's implementation and fixed pending-buffer wiping to preserve right-aligned unread data. All five focused suites and `pio run` pass. Final **95,508 B RAM / 1,625,689 B flash**, +248/+23,764 B versus 8D.2. No hardware/browser execution or reserve-floor approval. **Stop for mandatory M1 acceptance before 8D.4.** The following preparatory records are historical, superseded for current implementation status.
**Preparatory split (2026-09-05):** Scope review selected private request parsing first; the complete challenge/throttle/route/browser/test change exceeds the authored-line work-unit target. Added allocation-free `src/web_auth_parse.{c,h}` with no live HTTP callers: bounded canonical same-origin comparison, unique cookie extraction and strict UTF-8 login JSON decoding. `python3 tests/web_auth_parse/run.py` passes **268 cases** against production C; both existing session-store host modes pass. Final `pio run` passes in **7.50 seconds** and reports **95,260 B RAM / 1,601,925 B flash**, unchanged from 8D.2 because helpers are not live linked paths. No route/task/socket/stack-size/asset changes. No target/browser validation, runtime reserve approval or M1 completion is implied. Continue within **8D.3**, with the full atomic cutover still pending; see `docs/agent/current-state.md` for exact integration obligations.
**Login-renderer preparatory split (2026-09-05):** Added `src/web_login_ui.{c,h}` with no live caller/route, leaving Basic and existing serial UI unchanged. Standalone 7,387-byte HTML plus terminator has no protected asset dependencies, five security headers including no-store and exact script-hash CSP. Explicit-only challenge/login flow, bounded request/response handling, safe errors/manual backoff, disabled pending inputs, best-effort password/reference cleanup and generation-safe page-exit cancellation. Review fixes abort every attempt on exit (including unread error bodies) and clear re-entered passwords. `python3 tests/web_login_ui/run.py` passes production C rendering/failure checks, exact CSP hash and eight Node VM groups; parser and both session test modes also pass. Final `pio run` passes in **8.25 seconds**, unchanged **95,260 B RAM / 1,601,925 B flash**: unused renderer costs are not live-linked/runtime costs yet. No new task/socket/route/stack-size/module heap or generated-asset change. No real-browser/HTTPD/hardware validation or M1 completion. **Next is the atomic live 8D.3 cutover using both prepared pieces**, followed by the mandatory M1 target gate; no additional login-rendering split is needed.
## M2 — Reuse the admin shell, then expose it
### 8D.4 — Small transport-neutral console boundary
**Implementation checkpoint (2026-09-06):** [8D.4 implementation, resource accounting and target sign-off](phase8d4_implementation.md). Implemented / host-tested / build-verified / validated by explicit user sign-off after boot/full-client-mix evidence and successful empty Enter and soak testing. Owner callbacks and transport-qualified identity retain the existing two shared slots and SSH API, with no browser routes. Build **95,084 B RAM / 1,627,173 B flash**, **-424 / +1,448 B** versus recorded latest 8D.3. Numeric reserves remain open; unrecorded detailed checks do not reopen this user-approved closure. Wait for a separate 8D.5 request.
**Start in:** `src/admin_ssh_console.{c,h}`, `src/console_input.{c,h}`, and only the necessary `src/ssh_transport.c` callers.
**Scope:** Introduce the minimal transport identity/output/lifecycle boundary needed by a future web frontend. Retain the single dispatcher, fixed queue, line editing/history/completion/prompts, currentness checks, generation tokens, and existing SSH API compatibility where practical. Do not duplicate per-SSH buffers for hypothetical web capacity or rename the whole module. No web endpoint or second dispatcher/task.
**Gate:** UART0/admin-SSH serialization, hidden prompts, completion/history, deferred actions, disconnect/revocation with queued work, and slow-output behavior regressions pass. Memory delta is explained before adding browser slots. This refactor must stand alone and leave behavior unchanged.
### 8D.5 — Bounded admin WebSocket backend, no normal UI entry yet
**Target sign-off (2026-09-06):** User explicitly validates 8D.5 after settled cold-boot telemetry and successful 15-minute full-client-mix active-use soak at **230400 baud**, reporting a few broker drops under extremely fast/dmesg output. [Evidence and limitations](phase8d5_implementation.md#target-sign-off-2026-09-06). This supersedes the older pending acceptance/checkpoint notes below. No zero-drop claim or loaded/cleanup telemetry is inferred. Numeric reserves remain open; missing detailed results do not reopen signed-off 8D.5. Wait for a separate 8D.6 request; M2 is not yet complete.
**Combined backend checkpoint (2026-09-06):** User authorized finishing all of 8D.5, superseding the prerequisite-only pause below. Backend, shared-console allocation, protected admission, revocation, fail-before-side-effect restrictions and local/manual test tooling are **implemented / host-tested / build-verified**. All relevant host suites pass, including real cookie/store/ticket/transport endpoint integration. Parent reports the sequential final `pio run` after the HTTPD-owned shutdown/reuse fix passed at **95,580 B RAM / 1,637,273 B flash**, **23.55 s**. Deltas: **+416/+9,224 B** versus prerequisite, **+496/+10,100 B** versus 8D.4, **+1,048/+37,300 B** versus 8D.0. Final independent security integration review reported no actionable findings. **Target runtime/socket measurements, numeric reserves and acceptance remain pending.** No device operation or 8D.6/UI/M2 completion. Details and historical build evidence: [implementation record](phase8d5_implementation.md). 8D.6 onward remain separately requested work.
**Preparatory checkpoint (2026-09-06):** [8D.5 prerequisite, validation and handoff](phase8d5_implementation.md). Resumed existing uncommitted console-owner currentness/prompt cleanup work; reviewed and extended production-publication tests. Both console suites and `pio run` pass: **95,164 B RAM / 1,628,049 B flash**, **+80 / +876 B** versus recorded 8D.4. No routes/tasks/sockets/UI added. Work-unit review keeps the live backend in the next increment within 8D.5; backend/M2 remain incomplete. Target regression or explicit user decision is needed before stacking runtime changes; numeric reserves remain open.
**Start in:** The console boundary from 8D.4, `src/web_server.{c,h}`, and a small web-admin transport adapter as justified. Reuse existing HTTPD scheduling patterns without mixing admin data into the serial transport.
**Scope:** Admin-only, short-lived single-use tickets bound to both current web session and principal; bounded console admission/input/output; session expiry/logout/revocation cleanup. HTTPD owns socket work and the dispatcher owns command execution. No broker client for this route. An absent UI is not authorization: every ticket, upgrade, and sensitive operation is checked on the server. For self-affecting web actions not safely supported yet, explicitly reject before side effects and list the temporary restrictions for 8D.7.
**Gate:** Focused authenticated test-client or temporary local development-page checks (not a shipped debug endpoint): user-role rejection, admin command/output, prompt/backpressure, stale ticket/slot rejection, cleanup, and concurrent UART0/admin SSH. Admission failure does not remove the serial writer. Build and measure actual socket/console-slot cost. No generic HTTP command runner.
### 8D.6 — Browser terminal selector and serial-lease preservation
**Target sign-off (2026-09-06):** User confirms the remaining validation checks after 60-second boot/full-client-mix/partial-cleanup telemetry, and closes 8D.6. [Evidence and handoff](phase8d6_implementation.md#target-sign-off-and-evidence-2026-09-06). Full mix at **230400 baud** includes browser/admin SSH and four serial broker clients with one web writer. Internal/DMA lifetime minima **6,516 / 1,580 B** remain a numeric-reserve follow-up, not a claim of exhaustion or grounds to reopen sign-off. Exact flashed revision/browser/repetition counts/soak duration were not separately supplied. Latest cosmetic toolbar build passes in **7.60 s**, unchanged **95,580 B RAM / 1,646,489 B flash**, with 17 UI groups/CSP checks passing. Supersedes older pending notes below. Wait for separately requested **8D.7**; M2 remains open.
**Implementation checkpoint (2026-09-06, updated after review fixes):** [8D.6 implementation, accounting and target checklist](phase8d6_implementation.md). Implemented / host-tested / build-verified; target validation and parent re-review of fixes pending, no phase sign-off or M2 completion. Both P2 findings fixed: session identity changes require a clean document before adopting a new view, and fit caches only successful measurements with three bounded readiness retries. Sixteen production-rendered UI groups and focused UI/auth/store reruns pass; prior admin/console regression results remain recorded. Final finite `pio run`: **21.35 s, 95,580 B RAM / 1,646,489 B flash**, **0 / +9,216 B** versus 8D.5; cumulative **+1,048 / +46,516 B** versus 8D.0; review-only delta **0 / +1,376 B**. No backend/capacity/8D.7 restriction/asset change. Numeric reserves remain open; prior sign-offs stand. Stop for validation/user decision before separately requested 8D.7. This supersedes older planned-8D.6 status text in this document.
**Start in:** `src/web_ui.{c,h}`, using the completed serial/admin protocols.
**Scope:** Add admin-only Serial/Admin selection and separate bounded terminal state; no new settings. Keep serial connected and drained/observed while hidden. Keep writer/observer identity and Request control/Release control visible in both modes. Mode changes route displayed output and keyboard input only. Closing the admin route or shell `exit` leaves serial intact; explicit serial Disconnect retains its documented cleanup/reconnect behavior. Bound scrollback and avoid leaking browser listeners/sockets across switches.
**Gate:** Repeated switches preserve the same broker client ID and writer ID; background serial remains observed, with any bounded overflow visible rather than silently stopping observation. Ordinary users retain serial-only navigation and server-side denial. Exercise hidden prompts, line editing/history/completion, resize, admin-route reconnect, full logout, and expired-session UI. Measure simultaneous two-WebSocket plus HTTPS request headroom; no eviction to make the selector work.
### 8D.7 — Web-shell lifecycle parity and M2 acceptance
**M2 sign-off (2026-09-07):** User explicitly says "Jupp, sign M2 off" after the implemented-scope validation below and discussion of 8D.8 next. M2 is accepted with the deferred restrictions, nonblocking unresolved admission issue and unapproved numeric reserves/stack margins recorded in the current status. No full parity, new checklist passes or revalidation requirement is implied. Next is 8D.8 read-only settings entry and Serial page only upon a separate request; no implementation is authorized by this sign-off alone. The earlier checkpoints and original scope/gate below are historical planning and evidence, not outstanding conditions for M2 closure.
**Historical 8D.7 target sign-off (2026-09-07), before M2 sign-off:** User explicitly requests marking 8D.7 validated after thorough testing. Certificate rotation and web start/stop were verified, with lifecycle via UART0/SSH admin/web admin and restart after browser stop via another route. Full mix without broker drops up to 230400 baud after external adapter baud correction is user-reported. Intermittent supported two serial + one admin admission failures have recently not recurred and are accepted nonblocking, not fixed. [Evidence and limits](phase8d7_implementation.md). No detailed reboot/individual mutation checklist passes are inferred. This supersedes pending status and next-slice instructions in the historical checkpoints below for all three implemented slices, including other-account operations. Remaining self/generated/key/legacy-credential and other owner parity stays deferred/restricted; numeric reserves and M2 acceptance remained open at this earlier checkpoint.
**Second bounded certificate slice:** [Implementation, separate slice histories and pending target checklist](phase8d7_implementation.md). Exact parsed browser `web certificate rotate --force` uses a typed request-queue union and immutable owner `dispatcher_actions` mask: bounded drain/200 ms then nonblocking handoff to the existing 12 KiB dispatcher, not 4 KiB control. Pending input gating, token/principal/session revalidation and executing-slot reservation persist through execution. Transactional certificate commit → stop → start short-circuits errors and retains ownership on failed stop; SSH/UART0 unchanged. No new tasks/depth/routes/assets/stacks; target owner-mask/local-scratch accounting and stack margins unknown, host sizeof is not proof. Parent final `pio run` PASS **26.32 s, 95,580 B RAM / 1,648,061 B flash**: **0 / +1,036 B** vs first slice, **0 / +1,572 B** vs 8D.6, **+1,048 / +48,088 B** vs 8D.0. Implementer focused suites pass (transport **25**/tickets **12**, server **11**, boundary including certificate, lifecycle/policy/cookie-admin/store-serial/diff); independent reviewer reports no actionable findings. Sanitizers unavailable (missing libasan/libubsan); no hardware validation. User explicitly authorized stacking the next bounded slice: credential/account, then other owner slices. Other mutations remain blocked; target/M2 acceptance and numeric reserves pending. This supersedes the first-slice next-step/continuation-pending statement below.
**First bounded increment history (2026-09-06):** [Implementation, restrictions and target checklist](phase8d7_implementation.md). Browser `reboot` and `web stop` now use existing deferred control with final WEB session/currentness checks and discard of pending input. Other identity/network/account/SSH restrictions remain explicit. Host suites/review/build pass: **95,580 B RAM / 1,647,025 B flash**, **0 / +536 B** versus 8D.6, final build **12.44 s**. No new task/route/capacity. Target regression or explicit continuation decision pending; this is not completed 8D.7/M2. Next slice remains HTTPS identity/certificate handling, not settings. Prior sign-offs stand; numeric reserves remain open.
**Start in:** The console deferred-control boundary, web lifecycle owner, and only the affected command handlers.
**Scope:** Close the explicit 8D.5 restrictions for self-terminating web-shell operations (including HTTPS stop/identity changes and reboot where supported by the canonical registry). Reuse bounded deferred acknowledgement/close semantics; never claim application-buffer draining proves browser receipt. Prevent further input during pending actions. Preserve UART0-only bootstrap/recovery and safe policy for one-time self-generated credentials. No typed danger-zone API yet. If this requires several distinct owner changes, split them before implementation and keep unimplemented actions explicitly rejected.
**Gate — M2 (mandatory target/browser pause):** Browser/UART0/admin-SSH commands serialize with no output or hidden-prompt crossover; queued work is discarded after logout/revocation/slot reuse. Deferred acknowledgement/drain handling is bounded and reconnect behaves as documented; this is not confirmed peer receipt or a deadline for dispatcher queue residence or underlying certificate/NVS/lifecycle execution. Run simultaneous USB, serial WebSocket, admin WebSocket, user SSH, admin SSH, UART0 and UART1 traffic; verify lease retention, recovery availability, and measured memory/stack floors. Web-admin initialization/admission failure must leave M1 serial login and existing non-web paths usable. Stop for user confirmation before settings.
## M3 — Typed settings, one domain per chunk
Every row is a **separate implementation request**, not a batch. Add only the endpoints and UI needed for that row. All typed routes require current admin authorization, bounded bodies/responses, secret-safe encoding, and the established CSRF/origin/no-store policy. Do not send constructed command strings to `esp_console_run()`.
Typed operations must preserve subsystem owner/lock/persistence contracts and coexist safely with console operations. The current `admin_command_gate` is a narrow user-command wrapper, not an existing global typed-operation serializer; do not assume it solves concurrency. Specify per-domain serialization and committed-mutation notification before adding writes. Reuse a small common mechanism only when a concrete second caller needs it.
| Chunk | Bounded deliverable and starting source set | Focused acceptance gate / exclusions |
|---|---|---|
| **8D.8 — Read-only settings entry and Serial page** | Minimal admin Settings navigation, common bounded error handling, and a typed serial snapshot; start in `web_server`, `web_ui`, `serial_service.h`, `serial_config.h`. | Normal users are denied by server, unknown/oversized input fails safely, serial values match UART0. No mutations, schema generator, empty placeholder pages, or all-subsystem snapshot. |
| **8D.9 — Serial edits and persistence** | Typed framing/lifecycle and explicit apply/save/load/default/reset controls through existing serial APIs. | Invalid framing is rejected; working versus persisted state and stop/reconfigure data-discard effects are explicit. Writer ownership semantics stay unchanged; compare CLI/browser edits and reboot persistence. No quick popover yet. |
| **8D.10 — Accounts and passwords** | User list/create/delete/role/password workflows, final-admin protection, one-time generated-password handling; start in `user_database`, existing `user_console` mutation/revocation behavior, and web handlers/UI. | No raw database export, verifier fields, or UART0 recovery endpoints. Commit/invalidation behavior matches CLI, unrelated users remain connected, own-account changes have safe reconnect/credential-delivery semantics, secret fields are cleared after use. If CRUD and password UX exceed budget, split at read-only/role/delete versus create/password before editing. |
| **8D.11 — SSH authorized keys** | List fingerprints and add/delete/clear supported public keys through bounded user APIs. | Ed25519/P-256 import, maximum supported length, malformed input, duplicates, targeted revocation, and unchanged SSH authentication behavior. No private-key upload/export or host-identity management. |
| **8D.12 — Network settings without secret mutation** | Secret-free STA/AP/profile and mDNS settings, non-secret edits, and explicit persistence through `wifi_manager`, `wifi_config`, `mdns_service`/`mdns_config`. | Responses never serialize saved PSKs; validate working/persisted semantics, live hostname changes, and behavior after connection loss. No new manager/task or Wi-Fi blob migration. Split mDNS into a follow-up if needed. |
| **8D.13 — Wi-Fi secrets and connection controls** | Explicit password replacement/clear semantics, bounded transient input, profile selection/reconnect and AP policy actions using manager-owned operations. | Preserve existing secrets when fields are omitted; never prefill saved secrets; document apply/save and likely connection loss; reconnect via STA/AP and verify UART0/USB recovery. No background secret fetch or general credential export. |
| **8D.14 — Display settings** | Typed local display configuration and explicit persistence via `local_ui_config`/public UI APIs. | Validate limits, save/reboot, absent-display behavior, and concurrent buttons/CLI edits. No I2C ownership changes or electrical diagnostics UI. |
| **8D.15 — Bounded network diagnostics** | Secret-free network status and a narrowly bounded diagnostic workflow through existing network facilities. | Diagnostic start/result/cancel/expiry behavior and concurrent CLI use are bounded; callbacks do not format/send HTTP directly. No unbounded result/history buffer or new generic jobs framework. Split asynchronous ping from read-only status if necessary. |
| **8D.16 — Broker client visibility and writer transfer** | Admin-only detailed client snapshot plus explicit confirmed writer assignment using existing broker APIs; smallest broker change only if authoritative generation-safe validation is missing. | Stale/disconnected/reused target fails without changing the current lease; exactly one writer; normal users cannot obtain management details or transfer. Test concurrent USB/SSH/browser requests. No transfer on page open or selection alone. |
| **8D.17 — Serial/Wi-Fi quick popovers** | UI-only reuse of completed typed endpoints, with full-page links and shared validation; start in `web_ui`. | Hover, focus, click/tap parity, Escape/outside-click dismissal, no mutation on opening, explicit apply/save, no secret exposure. No duplicate backend or new settings scope. |
| **8D.18 — Client/writer contextual dialogs** | Reuse 8D.16 for live client popover and confirmed Active writer dialog. | Accessible pointer/keyboard/touch paths; refresh preserves explicit selection safely; stale confirmation is rejected visibly; normal users retain only ordinary status. No new writer policy. |
| **8D.19 — Ordinary service/session controls** | Typed service status and targeted disconnect/start/stop controls, excluding actions that cut off the invoking HTTPS session; start in existing service APIs and generation-safe snapshots. | Explicit scope/confirmation, stale target rejection, owner-safe execution and failure isolation. Stop/start and disconnect do not clear settings/identities. Split by service if more than one owner adaptation is necessary. |
| **8D.20 — Self-affecting service actions and reboot** | Confirmed typed operations for connection-losing HTTPS/Wi-Fi/service actions and reboot, reusing lifecycle behavior established in 8D.7/8D.13. | Acknowledgement/pending-action handling is bounded, no duplicate action on UI retry, connection loss is explained, recovery and reconnect work. No new unbounded queue or certificate/key rotation yet. |
| **8D.21 — Security/danger-zone settings** | Carefully separated HTTPS/SSH identity rotation/reset and any explicitly retained recovery-secret operation through existing security APIs. Enumerate allowed operations first; split HTTPS and SSH work into separate requests if both need owner changes. | Confirmation, secret-safe one-time responses, no routine private-key export, expected trust/fingerprint changes, bounded self-disconnect, no accidental configuration wipe. Bootstrap/unavailable-database recovery remain UART0-only. NVS encryption, secure boot, OTA, and new factory-reset semantics stay out of scope. |
Dependencies: 8D.8 establishes only the minimal typed-request/UI pattern. 8D.9 follows 8D.8; 8D.11 follows 8D.10; 8D.13 follows 8D.12; 8D.17 follows 8D.9/8D.13; 8D.18 follows 8D.16; 8D.20 follows 8D.7/8D.13/8D.19; 8D.21 follows 8D.20. Independent domains can be reordered by the user, but should not be implemented concurrently against shared `web_server`/`web_ui` files.
## 8D.22 — Final integration acceptance and documentation
No new feature work. Run the complete [Phase 8D acceptance matrix](user_administration_tests.md#planned-phase-8d-integrated-web-administration), including the accumulated per-chunk regression checks and a bounded concurrent-transport soak at supported workloads. Record the exact revision/configuration, browser(s), client mix, baud rate, test duration/cycle counts, linked RAM/flash, runtime free/minimum/largest internal heap and PSRAM, relevant stack margins, and queue/drop observations. Compare against 8D.0 and milestone measurements. Known high-load serial drops are not permission for new unbounded blocking, hidden lease loss, login failure, or resource exhaustion.
Verify optional web-session/admin/settings initialization failures preserve UART0 and native USB; admin-only failure must not disable otherwise working serial web access. Check logout/expiry/revocation across every added route, and verify no retained Basic path or direct typed endpoint bypasses policy. Review all routine snapshots/logs/DOM status for secret exposure and confirm changes did not regenerate unrelated vendor assets.
Update the roadmap and user/command documentation to distinguish completed features, explicit restrictions, build results, and tests actually passed. Update durable agent architecture/code-map notes only for implemented ownership/contracts. If a check fails, open a bounded repair chunk and rerun affected checks; do not append features or declare the whole phase done with unrecorded failures.
## Progress and next-request template
Progress: **8D.08D.6 and M1 validated by user sign-off; 8D.7 implemented scope validated and M2 explicitly signed off by the user on 2026-09-07. Deferred parity restrictions remain; numeric reserves/stack margins remain unapproved. Next is separately requested 8D.8 read-only settings entry and Serial page; no new implementation is authorized by sign-off alone.** Record incremental results in `docs/agent/current-state.md`, retaining the [baseline](phase8d_baseline.md) and cumulative resource measurements as work proceeds.
Suggested next request:
> Wait for a separate request for 8D.8 read-only settings entry and Serial page. Preserve the accepted 8D.7/M2 sign-offs without requiring revalidation, deferred browser command restrictions, permanently UART0-only bootstrap/recovery and existing resource bounds. Carry forward the accepted nonblocking, unresolved web admission issue and unapproved numeric reserves/stack margins. M2 sign-off alone does not authorize settings or another owner slice.
For later chunks:
> Work on Phase 8D.N only. First verify its prerequisites and last target-validation checkpoint. State allowed files, exclusions, resource budget, and focused checks. Split the chunk if it does not fit one session with validation time reserved. Build, report actual versus pending validation and memory deltas, update the handoff, and stop; do not continue to the next chunk.