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:
+22
-74
@@ -23,15 +23,9 @@ SSH role=admin ------> shared administration dispatcher <------ UART0
|
||||
(does not join the broker)
|
||||
```
|
||||
|
||||
## Typed Network settings (8D.12/8D.13)
|
||||
## Typed Network settings
|
||||
|
||||
`web_network_settings` provides admin-only secret-free GET snapshot and GET/POST operation routes; `web_ui.c` adds Network without changing broker/terminal ownership. Wi-Fi working/runtime projection is zero-wait under one mutex; mDNS is a separate consistent projection. SSIDs use reversible byte JSON, with explicit UTF-8-text/hex UI conversion; passwords are never returned/prefilled, only `password_configured`. Omitted passwords preserve current secrets; explicit replacement and disabled-STA clear are distinct, AP clear is always denied.
|
||||
|
||||
Only an ID enters the existing administration dispatcher. Its session/deadline revalidation precedes canonical generation-checked mutations; `wifi_manager` remains radio and reannouncement owner. Wi-Fi generation compare/merge/validation and queue-before-publication occur under its mutex; Save holds selected bytes stable, Load is stored-only without default-secret generation. mDNS independently checks generation for Set/Save/Load/Defaults and queues reannouncement; changed RAM with queue failure is explicitly reported, not rolled back. Edits require explicit Save. Next profile follows canonical enabled-priority order, not the profile editor's selected index.
|
||||
|
||||
One static login-bound pending/result slot and one firmware-lifetime one-second timer bound queued secret retention to 30 seconds plus scheduling latency. Dequeued locals wipe on return; admitted work is not hard-cancelled by logout/deadline. `accepted` means apply/owner admission, not online/DNS completion. Response delivery before disruption is not guaranteed; recovery is STA/AP inspection plus independent UART0 administration/native USB UART1 access, never automatic mutation replay. UI navigation preserves serial traffic/lease; actual network loss can disconnect network clients.
|
||||
|
||||
Bounds: 768-byte/four-receive request, 2,048-byte snapshot, 128-byte result; 27 handlers/six sockets, no task/stack-size/dispatcher-item/queue-depth/schema growth. Optional staged Network registration failures preserve unrelated routes; timer failure denies mutation without gating snapshot reads. Timer heap and target HTTPD/dispatcher stack/memory floors remain unmeasured. Full fields, registration rollback, states, UI and validation limits: `docs/phase8d12_13_implementation.md`. Browser-shell restrictions remain unchanged.
|
||||
`web_network_settings` admits bounded current-admin operations to the existing ID-only dispatcher; `wifi_manager` remains radio/event/reannouncement owner and `mdns_service` owns independent hostname persistence. Zero-wait secret-free projections never copy saved PSKs onto HTTPD. Conditional compare/merge preserves omitted secrets, queue admission precedes Wi-Fi publication, and edits require explicit Save. Wi-Fi Load is stored-only; mDNS may load deterministic defaults. SSIDs remain byte-reversible; AP clear is denied. One login-bound slot and one-second timer bound queued secret retention to 30 seconds plus scheduling latency, not cancellation after owner admission. `accepted` is not online/DNS completion. See [Network contracts](../web_administration.md#network).
|
||||
|
||||
## Startup and initialization
|
||||
|
||||
@@ -86,7 +80,7 @@ The broker drains serial input even with no clients. A full client output stream
|
||||
|
||||
Binary serial WebSocket output uses the IDF-5.5.0-pinned `web_httpd_ws_send_binary` adapter: one owner-only session-override send of a bounded header+payload copy (516 bytes of local scratch, 512-byte payload). Existing generation validation and one outstanding work item per slot remain required. Non-full sends immediately replace that session's send override with a reject-only guard, mark it closing and shut down the socket before deferred cleanup; this prevents automatic SDK control replies from reentering TLS after incomplete output. HTTPD retains TLS destruction ownership. Text/control and admin output retain the SDK sender. One send call does not imply one TLS record, packet or peer receipt; the user signed off drop-free230400-baud full-client-mix operation at160MHz. Runtime stack-margin validation remains a separate follow-up; evidence and acceptance scope are in `current-state.md`.
|
||||
|
||||
Active-client counter snapshots expose ID/type/pending/HWM/UART/queued/read/dropped. HWM is maintained independently of web tracing and counter clear seeds current occupancy. Read means transport handoff, not peer receipt. Disconnect removes the client row while global totals retain traffic and unread-output discards. Independent default-disabled web performance capture uses two fixed slot records and nonwrapping epoch/generation fences; toggles fence in-flight samples, disable freezes aggregates, clear preserves enable state. Binary-only timestamps bound reservation-entry to callback-entry before locking, synchronous send calls and completion to broker-read return; idle and intervening work prevent scheduler-only/backlog-at-completion claims. No new instrumentation allocations or scheduling/buffer changes. Exact fields, overhead and capture contracts: `docs/web_throughput_diagnostics.md`.
|
||||
Active-client counter snapshots expose ID/type/pending/HWM/UART/queued/read/dropped. HWM is maintained independently of web tracing and counter clear seeds current occupancy. Read means transport handoff, not peer receipt. Disconnect removes the client row while global totals retain traffic and unread-output discards. Independent default-disabled web performance capture uses two fixed slot records and nonwrapping epoch/generation fences; toggles fence in-flight samples, disable freezes aggregates, clear preserves enable state. Binary-only timestamps bound reservation-entry to callback-entry before locking, synchronous send calls and completion to broker-read return; idle and intervening work prevent scheduler-only/backlog-at-completion claims. No new instrumentation allocations or scheduling/buffer changes. Exact fields, overhead and capture contracts: [throughput diagnostics](../web_throughput_diagnostics.md).
|
||||
|
||||
### Clients to RS-232
|
||||
|
||||
@@ -110,51 +104,27 @@ TinyUSB callbacks enqueue/copy data and state; the transport task owns broker li
|
||||
|
||||
### HTTPS, WebSocket, and web serial
|
||||
|
||||
**8D.21 HTTPS identity extension:** The existing 8D.20 lifecycle routes/slot/controller additionally expose public stored SHA-256 fingerprint/identity generation and confirmed `rotate` with both expected generations. `web_server_replace_identity()` reserves the canonical service transition before acquiring the security identity reservation, then retains it across generation/commit and reserved stop/start. CLI rotate/reset and the deferred browser-shell owner use this same composition; direct security mutations acquire the same nonreused identity reservation. `web_security` holds its normal mutex only for admission/copies/publication during replacement, never over crypto/NVS; the reservation remains live while start copies the committed TLS pair. Generation/storage failure leaves identity and HTTPD unchanged; committed identity is never rolled back on lifecycle failure, so stored and served fingerprints can differ. Reset remains canonical CLI TLS-only recovery and starts a stopped service; ordinary stopped rotation stays stopped. No new browser reset, secrets, SSH identity action, route, task, timer or queue. Seven-field lifecycle snapshot is bounded to320 bytes; existing256-byte input/96-byte result and ACK deadlines remain. UI confirmation names the old public fingerprint and both generations, demands UART0 `web certificate info` verification of changed trust and fresh login, and shares the pending/no-replay gate with ordinary lifecycle controls. Full contracts/tests/target limits: `docs/phase8d21_implementation.md`. This extends, rather than replaces,8D.20 below.
|
||||
`web_server` owns HTTPS on port 443 with a persisted self-signed P-256 identity. `web_serial_transport` mediates two fixed WebSocket slots through the broker; HTTPD owns socket sends/close, the transport task owns broker IO. Four outstanding serial tickets, four cookie sessions, one optional admin WebSocket and six total HTTPD sockets are distinct limits; LRU is disabled. Current handler capacity is 39. Base HTTPS can serve authenticated non-WebSocket routes if optional serial/admin transport initialization fails.
|
||||
|
||||
**8D.20 integrated lifecycle (supersedes prerequisite-only status below):** `web_lifecycle_settings` admits strict current-admin cookie/Origin/CSRF stop/restart/reboot through three optional routes. One original-login operation slot and nonreused ID,256-byte/four-receive request,128-byte snapshot/96-byte result. Only after synchronous response send succeeds does HTTPD queue an ID callback; it hands that ID without waiting to the existing four-entry dispatcher. No request/fd/socket/operation pointer escapes the handler. Two-second ACK and30-second post-validation dequeue deadlines precede owner admission, not lifecycle completion. Lost HTTPD work retains one reservation until callback or successful destruction, avoiding late-work accumulation; failed destruction preserves it. Dispatcher reserves execution, revalidates original session/current admin and calls canonical generation-conditional owner APIs outside locks. Restart invalidates logins but retains its owner reservation through start; later revocation is not cancellation. Conditional reboot reserves the HTTPS generation then invokes the shared canonical `esp_restart()` primitive outside locks, with no console self-cleanup or HTTPD stop wait. UI explicit confirmation/manual results/15-second whole request fences and no replay/restore; existing Network reused.39 handlers/six sockets and unchanged tasks/stacks/timers/queue depth. Full API, uncertainty, SDK/reset audit and resource limits: `docs/phase8d20_implementation.md`.
|
||||
Cookie login/logout replaces Basic/cache. Digest-only records carry copied principals, CSRF state, absolute expiry and nonreused originating-session IDs. Strict same-origin/CSRF mutations and session/principal checks gate admission; logout invalidates its session before transport cleanup, account mutations invalidate only the affected account, and ongoing currentness is authoritative. Authentication initialization failure gates HTTPS; failed start/accepted stop wipes records. RNG/SHA/database calls run outside short spinlocks with post-call epoch/identity revalidation. [Authentication contract](../web_administration.md#authentication-and-admission).
|
||||
|
||||
**Historical 8D.20 internal owner prerequisite (superseded above):** `web_server` exposes a zero-wait scalar management snapshot and off-HTTPD generation-conditional stop/restart APIs. A firmware-lifetime saturated generation advances under the existing lifecycle mutex on every admitted canonical start/stop, including failed attempts; counter clear and repeated init do not restore old tokens. Conditional admission rejects failed cleanup, transition, absent server or exhausted generation. Reserved restart keeps `s_transitioning` true across successful stop into private start, with no unreserved stopped gap; external lifecycle calls remain outside the mutex. Stop failure skips start, partial-start cleanup failure retains ownership, and canonical stop/start remain recovery after saturation/failure. Repeated init preserves retained lifecycle errors. These APIs have no production caller yet and perform no authorization or ACK handoff; HTTP routes/UI/typed reboot are unimplemented. A future dispatcher must validate the original login and bound ACK handling before admission, never call shutdown from HTTPD or describe admitted work as cancelled by later revocation. Contract and incomplete scope: `docs/phase8d20_implementation.md`.
|
||||
`web_httpd_adapter` is the sole private IDF 5.5.0 boundary for duplicate headers, admission-before-101, consumed-scratch wiping, staged optional URI registration, combined binary sends and owner-only idle sweeps. Re-audit its version guard on SDK upgrades. HTTPD debug logging must not expose headers/tickets. `web_diagnostics` independently observes public post-TLS callbacks using six metadata records and a default-disabled 32-event ring; it cannot see preaccept/in-progress/failed TLS. [Admission diagnostics](../web_admission_diagnostics.md).
|
||||
|
||||
`web_server` runs HTTPS only on port 443 using the device-specific self-signed P-256 certificate from `web_security`. Current routes provide the UI, static assets, status, ticket issuance, and serial WebSocket upgrade.
|
||||
`web_httpd_idle` uses one one-second timer, six rows and at most one queued owner probe. Current-owner shutdown follows 15 seconds of observed ordinary idle, exempting actual WebSockets/async/pending input. Owner delays prevent hard timeout guarantees; accepted-but-lost work stays reserved until successful destruction, failed stop retains ownership. [Idle lifecycle contract](../https_idle_cleanup.md).
|
||||
|
||||
HTTPS login uses `user_database` and opaque server-side cookie sessions; Basic authentication and its cache are removed in 8D.3. No legacy credential is imported, synchronized or consulted for authentication. Both roles retain the same shipped web status/serial UI. 8D.5 adds an admin-only backend without a normal UI entry.
|
||||
`web_ui`/`web_login_ui` own authored documents/scripts and hash-bound CSP loaders; authentication documents/app are no-store. Checked-in generated xterm/logo assets are compiled, not regenerated by ordinary builds. Browser Serial/Admin/Settings navigation changes view/input only, preserving serial client/lease and hidden output draining. Session-identity changes require a clean document; pagehide/restore revalidates before exposing buffers. One shared quick-settings host/controller preserves drafts, stale selections and pending uncertainty. [Terminal and console contract](../web_administration.md#terminal-and-console-ownership).
|
||||
|
||||
`web_cookie_auth` owns login/session/logout policy: four 120-second digest-only pre-login challenges, explicit same-origin bootstrap, five credential verifications per 60-second global window, and no live-record eviction. Host-only `__Host-` Secure/HttpOnly/SameSite=Strict cookies have absolute lifetimes. Login consumes a challenge, validates bounded JSON and issues a fresh session; logout invalidates only its originating session. Mutations require CSRF and strict canonical HTTPS Origin; serial/admin upgrades require matching cookie/Origin/ticket, with admin role additionally required by the admin endpoints.
|
||||
### Browser administration and HTTPS lifecycle
|
||||
|
||||
`web_session_store` holds four static records with token/origin digests, copied principal, separate CSRF state, one-hour absolute expiry and non-reused 64-bit session IDs. These are live cookie sessions in 8D.3, with no sliding renewal. A portMUX protects short state copies/mutations; database/RNG/SHA calls occur outside it. Resolution rechecks ID/expiry after database validation; issuance also checks an invalidation epoch. Stop wipes records without resetting IDs/epochs. Only admitted HTTPS starts initialize the store; failed starts and accepted stops disable it before cleanup. Authentication/store-init failure now gates HTTPS startup rather than falling back to Basic. Sensitive views must be wiped by callers; snapshots contain only counts and storage sizes. Focused host checks live in `tests/web_session_store/`.
|
||||
`web_admin_transport`/`web_admin_tickets` add one optional admin socket, two session-bound tickets and admission to the same two remote-console slots shared with SSH, never a broker client. HTTPD owns a 1,552-byte PSRAM-only payload and IO; a 20 ms timer queues at most one poll. Current-owner shutdown avoids queued reusable socket pointers. Detach fences submissions and only successful HTTPD stop retires old work. Console/owner checks enforce currentness before sensitive work; unsupported parsed shell commands reject before effects. [Browser-shell policy](../web_administration.md#browser-shell-policy).
|
||||
|
||||
Serial tickets/slots bind to distinct originating web-session IDs; 8D.3 rejects zero instead of treating it as Basic. Trusted internal mint/upgrade callers supply the ID; bound checks also compare the session's copied principal, with no CSRF export. Mint/consume/admission/input and existing 250 ms owner checks validate session liveness/currentness. Session-specific transport revocation invalidates the store first, then clears matching tickets and flags matching reserved/active slots for existing HTTPD/broker cleanup. Account/global transport revocation now invalidates cookie records even if serial initialization failed; existing console mutation callers reach these hooks unchanged. A non-wrapping transport epoch cancels in-flight ticket publication across revocation and server detach/re-attach. Store/database checks remain authoritative if notification is missed. 8D.3 activates these checks for all browser routes, with five added authentication handlers (14 total), unchanged six HTTPS sockets and no new task.
|
||||
`web_lifecycle_settings` uses one original-login slot and send-return → nonreused-ID HTTPD callback → existing dispatcher for self-cutting HTTPS/reboot actions. Two-second ACK and 30-second dequeue bounds precede admission, not receipt/completion. Accepted-but-lost callbacks retain one reservation through failed stop; only callback/successful destruction retires it. Conditional stop/restart reserve saturated lifecycle generation; restart retains ownership through stop/start. Conditional reboot invokes canonical `esp_restart()` outside locks, never HTTPD self-stop or console-cleanup waits.
|
||||
|
||||
A WebSocket connection requires a one-time, principal-bound ticket with a maximum 30-second lifetime. Only four tickets can be outstanding; expired/stale identities are reclaimed and live capacity is rejected with 503/Retry-After, not eviction. Ticket issuance and upgrade require `Origin` matching validated Host after host-case/default-port normalization; missing Origin fails even for non-browser clients. Tickets are stored as digests, consumed before currentness validation, and are never persisted. An admitted session starts the serial service if necessary, creates a broker client, and opportunistically requests writer ownership. The web transport has two fixed session slots. Binary frames carry serial data; small text messages request or release writer ownership. HTTPD owns socket send/close operations, while the web transport task mediates broker work through bounded scheduling. The browser's combined Connect/Disconnect control closes the WebSocket and pauses automatic reconnect; after a user-paused disconnect it changes to Connect, which resumes connection attempts.
|
||||
|
||||
`web_httpd_adapter` is the sole private ESP-IDF 5.5.0 boundary. Its compile-time version guard requires review on upgrades. It validates NUL-separated parsed headers because public getters return only the first field, and rejects duplicates/ambiguous framing. The serial URI is registered as ordinary HTTP GET so cookie/ticket/principal/broker admission precedes explicit 101 and frame-handler installation; automatic IDF WebSocket routing would send 101 too early. Cleanup wipes consumed scratch but preserves right-aligned unread pending data. CMake compiles HTTPD logs above ERROR out to prevent header/ticket logging. No SDK patch or component copy exists. See `docs/phase8d3_implementation.md` for source verification, tests and pending on-wire checks.
|
||||
|
||||
`web_diagnostics` observes public synchronous HTTPS create/close callbacks without replacing socket/transport cleanup, and wraps only the four serial/admin ticket/upgrade handlers. Six always-maintained post-TLS metadata records supply a console-safe occupancy snapshot without querying HTTPD off-owner; an opt-in 32-entry numeric ring adds timing/heap/HTTPD stack samples. Firmware-lifetime connection sequences and capture epochs survive clear/restart and fence fd reuse/toggle races. No tasks/probes/event subscriptions; no authentication/request data retained. UART0/admin SSH commands never wait for HTTPD; browser policy remains unchanged. This is successful-TLS occupancy, not preaccept or failed/in-progress handshake instrumentation; exact limits/overhead in `docs/phase8d11_implementation.md`.
|
||||
|
||||
Ordinary HTTPS idle retention is independently enforced by `web_httpd_idle`: one persistent one-second ESP timer, at most one generation-qualified HTTPD work reservation and six owner-only rows. The private adapter observes IDF's all-route successful `req_new`/`req_delete` completion marker, checks actual WS/async flags and pending/readable input, and shuts down only the current expired ordinary fd after 15 seconds of observed idle. TLS-create resets reused-fd observations before diagnostic publication. No TLS cleanup override, LRU eviction, in-progress request interruption or diagnostics dependency. Stop fences submissions before destroying HTTPD; only successful stop retires queued state. Slow owner work and accepted-but-lost nonblocking UDP work preclude a hard wall-clock guarantee; loss stays bounded until successful restart. Timer preparation failure gates HTTPS start. SDK audit, tests and exact limits: `docs/https_idle_cleanup.md`.
|
||||
|
||||
Web serial initialization is failure-isolated from the base HTTPS service: if the transport cannot initialize, `web_server_init()` can still succeed and serve authenticated non-WebSocket routes.
|
||||
|
||||
`web_ui.c` contains authored index/application strings and response policy; it validates `/api/session` before connect/restore, adds explicit Sign out, and cancels stale work on 401/logout/page exit. `web_login_ui` is a standalone public page without protected-asset dependencies. Both authentication documents and app script are no-store. Its restrictive CSP contains a hard-coded hash of the inline loader, so those two must change atomically; preserve same-origin connections, no-referrer behavior, frame denial, and the existing cache policy. `web_assets_data.c` contains checked-in generated arrays for vendored compressed xterm assets and the logo. Normal builds compile these arrays directly; they do not regenerate assets.
|
||||
|
||||
### Browser admin backend
|
||||
|
||||
8D.17 adds UI-only Serial/Wi-Fi quick presentation in `web_ui.c`: one existing settings DOM host and the same validators/request controllers, never parallel quick/full editors. Quick mode logically selects Settings (terminal input disabled) while showing the previous receiving terminal behind a bounded nonmodal panel. Promotion retains the single draft/controller; dismissal clears drafts/fences reads and automatic checks, retains pending uncertainty and never replays a mutation. Full-page hover is non-destructive; explicit activation navigates the full page instead. Network quick mode hides/disables password controls and clears transient secrets, with only existing secret-free snapshots. No backend/transport responsibility changes. Contracts and validation limits: `docs/phase8d17_implementation.md`.
|
||||
|
||||
8D.8 adds an in-document admin-only Settings/Serial view and optional `GET /api/settings/serial`. It requires current cookie/principal/admin authorization, rejects bodies/queries and inherits ordinary-GET Origin/no-store policy. Eight working config/running fields fit a 256-byte response; `serial_service_get_snapshot()` takes the existing state mutex with zero wait, releasing it before encoding/send and returning unavailable on contention. No mutation, NVS, broker or socket-lifecycle operation occurs. Both hidden terminals continue draining; Settings input is disabled, refresh is explicit/single-flight with session identity checks that cannot supersede serial admission, and cancellation clears/fences the settings view. URI budget is now 17, sockets remain six/no LRU. The private adapter's startup-only exact-GET registration stages descriptor/name allocation before table publication, unlike installed IDF's public failure path; HTTPD retains normal free ownership. Only Settings uses that helper; existing registration callers remain unchanged. See `docs/phase8d8_implementation.md` for bounds, tests and target-pending evidence.
|
||||
|
||||
The 8D.6 document binds retained terminal state to its first validated username/role/session-stable CSRF tuple. Every later session adoption must match, otherwise both terminal hosts are hidden, both sockets/work are closed/fenced and a clean `/` document is required. Pagehide hides scrollback until same-session revalidation; no clearing is needed for unchanged-session restore or mode changes. Terminal-fit readiness uses successful-bounds caching and at most three generation-fenced animation-frame retries per external request, never unbounded polling.
|
||||
|
||||
8D.6 `web_ui.c` now supplies the admin-only Serial/Admin selector using this backend. Selection leaves serial and any open admin socket connected and draining; only focus, displayed terminal and keyboard destination change. Broker identity/lease and writer controls remain serial-owned in both views. Admin opens/reopens explicitly and closes independently. Two page-lifetime terminals have separate 5,000-line scrollbacks and 64 KiB callback-accounted pending output each; browser overflow is dropped with visible counts. Admin input is bounded to 4 KiB admission and 512-byte frames. Logout/expiry/page exit closes both with generation fencing and socket-listener cleanup; bfcache revalidates serial/session but never automatically reopens admin. No server policy/capacity changes or 8D.7 lifecycle parity. See `docs/phase8d6_implementation.md`; the following paragraph describes the original backend boundary, before its UI entry was added.
|
||||
|
||||
8D.5 additionally supplies `web_admin_transport` and `web_admin_tickets`: one optional admin socket, two 30-second digest-only tickets bound to current originating session/principal, the same two shared console slots, no serial broker client. Ticket POST requires cookie/Origin/CSRF/admin; ordinary GET upgrade requires cookie/Origin/admin/ticket and console admission before 101. Six total HTTPS sockets remain, LRU purge is disabled, and two routes bring the handler budget to 16. Optional admin registration/PSRAM allocation failures do not take down M1. A 20 ms ESP timer queues at most one HTTPD poll, with no new task; only HTTPD accesses the 1,552-byte PSRAM-only RX/TX payload or socket IO. Notifiers close the generation-qualified console and flag the socket. HTTPD shuts down the verified current fd directly and owns subsequent read cleanup, avoiding IDF's queued reusable `sock_db *` close race. Detach fences submissions; failed stop retains ownership, and queued state is retired only after successful HTTPD stop. Console dispatcher/prompt and owner input/output/idle checks enforce session and principal currentness. WEB supports deferred self-close only; parsed canonical policy denies unsupported lifecycle/network/account mutations before handler side effects. No normal UI entry, typed settings or lifecycle parity is included. See `docs/phase8d5_implementation.md` for validation limits and exact restrictions.
|
||||
`web_server_replace_identity` reserves service before identity and retains both through commit → reserved stop/start. Direct security and canonical CLI/browser-shell paths share task-bound nonreused identity reservations. Crypto/NVS run outside short security/service locks; commit precedes publication/wipe. Precommit failure leaves identity/HTTPD/logins unchanged; postcommit lifecycle failure never rolls back identity and can leave served/stored fingerprints different. Failed stop skips start and retains canonical recovery. Public service/security projections are separate observations, not authorization. [HTTPS ownership, generation and recovery contract](../web_administration.md#https-and-reboot).
|
||||
|
||||
### SSH
|
||||
|
||||
**8D.21 identity extension:** Existing SSH settings routes/controller/ID dispatcher slot expose atomic public stored fingerprint/generation and fixed supported ECDSA P-256 algorithm, plus rotation confirming service and identity generations. `ssh_transport_replace_identity()` takes existing command mutex with zero wait, compares service state then obtains task-bound nonreused security reservation before any stop/crypto/NVS. Canonical UART0/deferred SSH wrapper and direct security rotate/reset share admission; command mutex and identity reservation span stop→commit/publish→conditional restart. Crypto/NVS run outside security locks/spinlocks. Failed stop skips mutation/start; persistence failure can follow disconnection and attempts old-identity service recovery; committed replacement is never rolled back after restart failure. Stopped rotate stays stopped; canonical reset can recover unavailable material and start stopped SSH, without a browser Reset/recovery endpoint. Runtime owner retains context until all slots retire, frees it before clearing cleanup admission, and rejects orphan overwrites at start. wolfSSH copies caller DER; stack/candidate/live superseded key wiping retained. Public service/security snapshots are separate observations; admission compares both. Existing bounds256/768/96 bytes, login-isolated/manual15-second/no-replay result flow and shell policy unchanged; no HTTPS self-cutting ACK gate needed because HTTPS stays accessible. Changed-known_hosts verification requires trusted UART0 `ssh host-key info`. Full contract/evidence/parent-target limits: `docs/phase8d21_implementation.md`.
|
||||
|
||||
**Typed ordinary SSH controls (8D.19 first service slice):** `web_ssh_settings` owns one session-bound operation/result slot, with256-byte/four-receive JSON,768-byte safe two-row projection and96-byte result. Three optional current-admin routes use canonical cookie/Origin/CSRF protection and the existing four-entry dispatcher (IDs only), never lifecycle work on HTTPD. The dispatcher checks login/principal currentness and30-second dequeue deadline. `ssh_transport` copies only published state under its short lock, and conditional actions take the existing command mutex with zero wait before checking a saturated lifecycle generation; lifecycle comparison/admission shares that mutex with CLI. Disconnect publishes an exact SSH-ID close request under the existing SSH lock; only the owner closes sockets/wolfSSH. SSH session generations now retire exhausted slots, while a separate lifecycle generation fences stop/start ABA and survives counter clear. Failed/pending cleanup gates typed controls, retaining canonical UART0 recovery. UI confirms SSH/all-SSH/one-SSH scope, preserves stale selection without rebasing, and uses15-second bounded requests with manual result/refresh recovery, no automatic replay.36 handlers/six sockets and unchanged tasks/stacks/timers/queue depth. No invoking HTTPS-session-cutting action, web-session/USB/Wi-Fi controls or identity mutation. Full contracts, admitted-work/timeout limits and pending target checks: `docs/phase8d19_implementation.md`.
|
||||
Typed SSH settings use the existing ID dispatcher and original-login result slot, never HTTPD wolfSSH calls or owner waits. Conditional lifecycle/session controls compare a saturated service generation and exact nonreused session ID under canonical locks. `ssh_transport_replace_identity` reserves service then identity before stop, retaining the command mutex across stop → commit → conditional restart. Failed stop skips mutation/start; failed persistence may follow disconnection; committed identity is never rolled back after restart failure. Only the SSH owner frees context after all slots retire, and start rejects orphan handles. Direct security/CLI/deferred SSH callers share task-bound identity reservations; crypto/NVS run outside security locks. HTTPS remains available, so no self-cutting HTTP ACK gate is needed. [SSH contracts](../web_administration.md#ssh).
|
||||
|
||||
`ssh_transport` uses wolfSSH on port 22 with two fixed session/handshake slots. Initialization calls `wolfSSH_Init()` in the caller before task creation; after that, one owner task pinned to core 1 exclusively owns runtime contexts/sessions and wolfSSH calls. It enforces bounded handshakes, authentication attempts, receive work, and session buffers.
|
||||
|
||||
@@ -182,19 +152,11 @@ The final administrator cannot be deleted or demoted. UART0 establishes the firs
|
||||
|
||||
NVS is not encrypted. Password verifiers improve password storage, but Wi-Fi credentials and TLS/SSH private keys remain recoverable under physical flash extraction.
|
||||
|
||||
## Typed Accounts settings (8D.10)
|
||||
## Typed Accounts and Serial settings
|
||||
|
||||
**8D.11 extension:** Authorized-key operations share the Accounts slot/dispatcher and canonical database key transactions with mutation-lock account ID/generation checks. A zero-wait per-account projection returns only key slot/type/SHA256 fingerprint. Independently optional admin/Origin/CSRF JSON POST `/api/settings/accounts/keys` reads this projection; existing operation POST admits bounded public-key text or key-delete/key-clear. Three stable slots may be sparse. Successful mutations target-revoke, including self; uncertain acknowledgement never triggers automatic replay. 24 handlers, unchanged socket/task/stack-size/queue budgets. Runtime stack margins remain pending. See `docs/phase8d11_implementation.md`; older no-8D.11 statements below are historical.
|
||||
`web_account_settings` owns one login-bound slot for create/password/role/delete/key operations; HTTPD reads compact zero-wait metadata and queues only IDs. The database compares target username/account-ID/auth-generation inside its mutation lock and shares canonical invariant/commit logic. Successful commands target-revoke, including self; result loss is uncertain. A one-second timer wipes queued non-executing credentials after 30 seconds plus scheduling latency; admitted work wipes locals on return. Generated password delivery is a separate no-retained-retrieval POST, not mutation; key listing is fingerprint-only with stable sparse indices. [Accounts contracts](../web_administration.md#accounts-and-authorized-keys).
|
||||
|
||||
Current slice 2 extends the same dispatcher slot to create/password and self role/delete/password. Mutation bodies are 768 bytes/four receives; results stay secret-free, 96 bytes, replaceable and session-bound. Conditional password mutation checks identity under the canonical database mutex. A one-second firmware-lifetime ESP timer cancels/wipes non-executing queued credentials at their 30-second deadline plus timer/scheduling latency; dequeue wipes shared inputs after copying, and dispatcher-local credentials persist until admitted work returns. This is not a hard execution/erasure deadline. Separate bodyless admin/Origin/CSRF POST `/api/settings/accounts/generate-password` returns a 24-character value before any commit, without retained retrieval. UI generation has a 60-second best-effort lifetime and context-bound saved acknowledgement before separate submission; JavaScript cannot securely wipe strings. Self revocation may prevent final response/result access; disconnect/401 proves neither success nor cancellation. Browser-shell restrictions and UART0 first-admin provisioning/recovery remain unchanged. The generated endpoint is independently optionally registered, with failure isolation/restart coverage and 23 handlers. Implementation is complete, host-tested/build-verified; target validation/signoff remains pending. Parent build: 25.61 s, 95,908 B RAM / 1,694,237 B flash; timer runtime costs and stack/heap margins remain unmeasured. No task/stack/queue depth/socket expansion or 8D.11 work. Current contracts and attributed host evidence: `docs/phase8d10_implementation.md`.
|
||||
|
||||
**Historical slice 1 architecture (superseded scope/counts, retained evidence):**
|
||||
|
||||
`web_account_settings` supplies an optional admin-only compact account list and one session-bound role/delete operation/result slot, separate from Serial's slot but executed on the same dispatcher queue. HTTPD authorizes/parses/queues; the dispatcher revalidates the initiating login/admin and 30-second dequeue deadline, then calls conditional database mutations and best-effort target web/SSH revocation after success. `user_database_get_accounts()` copies at most eight username/role/ID/auth-generation records under the existing mutex with zero wait and no key/password fields. `user_database_delete_current()` and `user_database_set_role_current()` compare target identity under the mutation lock and share canonical CLI commit/invariant logic; stale selection never intentionally mutates a replacement account. Results are replaceable, not durable/idempotent, and already-admitted work can complete after logout. Self-target, create/password/generated-secret workflows remain unavailable in this slice; first-admin provisioning/recovery remain UART0-only. Accounts UI confirms mutations, retains visible stale lists/outcomes during bounded auto-check/refresh and fences navigation/session changes without changing broker ownership. Three optional routes bring HTTPD handlers to 22; six sockets/no LRU and existing tasks/stacks/queue depth remain. See `docs/phase8d10_implementation.md` for limits and pending target checks.
|
||||
|
||||
## Typed Serial settings (8D.9)
|
||||
|
||||
`web_serial_settings` admits strict bounded admin cookie/Origin/CSRF JSON into one static session-bound operation/result slot, queuing only a non-reused ID on the existing administration dispatcher. HTTPD never runs serial/NVS mutations. The dispatcher checks session/principal currentness and a 30-second dequeue deadline before canonical Apply/Start/Stop/Save/Load/Defaults/Reset APIs; already admitted work may complete after logout. A blocked dispatcher retains the slot, not a timed job cancellation. Results are login-isolated and replaceable after completion; no durable history/idempotent retry guarantee. After acknowledgement the UI checks at one-second intervals, bounded to 10 GET attempts and a 15-second overall deadline including session checks, then automatically refreshes working values for known terminal outcomes. Errors/exhaustion use manual recovery; no automatic mutation retries or navigation resumption. Settings remain visible with stale/pending labels; only Reset confirms saved-NVS overwrite. Selecting the current view is a no-op. Settings UI retains uncertain-result warnings, explicit RAM/NVS/discard explanations and both terminal sockets/lease. Two optional exact GET/POST registrations bring the URI budget to 19, with six sockets and unchanged tasks/stacks/queue depth. `/api/status` also uses the zero-wait serial snapshot and emits `running:null` when unavailable. See `docs/phase8d9_implementation.md` for resource and target-pending evidence.
|
||||
`web_serial_settings` queues bounded typed operations to the same dispatcher, retaining one original-login result and a 30-second dequeue check. Apply/Defaults are RAM-only; Save persists device working state. Reconfiguration can discard serial-service pending bytes while broker clients/lease/output remain. Snapshot reads are zero-wait and consistent; `/api/status` reports unavailable running state as null. Settings navigation preserves both terminals; bounded completion checks and manual uncertainty recovery never replay mutations. [Typed API/lifetime](../web_administration.md#typed-settings-api-and-operation-lifetime).
|
||||
|
||||
## Console architecture
|
||||
|
||||
@@ -211,17 +173,13 @@ admin SSH line editor ----/ |
|
||||
|
||||
The dispatcher is the sole caller of `esp_console_run()`, serializing UART0 and all admin SSH commands. This is required because the console registry is treated as non-reentrant, but it also means a long command or interactive prompt blocks all administration entry routes.
|
||||
|
||||
The 8D.4/8D.5 boundary retains `admin_ssh_console_open_owned()` and adds available-slot admission for runtime SSH/browser owners: copied transport-qualified slot/session/generation identity plus a firmware-lifetime immutable owner adapter. The existing two console slots are shared, not multiplied per frontend; active/executing slots cannot be replaced. Owners serialize per-session input, consume output and enforce transport liveness; completion scratch is claimed nonblockingly across owners. The existing control task calls drain/lifecycle adapters outside console locks. SSH uses generation-checked published snapshots, principal copies and its assigned console index, never wolfSSH from the control task. `SELF_CLOSE` is owner-relative; legacy SSH actions remain SSH-specific and unsupported owner actions are rejected. Dispatcher-side owner `is_current` checks run outside console locks, with full identity recheck after validation. Commands revalidate immediately before the runner; prompts revalidate before publication and after waits (250 ms polling plus check/scheduling latency), rejecting revoked submitted input and stale wakes. SSH preserves close intent through external-close consumption. Consumed output is wiped. These checks do not cancel arbitrary executing handlers or replace owner-side input/output and lifecycle validation.
|
||||
The transport-neutral boundary retains `admin_ssh_console_open_owned()` and adds available-slot admission for runtime SSH/browser owners: copied transport-qualified slot/session/generation identity plus a firmware-lifetime immutable owner adapter. The existing two console slots are shared, not multiplied per frontend; active/executing slots cannot be replaced. Owners serialize per-session input, consume output and enforce transport liveness; completion scratch is claimed nonblockingly across owners. The existing control task calls drain/lifecycle adapters outside console locks. SSH uses generation-checked published snapshots, principal copies and its assigned console index, never wolfSSH from the control task. `SELF_CLOSE` is owner-relative; legacy SSH actions remain SSH-specific and unsupported owner actions are rejected. Dispatcher-side owner `is_current` checks run outside console locks, with full identity recheck after validation. Commands revalidate immediately before the runner; prompts revalidate before publication and after waits (250 ms polling plus check/scheduling latency), rejecting revoked submitted input and stale wakes. SSH preserves close intent through external-close consumption. Consumed output is wiped. These checks do not cancel arbitrary executing handlers or replace owner-side input/output and lifecycle validation.
|
||||
|
||||
For SSH, standard output/error is redirected to the invoking session's bounded output ring. `console_input` routes visible or hidden prompts to UART0 or the active SSH session. `exit` and Ctrl+D on an empty admin SSH line use bounded deferred self-disconnect after their acknowledgement drains; role-`user` SSH remains a binary-transparent serial stream. Session tokens include slot and generation so late queued work cannot attach to a reused SSH slot. Only the SSH owner task moves ring output through wolfSSH.
|
||||
|
||||
Admin SSH `exit`, remote reboot, SSH stop/disconnect, and host-key rotate/reset use deferred control. The control task waits up to ten seconds for command state plus administration and transport application buffers to clear, then adds a short delay; this is a bounded best-effort heuristic, not peer-delivery confirmation. UART0 invokes these actions synchronously. User mutations and their revocations are not part of this mechanism. UART0 linenoise and the SSH editor consume the same manually maintained completion matcher and candidate formatter, so the two administration routes cannot drift in offered or displayed ambiguous completions; the hints can still drift from command registration and are not an authorization list.
|
||||
|
||||
The first 8D.7 slice enables browser-admin reboot and HTTPS stop through the same control task. `web stop` is deferred only for browser origin; UART0/admin SSH keep their synchronous HTTPS-stop path. WEB performs authoritative cookie/principal/token validation after drain and delay, then calls lifecycle APIs outside console locks, never socket IO. Console snapshots expose pending deferral; HTTPD discards buffered/new input observed during it and latches each frame's discard decision across payload reception/cancellation. HTTPS stop intentionally closes both browser routes.
|
||||
|
||||
The second 8D.7 slice additionally permits exact parsed browser `web certificate rotate --force`. The request queue has a typed command-line/deferred-action union with unchanged capacity. An immutable owner `dispatcher_actions` mask sends certificate work, after the bounded drain and 200 ms delay, nonblockingly to the existing 12 KiB dispatcher rather than the 4 KiB control stack; zero mask retains SSH control-task behavior. Pending input remains gated through queueing/execution. Dispatcher token/principal/session/owner revalidation and an executing-slot reservation prevent stale execution or reuse during self-detach; WEB validates currentness again before lifecycle APIs. Transactional certificate generation/persistence commits before stop → start; generation/commit error skips lifecycle calls, stop error skips start and retains HTTPD ownership, and later lifecycle failure does not roll back committed material. HTTPD alone owns socket IO. Successful restart invalidates browser sessions and both routes; certificate trust and login must be renewed, while USB/UART0/SSH remain available. Account/legacy-credential/network/restricted SSH mutations remain blocked. No new tasks, depths, routes, assets or stack sizes. Drain/acknowledgement bounds are not execution deadlines or delivery guarantees; owner-mask/local-scratch target layout and control/dispatcher stack margins remain unmeasured (host sizeof is not target proof).
|
||||
|
||||
The third 8D.7 slice permits browser other-account interactive add/password and forced delete/role mutations, with shared parsed policy at dispatcher admission and canonical-handler defense. Self-target, generated-secret, key, bootstrap and recovery workflows remain blocked. Account/owner/session/token currentness is checked after password prompts and before database API operation admission. This is operation-admission currentness, not an atomic liveness/NVS-commit guarantee: an admitted derivation/mutation may finish and target-revoke after disconnect or expiry; subsequent stale operations reject. Reconnecting administrators must inspect uncertain account outcomes rather than assume cancellation. Existing transaction cleanup, account invariants and best-effort targeted notifications remain unchanged.
|
||||
Browser stop/reboot uses this same owner-adapter control path. Exact forced certificate rotation instead uses the typed queue union and immutable `dispatcher_actions` mask to hand off after drain/200 ms to the existing 12 KiB dispatcher, not the 4 KiB control stack. Pending input is discarded through execution and an executing slot remains reserved across self-detach. Canonical shared service/identity replacement preserves commit/stop/start failure semantics. Parsed other-account interactive add/password and forced role/delete are allowed, but browser self/generated/key/recovery and restricted network/SSH shell actions are not; typed Settings has separate permissions. Post-prompt currentness is operation admission, not an atomic session-liveness/NVS guarantee. [Browser policy and recovery](../web_administration.md#browser-shell-policy).
|
||||
|
||||
## Wi-Fi and persistence
|
||||
|
||||
@@ -241,21 +199,11 @@ Persistent namespaces/blobs include:
|
||||
|
||||
Configuration modules generally choose RAM defaults without erasing incompatible storage. Security-material modules fail closed on malformed existing material and require explicit reset. OTA slots, coredump space, an NVS-key partition, and storage are reserved in `partitions.csv`; OTA, NVS encryption, coredump handling, and filesystem mounting are not implemented.
|
||||
|
||||
## Typed Display settings (8D.14)
|
||||
## Typed Display and Broker settings
|
||||
|
||||
`web_display_settings` adds admin-only bodyless GET snapshot and GET/POST operation routes. HTTPD validates bounded typed JSON and queues only an ID on the existing administration dispatcher. One login-bound slot and non-reused operation IDs follow Serial's30-second dequeue admission deadline, without a new timer; already-admitted operations may finish after logout. Results are replaceable, not durable or idempotent. The UI preserves both terminals/lease, shows Serial-style labels/values, validates timeout limits, explicitly distinguishes RAM/drafts/NVS and uses at most10 one-second result GETs/15seconds overall, then manual recovery without mutation replay.
|
||||
`web_display_settings` queues IDs; `local_status_ui` owns a nonwrapping configuration generation and zero-wait writer reservation shared with CLI/legacy Apply. NVS runs outside critical sections. Save stabilizes RAM, Load preserves fallback behavior, Reset commits before RAM publication. Buttons/diagnostic holds change independent activity state, not config generation. Settings need an available UI task, not an attached OLED. [Display contract](../web_administration.md#serial-and-display).
|
||||
|
||||
`local_status_ui` provides a zero-wait RAM/generation projection and conditional public update API. A short critical-section reservation serializes every config writer, including legacy Apply and CLI, across NVS IO performed outside the critical section. Stale nonzero generation rejects before mutation; successful RAM publication advances a nonwrapping generation and activity. Save stabilizes selected working bytes, not browser drafts. Load keeps canonical missing/incompatible-storage defaults without rewriting NVS. Reset now commits defaults before publishing RAM (CLI too); failure leaves RAM unchanged without rollback. Buttons/diagnostic holds affect independent activity, not configuration generation; renderer reads its previous consistent RAM copy during storage work. Absent panel does not prevent config when UI task is available; no panel/I2C IO is called by settings.
|
||||
|
||||
Bounds:256-byte/four-receive request,128-byte snapshot,96-byte result, one slot;30 URI handlers/six sockets, no new task/timer/stack-size/queue/schema expansion. Optional staged allocation failures retain other routes. Host tests/build pass; target and memory/stack margins pending. Full contract, resource evidence and checklist: `docs/phase8d14_implementation.md`.
|
||||
|
||||
## Typed Broker management (8D.16)
|
||||
|
||||
`web_broker_settings` provides admin-only bodyless GET management snapshot and GET/POST assignment/result routes, using Display-style bounded parsing, one login-isolated result slot and the existing typed dispatcher. Only a numeric ID is queued. Original principal/session and30-second dequeue deadline are checked before owner admission; already-admitted work can finish after logout. Optional staged registration preserves other services. Bounds:256-byte/four-receive request,2048-byte snapshot,96-byte result;33 handlers/six sockets, no new timer/task/queue/stack-size/assets changes.
|
||||
|
||||
The broker copies compact client rows and writer/lease generation together under its existing mutex with zero wait. Conditional assignment compares generation and connected non-reused target under the same force-writer lock before any effect. Lease grant/release/revoke events advance a separate saturating32-bit generation before delivery attempts, including same-writer ABA; counter clears and dropped events cannot invalidate this fence. UINT32_MAX disables conditional assignment, not ordinary transport/console recovery. Client IDs retain three slot/29 generation bits, but exhausted slots now retire until reboot rather than reusing IDs. Reboot invalidates originating web sessions. Existing unconditional force APIs and one-writer/multiple-observer policy remain unchanged.
|
||||
|
||||
Browser Settings/Broker shows safe ID/type/name/role/pending/HWM/drop rows. **8D.18** reuses this controller in8D.17's sole nonmodal quick host for admin-only Broker clients/Active writer entrances; no second DOM/draft/backend. Contextual snapshots refresh5seconds after successful completion with one active Broker controller and5-second whole-read deadline, stopping on error/uncertainty/departure. Explicit selection retains its identity and lease version across refresh; stale/absent latches cannot be cleared by later matching snapshots, only deliberate reselection. Native confirmation captures exact target/generation before session revalidation. Viewing/selecting never mutates; completion checks remain bounded to10 one-second GETs/15seconds, no mutation replay. New contextual triggers never navigate away from full-page drafts; promotion preserves selection/controller, stops live scheduling. Both terminals receive with input isolated; focused controls use guarded aria-disabled state to avoid focus loss during refresh. No diagnostic/service controls. Backend/wrap contract: `docs/phase8d16_implementation.md`; current UI/test/target limits: `docs/phase8d18_implementation.md`.
|
||||
`web_broker_settings` exposes compact zero-wait rows and confirmed writer assignment through one login-isolated slot. The broker atomically snapshots clients/writer/lease version and compares selected target/version inside the force-writer lock before effects. Three-bit slot/29-bit client generations retire rather than wrap; a separate saturated 32-bit lease generation advances before advisory event delivery and survives counter clear. Saturation blocks conditional assignment, not ordinary request/release/disconnect/recovery force. Contextual refresh never silently rebases explicit selections or clears sticky stale/absence latches; deliberate reselection is required. [Broker/context contracts](../web_administration.md#broker-and-contextual-controls).
|
||||
|
||||
## Local UI and hardware boundaries
|
||||
|
||||
@@ -282,4 +230,4 @@ Hardware diagnostics are synchronous console commands. RS-232 tests own the phys
|
||||
|
||||
`user_database_init(load_result)` has no credential input. Missing storage is persisted empty; `user_database_recover_empty()` is the unavailable-only destructive recovery API. Valid v1 user bytes load without rewriting or account changes. The private `v1_admin_marker` retains its byte position and is derived from administrator count during mutations; it is not a public bootstrap state, new role or schema change. No user migration/bootstrap/synchronization API remains.
|
||||
|
||||
`web_security` owns TLS only. A private reader validates 1,392-byte v1 `web_sec/material`, copies exact key/certificate DER, fingerprint and generation into 1,340-byte v2, commits, then publishes. Temporary v1 credential-bearing input is wiped; no public legacy credential type/getter/rotation remains. Malformed/unknown records and read/validation/commit failures fail closed, with no fallback regeneration or overwrite of rejected records. Missing material may be generated; explicit reset replaces TLS only. Downgrade to v1-only firmware is incompatible. Logical NVS replacement is not secure flash erasure. Contracts/evidence: `docs/legacy_credential_removal.md`.
|
||||
`web_security` owns TLS only. A private reader validates 1,392-byte v1 `web_sec/material`, copies exact key/certificate DER, fingerprint and generation into 1,340-byte v2, commits, then publishes. Temporary v1 credential-bearing input is wiped; no public legacy credential type/getter/rotation remains. Malformed/unknown records and read/validation/commit failures fail closed, with no fallback regeneration or overwrite of rejected records. Missing material may be generated; explicit reset replaces TLS only. Downgrade to v1-only firmware is incompatible. Logical NVS replacement is not secure flash erasure. Contracts/evidence: [legacy compatibility](../legacy_credential_removal.md).
|
||||
|
||||
+30
-56
@@ -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.8–8D.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.
|
||||
|
||||
+19
-246
@@ -1,257 +1,30 @@
|
||||
# Current project state
|
||||
|
||||
This file is working memory. Update it during active work and before handoff; do not treat it as a permanent design record.
|
||||
Working memory, not an implementation timeline. Source is authoritative; begin with [code map](code-map.md), then [architecture](architecture.md) and [decisions](design-decisions.md).
|
||||
|
||||
## Development state
|
||||
## Accepted state — 2026-09-13
|
||||
|
||||
- **8D.19 remainder dropped (2026-09-13), explicit user scope decision:** Remove unimplemented ordinary browser-session/USB control expansion from the plan; retain existing SSH controls. No firmware removal or behavior change. Supersedes older remaining19/audit-next/phase-incomplete instructions; scope closure is not target sign-off. Retained feature scope is implemented; next is later-phase target validation and8D.22 integration/documentation when requested. Updated plan,19 record and code-map; no build/device/commit action.
|
||||
- **8D.22 explicitly signed off by the user:** “Yep, I tested the firmware thats a 8d.22 signoff.” The retained Phase 8D scope is complete; earlier per-slice pending target/review/integration gates are superseded. Roadmap already records 8A/B/C as complete and target-hardware validated, so **Phase 8 is complete**. Acceptance does not manufacture individual unreported test passes.
|
||||
- [Roadmap](../roadmap.md#phase-8--role-based-users-and-administrative-access--complete) holds the completed-phase gist; [web administration](../web_administration.md) holds current API/owner/recovery contracts; [acceptance evidence](../web_administration_acceptance.md) holds the latest report and limits. The old plan/baseline/per-slice histories are consolidated, not archived as another timeline. Test READMEs now link directly to current contracts and evidence; obsolete forwarding notes were removed too.
|
||||
- Final prior production build **PASS: 100,556 B linked RAM / 1,828,573 B flash, CPU 160 MHz**. No build/test/device execution is implied by this documentation update. The prior combined binary WebSocket-send fix was separately user-accepted at **160 MHz / 230400 baud with full mix including browser admin**; preserve combined send and bounded failed-send isolation.
|
||||
- Latest loaded capture: two serial WS, USB, two SSH roles with SSH serial writer; browser admin used then closed, not active in the capture. Internal/DMA/PSRAM free **31,508 / 23,752 / 8,136,624 B**, minima **2,052 / 460 / 8,065,972 B**, largest **18,432 / 18,432 / 7,995,392 B**; SSH minimum-free stack **15,028 B**. Full boot/loaded table is in acceptance evidence. Web send/queue/protocol and SSH IO errors zero; one SSH handshake failure/session revocation. Missing latest broker/serial counters prohibit an exact zero-drop inference.
|
||||
|
||||
- **8D.21 remaining SSH identity slice implemented end-to-end (2026-09-13), host/build verified, independently reviewed; target acceptance pending:** Clean initial worktree; completed HTTPS history/shared reservations preserved. `ssh_security` adds atomic zero-wait public fingerprint/generation and task-bound nonreused reservation shared by direct rotate/reset; crypto/NVS outside security locks, commit-before-publication/wipe. `ssh_transport_replace_identity()` holds existing command mutex across service-before-identity comparison/reservation and canonical stop→persist→conditional restart, fails stop without mutation/new start, preserves stopped rotate/reset behavior and partial effects. Runtime start refuses orphan handles; failed stop retains context until owner retires all slots. Existing cleanup already freed wolfSSH sessions before broker failure—no preexisting UAF demonstrated. Existing19 routes/256-byte request/768-byte snapshot/96-byte result/ID dispatcher slot and UI now expose P-256 metadata and confirmed rotation with both generations, all-SSH/known_hosts/trusted UART0 `ssh host-key info`/partial-effect warnings,15-second/manual result/no replay; HTTPS stays accessible. No browser Reset/recovery/export, user authorized-key, deprecated shell-policy, task/timer/queue/route/assets/config/SDK/dependency changes. PASS SSH security5 including real-mbedTLS/NVS+actual combined-owner integration, runtime retention, management5, cookie SSH7+all prior variants/shared, UI158+HTML/CSP, HTTPS lifecycle44+2/security17, dispatcher/policy and broad regressions. Final production pio21.98s **100,556 RAM/1,828,573 flash (+24/+3,500 vs SSH baseline100,532/1,825,073)**; CPU160 defaults/active/generated verified, combined WS send untouched. Full21 allowed HTTPS+SSH identity implementation scope complete, not target/M3/8D.22 acceptance. Independent review found no confirmed actionable defects; installed wolfSSH copy/lifetime, owner failures and auth/UI scope audited. Reviewer reran SSH security5/retention/management/cookie SSH7/UI158/console suites/HTTPS lifecycle+security17/diff PASS. Parent final pio confirmation PASS7.15s100,556/1,828,573 B; diff check PASS. No target sign-off inferred. Full contracts/tests/resources/exclusions and hardware/runtime limits: `docs/phase8d21_implementation.md`. No upload/erase/commit/branch.
|
||||
## Follow-ups, not acceptance blockers
|
||||
|
||||
- **8D.21 HTTPS-first slice implemented end-to-end (2026-09-13), host/build verified, independently reviewed; target sign-off pending:** Preserved initial three-file audit documentation work, then completed shared `web_server_replace_identity()` service-before-identity reservation across generation/commit/stop/start, zero-wait public `web_security` fingerprint/generation projection and nonreused reservation shared by direct canonical rotate/reset. Crypto/NVS outside locks; no identity mutation before stale service rejection, no CLI/browser-shell bypass, no rollback after commit. CLI reset/recovery starts a stopped service; stopped ordinary rotation stays stopped. Existing8D.20 lifecycle routes/slot/ACK/original-login dispatcher and shared UI controller extend with confirmed `rotate`, required identity generation and320-byte seven-field snapshot. Fingerprint/both generations confirmed; trusted UART0 `web certificate info`, changed trust/fresh login, all-web-session disruption, partial-effect uncertainty,15-second UI bounds/manual results/no replay retained. No browser reset/export/recovery secrets, SSH identity work, new tasks/timers/routes/queues/assets/config/SDK/dependencies. PASS security17, lifecycle44+two integrated production-owner/real-mbedTLS/NVS fault groups, cookie lifecycle8+shared/all variants, UI156+renderer/HTML/CSP, dispatcher/console self-detach and broad regressions. Final pio22.78s **100,532 RAM/1,825,073 flash (+24/+3,568 vs audited100,508/1,821,505)**; defaults/active/generated CPU160 verified, combined WS send and prior20/18/19/throughput work preserved. Independent reviewer found no confirmed actionable findings, reran security17/lifecycle44+2/cookie lifecycle/UI/dispatcher/console lifecycle/diff PASS. Parent final pio confirmation PASS6.98s100,532/1,825,073 B; diff check PASS. Final expanded integration rerun PASS; no hardware/upload/erase/commit/branch or runtime-reserve/M3 claim. Exact ownership/wrap/failure/API/tests/resources and pending parent/target checklist: `docs/phase8d21_implementation.md`. **Chosen HTTPS slice complete; full21 incomplete, remaining SSH identity work separately requested.**
|
||||
- Extremely low internal/DMA lifetime minima warrant correlated transient-headroom investigation; overlapping capability pools and conservative/non-simultaneous region minima do not prove allocation failure. Numeric reserves, HTTPD/dispatcher stack margins, peak correlation and detailed soak/fault evidence remain unapproved/unreported. Do not reopen functional sign-off or invent a reserve threshold.
|
||||
- SDK TLS `-0x004C` is generic NET_RECV_FAILED, not OOM. Two boot auth failures plausibly involve stale cookies, but causation is unconfirmed. Prior intermittent web admission issues and accepted idle cleanup do not justify claiming every admission failure fixed.
|
||||
- Real DNS/reannouncement, NVS power-loss, browser geometry/accessibility, individual fault cases and exact duration claims require explicit evidence if investigated; retained [regression procedures](../user_administration_tests.md) are not execution records.
|
||||
- **Next: Phase 9 security/production hardening only if requested.** No source, configuration, test implementation, build, upload, erase, branch or commit is authorized by documentation consolidation.
|
||||
|
||||
- **Historical 8D.21 audit-only handoff (2026-09-13), superseded by implemented HTTPS slice above:** Read exact plan and relevant memory/source; enumerated HTTPS-first public metadata + confirmed rotation, excluded redundant healthy-material reset UI, all SSH identity work and recovery secrets/database recovery. Verified TLS-only reset additionally recovers unavailable material and starts a stopped HTTPS service at the CLI; preserve those semantics. Existing security mutex protects generation/commit but not restart; CLI and browser-shell callers commit then stop/start separately. Completion requires combined service/identity generation admission and reservation shared with canonical callers,8D.20 ACK path, complete typed API/UI and fault/concurrency/auth/no-replay tests—not unused prerequisite interfaces. Baseline `pio run` PASS7.17s100,508 RAM/1,821,505 flash; initial worktree clean. No source/test/device/config changes or regression/final-build/review/sign-off claim. Only audit handoff documentation delivered; chosen HTTPS slice and full21 both incomplete. Exact findings, exclusions and continuation checklist: `docs/phase8d21_implementation.md`. Do not mistake this entry for feature delivery; durable architecture/code-map/decisions remain unchanged because no owner contract changed.
|
||||
## Scope and safety to retain
|
||||
|
||||
- **8D.20 end-to-end integration complete (2026-09-13), host/build verified, independently reviewed; target sign-off pending:** Continuation found/preserved uncommitted backend/API/UI/tests beyond the prerequisite-only memory. Inspected and validated that integration, added send-return-at-deadline/request-storage-reuse/accepted-restart-expired-login regressions and explicit post-restart login wording. `web_lifecycle_settings.{c,h}` provides three optional current-admin routes, strict256-byte/four-receive typed stop/restart/reboot,128-byte snapshot/96-byte result, one login-bound slot/nonreused ID and one2-second ACK reservation. Synchronous send return then HTTPD ID callback then existing four-entry dispatcher, original-login/current-admin/post-validation30-second deadline, canonical generation/reserved stop/restart and narrow `esp_restart()` admission. No request/fd capture, no lifecycle on HTTPD, no lock/self-console-cleanup wait at reset. Lost HTTPD work stays singly reserved until callback or successful destruction; late/ABA work inert; failed stop retains canonical recovery. UI HTTPS/Reboot explicit confirmations,15-second whole requests, manual results, pending/unknown duplicate gate and no replay/restore; existing Network link/controller, corrected USB-vs-admin recovery wording. PASS lifecycle41, cookie lifecycle8+shared/all prior variants, dispatcher and broad console/auth/transport/store/idle/performance/SSH/Network/broker/security/parser/login/diagnostics; UI153+seven-view HTML/CSP. Final focused suites rerun after final wording. `pio run` first21.86s100,508/1,821,441; final22.85s100,508/1,821,505 B. Final delta vs pre-phase100,404/1,808,441: **+104 RAM/+13,064 flash**; vs prerequisite100,404/1,808,589: +104/+12,916. Module mutable object108 B (slot88,lock8,IDs/handle12); linked placement differs.39 handlers (36+3), six sockets; no task/timer/queue-depth/item/stack/session/ticket/heap/PSRAM/schema/config/assets/SDK/dependency/identity changes. CPU160/nonblocking HTTPD queue verified; UI18/SSH19/combined WS preserved. No device/upload/erase/commit or runtime reserve/full-M3 claim. SDK sequencing/reset inspected, not real scheduler/reset tested. Independent reviewer found no confirmed actionable defects after installed-IDF callback/reset and owner/UI audit; independently reran lifecycle41, cookie lifecycle8+shared, dispatcher, UI153+renderer/HTML/CSP and diff checks PASS. Parent final pio confirmation PASS6.82s100,508/1,821,505 B; diff check PASS. Full exact contracts, test commands/evidence, limits and pending target checklist: `docs/phase8d20_implementation.md`. The prerequisite-only entry below is historical and superseded by this integration.
|
||||
- 8D.15 dedicated typed network diagnostics was removed; shell diagnostics remain subject to frontend policy. Unimplemented 8D.19 ordinary browser-session/native-USB controls were removed; SSH settings remain. No implicit full browser-shell parity or browser identity reset/recovery/export.
|
||||
- One UART1 broker writer, isolated observers and binary transparency. UART0 is administrative recovery; native USB is network-independent UART1, not administration or uninterrupted reboot.
|
||||
- Typed operations carry original-login IDs to the existing dispatcher. Owner-reserved generations fence stale/ABA changes; later revocation/timeout does not cancel admitted work. HTTPS commits before stop/restart; SSH stops before commit/restart; committed identity never rolls back on lifecycle failure. Lost ACK/result means uncertainty, never automatic replay.
|
||||
- Preserve private IDF HTTPD version guards, at-most-one owner-work reservations through failed destruction, retained SSH context until all slots retire, bounded queues/buffers and secret-free metadata. Canonical recovery survives conditional-token exhaustion.
|
||||
|
||||
- **Historical 8D.20 lifecycle-owner prerequisite (2026-09-13), superseded by integration above:** Scope split before editing under plan600–800-line rule after finding missing HTTPS generation admission and stop/start reservation. `web_server.{c,h}` adds zero-wait secret-free management snapshot, conditional stop/restart under canonical mutex, saturated lifecycle generation across CLI ABA/counter clear and continuously reserved restart. Failed stop skips start; partial cleanup retains ownership and canonical recovery. Repeated init no longer clears retained invalid-state lifecycle failure. No new HTTP routes/UI/ACK handoff/typed reboot; these remain next authorized8D.20 integration, not complete phase delivery. Existing Network Wi-Fi controls must be reused; unrelated19 other-service and21 excluded. Initial worktree clean. PASS lifecycle34 (27+7 owner groups), broad console/auth all variants/UI143/SSH/Network/broker/store/transports/idle/performance/security/login/parser/diagnostics and diff check. Baseline pio21.10s100,404 RAM/1,808,441 flash → post-production35.05s100,404/1,808,589 (+0/+148 B); final pio20.02s same sizes, lifecycle34/diff confirmation PASS. Object generation4 B; unused public management entry points linker-discarded, so not full feature cost. CPU160 active/default/generated verified; UI18/SSH19 and combined WS source unchanged. No new task/timer/queue/depth/stack/socket/route/heap/assets/SDK/dependency/config/secret/device/upload/erase/commit changes. Self-review only; no independent reviewer tool, parent review pending. Target/runtime reserves/M3 pending. Exact owner contract, seven new fault groups, split rationale, remaining ACK/API/UI/reboot scope and target checklist: `docs/phase8d20_implementation.md`. Earlier no8D.20 wording below is historical;19 remains SSH-only completed/reviewed, other-service slice unimplemented.
|
||||
## Documentation handoff
|
||||
|
||||
- **8D.19 first service slice — SSH (2026-09-13), user-authorized; implemented/host/build verified, independently reviewed; target sign-off pending:** Admin Settings/SSH provides safe two-row status and confirmed SSH start/stop/one-session disconnect only. `web_ssh_settings` owns256-byte/four-receive request,768-byte snapshot,96-byte result and one login-bound slot/30-second dequeue deadline; existing four-entry dispatcher receives IDs only. SSH owns zero-wait published snapshot, command-mutex conditional lifecycle admission with saturated service generation (CLI stop/start ABA included), exact-ID external close and retired exhausted session slots. HTTPD never waits for SSH/wolfSSH; no new task/timer/stack/depth/socket/asset/config/dependency.36 handlers/six sockets. UI uses existing Settings styles/native confirmations, sticky stale target selection,15-second whole-request deadline and explicit manual Check Result then Refresh (no polling/replay). Invoking HTTPS login/browser terminals/Wi-Fi/USB/UART0 are not stopped. Canonical browser-shell restrictions unchanged. All web-session/HTTPS/USB actions excluded; other-service8D.19 requires separate owner/scope audit, phase incomplete; no8D.20/21. Initial worktree clean. PASS canonical SSH4, cookie SSH6+shared/all existing variants, dispatcher, lifecycle27, UI143+six-view HTML/CSP, broad accounts/Network/broker/policy/auth/transport/store/idle/diagnostics/performance/security/login and diff check. Independent reviewer found no confirmed actionable defects; canonical SSH4, cookie SSH6+shared, dispatcher, lifecycle27, UI143+renderer/HTML/CSP and diff checks independently PASS. Parent final pio confirmation PASS19.95s100,404/1,808,441 B; diff check PASS. Baseline pio6.87s100,300 RAM/1,793,301 flash; final35.31s100,404/1,808,441 (+104/+15,140 B). CPU160 defaults/active/generated confirmed;8D.18 contexts/combined binary WS path preserved. No hardware/real-browser geometry/heap/stack validation, upload/erase/commit or target/reserve/M3 sign-off. Exact API/owner race/uncertainty/resource/test limits and pending checks: `docs/phase8d19_implementation.md`.
|
||||
Initial Git status was clean. This task changes root `README.md`, `docs/` and five test-directory READMEs; executable source/tests/config/generated assets remain untouched. Independent documentation review checked acceptance scope, owner contracts and local links. It restored explicit pointer-backed HTTPD response-header lifetime and same-version SDK-patch audit warnings, updated test README links, and removed obsolete forwarding notes without reopening sign-off.
|
||||
|
||||
- **8D.18 implemented (2026-09-13), user-authorized; host/build verified, independently reviewed; target sign-off pending:** UI-only Broker clients/Active writer contexts reuse8D.16 snapshot/lease fence/operation controller and8D.17 single host/native confirmation/dismissal focus fix.5-second-after-success single-flight live refresh with5-second whole-read deadline; errors/timeouts stop, no auto error/mutation retry. Explicit selection identity+lease token is never rebased; stale/absent latches prevent resurrection, consumed selection requires explicit reselection. New triggers preserve full-page Serial/account/Network drafts even on activation; quick promotion preserves selection/controller and stops live timer. Focused controls use aria-disabled plus existing handler guards rather than native focus loss; previous terminals keep receiving with input isolated. Initial worktree clean. PASS135 UI groups+renderer/HTML/CSP, broker management/wrap, cookie Broker+base/all existing variants, lifecycle25, dispatcher/canonical accounts/Network, parser294, transports/tickets/store, idle/diagnostics/performance/security/login and diff check. Broad tests preceded final focus refinement; full UI/build rerun afterward. Independent reviewer found no confirmed actionable defects and reran UI135+renderer/HTML/CSP, canonical broker management/wrap, cookie Broker and diff checks PASS. Parent final pio confirmation PASS6.89s100,300/1,793,301 B; diff check PASS. Native picker during live updates and actual blank/reselect remain browser-validation gaps, not proven defects. Baseline pio7.02s100,300 RAM/1,788,661 flash; final21.52s100,300/1,793,301 (+0/+4,640 B). CPU160 defaults/active/generated verified, combined WS send unchanged. No backend/API/policy/assets/config/dependencies/icons/8D.15/19/later/upload/erase/commit/SDK changes. No real-browser geometry/accessibility or device tests this phase; no reserve/M3 claim. Exact contract, test-development corrections and pending parent/target checklist: `docs/phase8d18_implementation.md`. Stop before any separately authorized next phase.
|
||||
|
||||
- **8D.17 independent-review P2 fixed (2026-09-13), independently confirmed resolved:** Quick dismissal now restores Serial/Admin state/input without xterm focus; automatic departure and adjacent-trigger switches preserve focus, Escape/Close focus only trigger. Observable Terminal.focus harness reproduced adjacent-trigger failure before fix;128 UI groups+renderer/HTML/CSP PASS, pio21.90s100,300 RAM/1,788,661 flash (+0/+32 B review delta; phase+0/+6,048 B), diff check PASS. Two new groups cover adjacent keyboard triggers and timed/outside-pointer dismissal/explicit focus for both terminal backgrounds. Existing uncommitted work preserved; no unrelated source/device/assets changes. Reviewer confirmed no new actionable findings and independently reran128 UI groups+renderer/HTML/CSP and diff checks PASS. Parent final build confirmation PASS6.98s,100,300/1,788,661 B; diff check PASS. Exact correction in `docs/phase8d17_implementation.md`; target/geometry limits remain.
|
||||
|
||||
- **8D.17 implemented (2026-09-13), user-authorized; host/build verified, independent parent review and target sign-off pending:** UI-only Serial/Wi-Fi quick popovers reuse the sole settings DOM/validators/controllers and typed endpoints. Hover/focus/click/tap, Escape/outside dismissal, bounded scrollable panel, full-page promotion, explicit Apply/Save; Network excludes password controls and clears transient input. Full-page hover cannot steal drafts; focused quick editor resists unrelated hover. Close fences reads/checks, preserves pending uncertainty/no replay; both terminals drain with selected input disabled, previous terminal restored on close. Worktree initially clean. PASS126 UI groups+renderer/HTML/CSP, cookie base/all existing variants, lifecycle25, dispatcher/canonical accounts/Network/broker, parser, transports/tickets/store, idle/diagnostics/performance/security/login regressions and diff check. Optional Chromium18-case geometry attempt failed before measurement (sandbox process-singleton socket/Crash Reports), no geometry pass. New test Serial ack fixture schema fixed and final suite/build passed. Baseline pio6.88s100,300 RAM/1,782,613 flash; final21.74s100,300/1,788,629 (+0/+6,016 B). CPU160 defaults/active/generated confirmed; signed-off combined WS/Broker8D.16/Display unchanged. No backend/API/assets/config/dependencies/icons/8D.18/8D.15/upload/erase/commit. Exact ownership, tests/limits/resources and target checklist: `docs/phase8d17_implementation.md`. Parent will independently review; no target/reserve/M3 sign-off.
|
||||
|
||||
- **8D.16 implemented (2026-09-13), user-authorized; host/build verified, independently reviewed; target sign-off pending:** Admin Settings/Broker has bounded detailed client rows and explicit confirmed writer assignment, no view/selection mutation, no8D.17/18 popovers/icons. `web_broker_settings` uses256-byte/four-receive requests,2048-byte snapshot,96-byte result, one login-isolated slot and existing typed dispatcher IDs/30-second dequeue deadline; no new timer/task/queue/stack/assets. Broker owns atomic zero-wait client/writer/lease snapshot and conditional generation+target check under force lock;29-bit client generations retire slots rather than wrap, separate saturated32-bit lease generation fences ABA/counter clears/dropped events, ordinary recovery retained.33 handlers/six sockets. PASS cookie Broker6+shared, canonical broker7+2 management/wrap, dispatcher, lifecycle25, UI119+five-view HTML/CSP, all existing cookie variants, canonical Network/accounts, parser294, console lifecycle/policy, transport/tickets/store, idle/diagnostics/performance/login and diff checks. Baseline pio7.14s100,196 RAM/1,765,233 flash; substantive-source final20.54s100,300/1,782,613 (+104/+17,380 B); final post-review confirmation21.20s unchanged. Initial worktree clean; CPU160 confirmed defaults/active/generated, signed-off combined WS send untouched. Tests fixed row-schema count and harness extraction/capacity/count issues. Independent final review found no actionable findings; reviewer reran broker7+2, cookie Broker/Display, dispatcher, lifecycle25, UI119+C/HTML/CSP, throughput and diff checks PASS. Parent final pio confirmation PASS6.93s at100,300/1,782,613 B; diff check PASS. Target/full-mix/HTTPD-dispatcher margins and user sign-off pending; no reserve/M3 claim. Exact API/wrap/resources/test limits and target checklist: `docs/phase8d16_implementation.md`. No upload/erase/commit/SDK changes. Older next8D.16 authorization wording below is historical.
|
||||
|
||||
- **8D.15 removed from plan (2026-09-09), explicit user scope decision:** Network diagnostics stay exclusive to the admin shell; no dedicated typed diagnostics endpoints/settings UI. Removed the planned deliverable from `docs/phase8d_plan.md`; retain later phase numbers, so next planned chunk after8D.14 is8D.16, only on separate request. Existing shell permissions and implemented Network settings/status unchanged. Documentation only; no firmware/build/device action or8D.14 target sign-off implied.
|
||||
|
||||
- **8D.14 Display implementation complete (2026-09-09), user-authorized; host-tested/build-verified, target pending:** Admin Settings → Display supplies typed dim/off0–86400s, Apply/Save/Load/Defaults/Reset, Serial-style label/value presentation and bounded result polling. `web_display_settings` owns one secret-free login-bound slot, 256-byte/four-receive requests,128-byte snapshot/96-byte results; IDs use the existing dispatcher. `local_status_ui` owns nonwrapping config generation and zero-wait reservation across NVS outside critical sections; CLI shares the gate, buttons retain independent activity/reprobe behavior. Reset now commits defaults before RAM publication (CLI too), eliminating rollback overwrite. Absent panel does not gate config if UI task is available; no I2C/renderer ownership change.30 handlers/six sockets, no new task/timer/stack size/queue/schema/assets changes.30-second dequeue admission deadline, not timed cancellation; admitted work may finish after logout. PASS Display7+shared auth, UI111+C/HTML/CSP, lifecycle23, dispatcher/accounts/policy, cookie all variants, canonical Network, parser294, transports/tickets/store/idle/diagnostics/throughput/broker/login regressions and diff check. Separate self-review fixed strict result status/action/replacement handling. Independent final review found no actionable findings and independently reran Display7+shared auth, dispatcher boundary, lifecycle23, UI111+C/HTML/CSP and diff checks PASS. Parent final pio confirmation PASS9.08s, unchanged100,196/1,765,233 B; diff check PASS. Actual pre-edit pio12.10s100,100 RAM/1,748,513 flash; final pio26.09s100,196/1,765,233 (+96/+16,720 B). CPU160 confirmed in defaults/active/generated configuration; throughput fix and user sign-off preserved. Initial worktree reported clean. Optional Chromium geometry attempt blocked by sandbox socket/crash-report restrictions; no geometry pass. Target display/save-reboot/absent-panel/buttons-concurrency/full-mix/heap/HTTPD-dispatcher margins and sign-off pending, no reserve approval or full M3 claim. Exact contracts/tests/resources/checklist: `docs/phase8d14_implementation.md`. No upload/erase/commit or later phase; older no-8D.14 authorization statements below are historical.
|
||||
|
||||
- **Throughput fix signed off at160MHz (2026-09-08):** User explicitly validates drop-free230400-baud operation with the full client mix, including browser admin, at160MHz. Current defaults, active sdkconfig and generated sdkconfig.h independently confirm160MHz selection. This supersedes earlier frequency uncertainty and pending functional throughput validation below. Retain combined binary header/payload TLS send and bounded failure isolation; no further tuning needed for this issue. Latest full-mix160MHz result is user-reported without additional raw counters or duration; no invented long-soak, peer-byte comparison or HTTPD-stack reserve validation. Documentation-only acceptance update; no source/config/build/upload/erase/commit action.
|
||||
|
||||
- **Combined binary WS send functionally validated by user (2026-09-08):** User confirms no drops with browser admin also connected and explicitly signs off. Prior detailed two-browser/SSH/USB capture (browser admin absent) had UART71,292 B, all four clients read71,292/drop0, global285,168, web430 frames/142,584 B, HWM2,499/2,563; send averages4,215/4,522us and callback5,691/6,253us, no transport errors/disconnects. Full-mix follow-up is user-reported, no additional counters/duration supplied. Supersedes target-pending functional status below; no sustained-soak/HTTPD-stack reserve approval inferred. User also reports commenting out CPU240 default and assumes160MHz; inspection shows tracked default commented but active sdkconfig and generated sdkconfig.h STILL select240MHz. Thus full-mix functional sign-off stands, but160MHz operation is not verified. Preserve user config edit; no automatic rollback/build/upload. Next optional step is explicit160MHz selection in defaults and active config, rebuild/generated-setting verification, then target retest. Documentation-only sign-off update.
|
||||
|
||||
- **Combined binary WS send experiment implemented (2026-09-08), target pending:** User authorized after CPU240 target run still dropped6,318 browser bytes. `web_httpd_adapter` now assembles FIN/binary/unmasked header+payload in516 B owner-local scratch, sends once through the existing HTTPS session override; serial binary only, controls/admin remain SDK path. IDF5.5.0-pinned owner/current-context/upgraded/nonclosing/nonasync checks,512 B payload/one pending slot/generation fencing retained. Full-length return required; short/zero/error installs reject-only send override BEFORE ws_close/shutdown so SDK automatic PONG/CLOSE cannot reenter TLS with different arguments before deferred cleanup, even if shutdown fails. Independent review found that race in initial implementation; implementer fixed, parent inspected correction and reran performance suite PASS. No retry/replay; normal HTTPD owns TLS destruction. One TLS API call is not a guarantee of one TLS record/packet or peer receipt. No priority/affinity/TCP/buffer/task/config/assets changes; CPU240 retained. Implementer final pio run PASS22.08s,100,100 B RAM/1,748,513 B flash (+352 vs CPU-only baseline); helper local stack frame560 B, runtime HTTPD margin unmeasured. Implementer tests PASS performance (513 installed-SDK wire comparisons, short/zero/TLS errors, actual SDK automatic PING/CLOSE after failure including shutdown failure, fd reuse/slot isolation/diagnostic epochs), cookie base/settings/admin, idle18, lifecycle21, store serial. Parent performance/diff check PASS. Host TLS/scheduler doubles are not target timing/fault proof. No upload/erase/commit. Next: same settled full-mix capture and dmesg burst at240MHz, memory and HTTPD stack margin, then sustained traffic; no throughput fix claimed.
|
||||
|
||||
- **CPU240-only target result (2026-09-08):** UART71,311 B/no loss/errors; SSH/USB each71,311 read/drop0, HWM704/449. Web10 read68,504/drop2,807; web11 read67,800/drop3,511; both HWM4096, pending0. Global278,926 read+6,318 dropped=4×71,311; web277 frames/136,304 B equals browser reads. No connect/disconnect or send/queue/protocol errors;17 authenticated status requests. Callback wait avg11,704/11,939us, send8,292/8,547us, first drain3,332/3,257us. DCD116/RI4 transitions retained without diagnosis. Roughly68% fewer dropped copies than160MHz paired run, but still overflowing: frequency-only not a fix or proof CPU is dominant. User authorized combined-header/payload experiment above; supersedes prior target-pending/next-flash CPU entry below.
|
||||
|
||||
- **240 MHz-only throughput experiment prepared (2026-09-08), user authorized; target pending:** Set `CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y` in tracked `sdkconfig.defaults` and updated the existing ignored `sdkconfig.esp32-s3-devkitc-1-n16r8` selection/compatibility aliases so the current build actually changes from160 to240MHz. Generated `.pio/build/esp32-s3-devkitc-1-n16r8/config/sdkconfig.h` confirms CPU240 and FreeRTOS100Hz. No transport, priority, affinity, buffer, TCP, asset or task changes. `pio run` PASS56.42s,100,100 B RAM/1,748,161 B flash (unchanged); existing nonfatal SDK FATFS_PRINT_FLOAT bool-default warning. No upload/erase/commit or target throughput-fix claim. Higher power/temperature possible. Next: flash normally, establish and settle full mix including two web serial clients and both admins, then repeat quiet UART0 performance capture and same dmesg burst; compare drops/HWM/send/wait timings and memory, then longer sustained output. Defaults alone do not override an existing sdkconfig on other worktrees; explicitly update its CPU choice too. Rollback requires160MHz in both defaults and active config, then rebuild.
|
||||
|
||||
- **Paired 160 MHz target throughput evidence (2026-09-08):** Full mix UART71,317 B, serial loss/errors0; SSH/USB each read71,317/drop0/HWM704/640, web10 read61,801/drop9,516 and web11 read61,234/drop10,083, both HWM4096. Global queued/read265,669/drop19,599; web253 frames/123,035 B matches browser reads, send/queue/protocol errors0; no disconnects,14 status requests only. Web callback wait averages12,954/13,278us, sends9,315/9,134us, first drain3,422/3,624us. Paired one-browser run UART71,292 B, SSH/USB/web11 each read71,292/drop0, HWM706/449/1536; global213,876/drop0, web222 frames/71,292 B, wait3,338us/send7,604us/first drain2,709us, errors0. One browser disconnected after capture began,20 status requests and one denied writer request: not perfectly settled, but byte accounting exact. Both admin routes retained per user. Loss localized to browser broker queues under shared HTTPD service deficit; elapsed send time is not CPU usage and successful sends are not peer receipt. Idle-inclusive maxima (~0.95s two-browser/~2.59s one-browser) are not scheduler-stall evidence. CPU saturation/TCP blocking not distinguished; single-variable frequency experiment now authorized, superseding diagnostic-only/no-target-evidence wording below. Do not enlarge queues or alter affinity/priority/TCP alongside it.
|
||||
|
||||
- **Web throughput diagnostics handoff (2026-09-08), target unmeasured:** Broker active-client ID/type/pending/HWM/UART/queued/read/dropped rows and independent default-disabled `web performance enable|disable|show|clear` now documented against source. Two fixed web slots, epoch/generation fencing, binary-only queue-entry/callback, synchronous-send and completion/drain timing aggregates; nonempty intervals include idle and are not scheduler-only or proof of completion-time backlog. No CPU160MHz/scheduling/priority/4096-byte broker/512-byte web buffer tuning or new instrumentation allocations. User burst71,292 × four=285,168 expected,265,852 queued,19,316 dropped; browser142,584−123,268 matches, without reported UART loss/disconnect/new login. One-browser no-drop report is not a measured paired sample. Parent broker7/performance suite, console boundary/lifecycle/policy (new SSH-allowed/browser-denied command tests), transport25/tickets12, server lifecycle21, admission diagnostics12+guard, cookie Network+shared PASS; review no confirmed actionable bugs. Parent pio run PASS9.07s,100,100 B RAM/1,748,161 B flash (+552/+3,836 vs Settings presentation), diff check PASS. Fixed extracted console fixture missing new handler double; no production behavior fix. Host sanitizer libraries missing, no sanitizer pass. Real scheduling/overhead/stack margins unmeasured. UART0 quiet/settled full-mix and one-browser capture recipe, before-disconnect attribution, enabled/disabled comparison, 177.8ms retention scale and limits: `docs/web_throughput_diagnostics.md`. Documentation-only pass; no source/tests/assets/device/build/commit action.
|
||||
|
||||
- **8D.12/8D.13 + Settings presentation functionally signed off (2026-09-08):** User explicitly accepts after 60-second boot/full-mix telemetry. Web writer8, web17/SSH10/USB11 observers, both admins, two cookies at230400 8N1 RTS/CTS DTR active. Web send/queue/protocol/close-failure0, SSH handshake/auth/IO failures0; retain SSH rejected1 byte, web rejected1 frame/1 byte, serial connect3/disconnect1, cookie login3/logout1, authfailures2 boot/3 loaded without causal diagnosis. Loaded free internal/DMA/PSRAM31,512/23,756/8,112,140 B; minima2,276/156/8,070,736 B; largest20,480/20,480/7,995,392 B. SSH stack min18,468 boot/16,276 loaded. Exact samples/counters/limits: `docs/phase8d12_13_implementation.md`. Supersedes target-pending/no-signoff wording below for implemented phases and visual refinements. Very low lifetime internal/DMA minima remain transient-headroom follow-up (conservative non-simultaneous region sums, not proof allocation failed); HTTPD/dispatcher floors/peak correlation and numeric reserves unapproved, not reopening functional acceptance. No invented individual checklist/soak/full-M3 pass, no 8D.14 authorization. Documentation only, no source/config/build/test/device/assets/commit action.
|
||||
|
||||
- **Settings presentation unified (2026-09-08), user-requested visual refinement:** Accounts/Network now use Serial-style 600px label/value definition lists, shared form styling, compact muted help, consistent Refresh/result labels and action grouping. Public-key textarea/generated-password fields styled; checkboxes intrinsic-width. Safe DOM text replaces preformatted summaries; readable ASCII SSID/hex fallback retained, pre-wrap preserves significant spaces (review finding fixed with rendered-width regression). IDs/events/auth/mutations/secret cleanup/terminal ownership unchanged; no backend/assets changes. Parent UI100 behavior groups plus HTML/renderer/CSP and headless Chromium geometry/whitespace checks at320/600/1200px PASS; pio run PASS23.69s, 99,548 B RAM / 1,744,325 B flash (+0/+1,744 vs preceding ASCII-summary build). Diff check PASS. Fixtures/browser layout checks are not target visual sign-off or live Wi-Fi validation. No upload/erase/commit.
|
||||
|
||||
- **8D.12/8D.13 implemented together by user authorization (2026-09-08); host-tested/build-verified, target pending:** Backend and admin Network UI deliver 8D.12 nonsecret STA/AP/profile/mDNS edits and explicit persistence, then 8D.13 secret replacement/disabled-STA clear and connection controls. Exact API, byte SSID/UTF-8+hex UI, owner/generation/persistence/uncertainty contracts: `docs/phase8d12_13_implementation.md`. One 768-byte request, 2,048-byte snapshot, 128-byte result; one login-bound slot and one-second ESP timer, 30-second non-executing expiry plus scheduling latency, not hard cancellation. Existing dispatcher IDs/manager owner; accepted != online. Wi-Fi Load stored-only, no reset/default-secret/export; mDNS separate generation/Set/Save/Load/Defaults/reannouncement. 27 handlers/six sockets, no stack/task/queue/schema growth; staged optional route failures preserve unrelated routes. Reported backend/cookie Network PASS; backend P3 queue-drop-counter fix complete; UI agent97+renderer/CSP/review PASS; lifecycle agent21 PASS. Parent final reruns PASS Network five production-path groups, cookie Network five+shared/accounts/serial-settings/admin, console boundary/canonical accounts, lifecycle21, UI97+CSP, idle18+guards, transport25/tickets12, store--serial, diagnostics12+guard and diff check. Independent reviews no remaining actionable findings. Parent pio run PASS24.99s, 99,548 B RAM / 1,742,437 B flash (+288/+36,656 vs accepted legacy cleanup). Earlier pre-final-UI integration build emitted nonfatal FATFS_PRINT_FLOAT config warning; no unrelated config edits. Host owner paths use radio/scheduler/storage doubles, not real network validation. Timer heap, memory floors, HTTPD/dispatcher margins, live Wi-Fi/mDNS/DNS/trust and target checklist remain pending. Profile editor is not explicit-index connection selection: only canonical Next profile. UART0/USB recovery, danger confirmations, no same-response delivery guarantee and unchanged browser-shell restrictions documented. No 8D.14, full M3, target acceptance or reserve approval. Documentation agent touched only authorized docs, no source/tests/assets/build/device/commands; also corrected stale SSH-to-HTTPS startup dependency against `main.c` from accepted legacy cleanup. Older wait-for-8D.12 statements below are superseded, not earlier scoped sign-offs.
|
||||
|
||||
- **Legacy-credential cleanup signed off (2026-09-08):** User explicitly accepts cleanup, confirms unchanged HTTPS certificate fingerprint and continued use of preexisting test users, supplementing the clean full-mix telemetry below. Supersedes prior target-pending/fingerprint-unconfirmed status for this cleanup; idle-cleanup scoped acceptance stands. User suspects two boot auth failures were stale pre-flash sessions in two browser tabs; plausible, not traced/confirmed. Exact acceptance and limits: `docs/legacy_credential_removal.md`. No unreported blank provisioning/recovery/fault-injection/soak/all-key checks, numeric reserve approval or broader phase/M3 acceptance inferred. Documentation only; no source/config/build/test/device/assets/commit action.
|
||||
|
||||
- **Post-legacy-removal target evidence (2026-09-08):** User provides 60-second boot/full-mix telemetry and reports substantial traffic. Five accounts/two admins; two SSH public-key sessions and two browser password logins operational. USB writer16, SSH observer9, web observers10/11, both admin consoles active at 230400 8N1 RTS/CTS DTR active. Serial WS connect2/disconnect0, admin1/0, all supplied web send/queue/protocol/close and SSH handshake/auth/IO failure counters zero; tickets all consumed/no expiry. Two web auth failures already present at boot unchanged under load, zero invalid login credentials; request origin unknown. Boot internal/DMA/PSRAM free66,488/58,732/8,246,148 B; loaded31,820/24,064/8,112,076 B, minima15,740/7,984/8,074,196 B, largest20,480/20,480/7,995,392 B. SSH stack minimum-free18,476 boot/16,284 loaded B. Exact counters/evidence limits in `docs/legacy_credential_removal.md`. No certificate fingerprint comparison, blank provisioning/recovery, all-key verification, exact revision/duration or explicit sign-off supplied; browser RX0 and no broker drop counters prevent all-route/bidirectional/lossless claims. No source/build/device action; documents only.
|
||||
|
||||
- **Legacy credential removal (2026-09-08), implemented/host-tested/build-verified; target pending:** New database and TLS module contracts inspected. Missing user storage commits empty; normal UART0 `user add <username> admin [--generate]` provisions the first administrator. Unavailable-only UART0 recovery rebuilds empty and refuses healthy storage. Existing valid user v1 bytes stay unchanged; private derived `v1_admin_marker`, no public bootstrap/migration/sync API. HTTPS private v1 reader migrates `web_sec/material` 1392 → TLS-only v2 1340 bytes, retaining exact DER/fingerprint/generation, committing before publication; failures fail closed without fallback replacement. Legacy credential commands removed; generated user passwords and TLS rotation retained; reset is TLS-only. Older v1-only firmware cannot read v2; logical NVS replacement is not secure flash wiping, and no factory erase is required. Startup integration complete; SSH gate independent of HTTPS identity, obsolete completion/policy entries removed. Parent security15, canonical accounts, policy/startup64-gate combinations, console lifecycle, cookie accounts, idle18+guards and build/diff checks PASS. Integration agent reports broad auth/transport/lifecycle/UI regression PASS. Review no actionable findings; final agent-added sparse populated v1 reload/authorization regression PASS, test-only extension. Parent final production build 3.93 s, 99,260 B RAM / 1,705,781 B flash (−56/−3,200 vs idle-cleanup baseline). Host NVS doubles do not prove flash/power-loss behavior; no independently captured old-device TLS fixture or hardware validation. Record: `docs/legacy_credential_removal.md`.
|
||||
|
||||
- **Scoped user report — HTTPS idle cleanup worked (2026-09-08):** User accepted that the cleanup worked. This supersedes the no-target-acceptance wording for that specific behavior below, not the historical measurements. No additional soak duration, detailed checklist passes, resource reserve approval, broader 8D.11/M3 sign-off or legacy-removal target validation was supplied.
|
||||
|
||||
- **User-authorized bounded ordinary HTTPS idle cleanup complete (2026-09-08), host-tested/build-verified; target pending:** Preserved existing 8D.11 key/diagnostic implementation (initial Git status was clean). User capture: post-TLS occupancy 6/6, ordinary4/serial2/admin0, ordinary connection ages 50–74 s; admin ticket returned in 14 ms, no TLS/upgrade observed and ticket unconsumed. Installed IDF stops selecting listener at capacity with LRU disabled. `web_httpd_idle.{c,h}` plus sole-private-boundary sweep in `web_httpd_adapter`: six owner-only rows, 15-second observed-idle window (three five-second status polls), one-second ESP timer/at most one queued owner probe, current fd shutdown (no reusable-pointer queued close), all-route SDK request-plus-purge completion marker and input readiness/WS/async checks, TLS-create fd reset, submit fence/nonwrapping restart generations. Diagnostics does not gate policy; socket/timeout/LRU/task/stack/asset settings unchanged. Implementation `pio run` PASS 57.55 s, final confirmation PASS 3.13 s; **99,316 B RAM / 1,708,981 B flash (+160/+1,384 versus diagnostic baseline)**. Rows144 B/module static167 B before placement; timer32 B internal heap before overhead; sweep local frame80 B, runtime margins unmeasured. Final idle **18 + SDK guards**, server **18**, diagnostics **12+1** PASS; cookie accounts/admin/settings/serial-settings, admin transport25/tickets12, store/serial, UI68+CSP and canonical console/account/key/lifecycle/policy suites PASS; diff check PASS. Cookie test-double missing `<stdint.h>` fixed and all variants rerun. Exact contracts/audit/tests/resources/target checklist: `docs/https_idle_cleanup.md`. Ordinary ages are connection ages, not proved idle duration. No hard wall-clock/admission guarantee: owner-blocking handshake/parser/send delays cleanup; continuously active ordinary sockets are not evicted. Reported queue errors retry, but accepted-and-lost nonblocking UDP work stays one reserved probe until successful stop/restart (tested), rather than accumulating unsafe delayed work. Failed stop remains detached/owned until retry. No hardware/upload/erase/commit, target acceptance or reserve approval; next is the documented target reproduction/soak, not capacity changes.
|
||||
|
||||
- **Authorized 8D.11 admission diagnostic slice complete (2026-09-08), host-tested/build-verified; target pending:** `web_diagnostics.{c,h}` uses public synchronous HTTPS create/close callbacks and four ticket/upgrade wrappers, six fixed metadata slots and a 32-record opt-in numeric ring. Console `web diagnostics enable|disable|show|clear` via UART0/admin SSH; browser policy unchanged. No close/open override, queue probe/task, capacity/timeout/log-level/assets/SDK change. Post-TLS owner-published occupancy only; TLS failures/in-progress handshakes and preaccept backlog remain invisible. Final `pio run` PASS **10.30 s, 99,156 B RAM / 1,707,597 B flash**, **+3,080/+3,912 B** versus recorded key-slice baseline. Ring/table target symbols 2,816/192 B; diagnostic handler/record/TLS callback/show local frames 144/144/160/528 B excluding callees, runtime margins unmeasured. PASS diagnostic **12+1**, server lifecycle **16**, canonical console lifecycle/policy/boundary, canonical account/key transactions, cookie/admin and cookie/accounts, admin transport **25**/tickets **12**, browser **68 plus renderer/CSP**, store/serial and diff check. Exact files/commands/contracts and reproduction checklist in `docs/phase8d11_implementation.md`. Existing key work preserved; no device/commit/upload/erase, timeout diagnosis, target acceptance or reserve approval claimed. Next is user target capture, not capacity changes or 8D.12.
|
||||
|
||||
- **8D.11 partial target evidence (2026-09-08):** User supplied 60-second fresh-boot/full-mix telemetry; no functional/sign-off claim. Full mix eventually established (two SSH public-key sessions, SSH writer + USB/two web observers, both admins), but last browser admission again required retries. Three TLS errors `-0x0050` verified against installed mbedTLS as NET_CONN_RESET, not allocation failure; cause unknown. Loaded internal/DMA free 32,596/24,840 B, lifetime minima 5,468/532 B, largest 22,528 B; per-region minima are conservative/non-simultaneous. Settled free is similar to prior 8D.10, minima much lower; no causal regression attribution. SSH stack minimum-free 18,468 boot/16,276 loaded B. Web send failure/close 1, expired tickets 3; SSH handshake/auth failures zero; no broker drop counters. Exact memory/counters/evidence limits in `docs/phase8d11_implementation.md`. Previously accepted admission issue recurred, not fixed; investigate timing/socket occupancy/allocation correlation before capacity changes. Key algorithms/new UI provenance, mutation checklist, HTTPD/dispatcher margins, cleanup/soak and target sign-off remain pending. Documentation-only update; no source/build/device action.
|
||||
|
||||
- **8D.11 implemented (2026-09-08), host-tested/build-verified; target sign-off pending:** Accounts now supports fingerprint listing and bounded Ed25519/P256 public-key import/delete/clear. Conditional database wrappers check target identity under the canonical lock; existing dispatcher/account slot and target-only revocation remain. Optional POST `/api/settings/accounts/keys` brings handler budget to 24, six sockets/tasks/stacks/queue depth unchanged. Sparse stable key slots are supported; review finding fixed and regressed. Parent canonical accounts, cookie accounts, UI, lifecycle (16), build and diff checks passed; final agent UI extension passes 68 groups plus CSP. Final production build 14.85 s, 96,076 B RAM / 1,703,685 B flash (+168/+9,448 vs 8D.10). Exact API, evidence attribution and pending target checklist: `docs/phase8d11_implementation.md`. No device/assets/commit action, reserve approval, full M3 claim or 8D.12 work. Supersedes the older wait-for-8D.11 instruction below, not accepted 8D.8–8D.10/M2.
|
||||
|
||||
- **8D.8–8D.10 implemented scope signed off by user (2026-09-08):** User provides settled boot/full-mix telemetry and reports thorough Serial parameter display/settings and user/account testing: “Implemented work can be signed off.” Accepts 8D.8, 8D.9 including UX refinement, and both implemented 8D.10 slices, superseding pending-signoff/target-blocker statements below without inventing individual checklist passes. Current source/handoff confirms credential/self workflows and generated route complete. Exact flashed revision/browser/durations/reboot/fault-injection details not separately supplied. Full mix at **230400 8N1 RTS/CTS, DTR active**: SSH writer8, USB observer9, web observers26/11, SSH admin and web admin, two cookie sessions; no broker drop counters supplied. Boot internal/DMA/PSRAM free **69,928/62,172/8,246,368 B**, loaded final **32,556/24,800/8,087,656 B**, loaded minima **19,228/11,472/8,065,444 B**, largest **23,552/23,552/7,995,392 B**. SSH minimum-free stack **18,476 boot / 16,284 loaded B**. One SSH handshake failure/broker revocation, one WebSocket send failure/close and one invalid login retained without diagnosis. All six loaded samples, counters, scope and evidence limits: `docs/phase8d10_implementation.md`. Numeric reserves, HTTPD/dispatcher stack margins, cleanup/soak evidence and earlier accepted admission issue remain followups, not reopening sign-off. Browser-shell restrictions/UART0 recovery and M2 stand; no full M3 claim. **Wait for separately requested 8D.11.** This update documentation-only, no new build/test/device/source/config/asset/commit action; preserve existing user `platformio.ini` edit.
|
||||
|
||||
The following implementation entries predate target sign-off and retain historical build/test attribution; their target-pending status is superseded above.
|
||||
|
||||
- **Current 8D.10 slice 2 complete, host-tested/build-verified (2026-09-08); target/signoff pending, not target accepted:** Create/password/generated-value/self workflows use the existing dispatcher slot, 768-byte/four-receive admission and canonical mutation-lock identity checks. One-second periodic timer cancels/wipes non-executing credentials at 30 seconds plus scheduling latency; admitted work is not cancelled and locals wipe after return. Generation is separate before commit, with no retained retrieval; UI 60-second lifetime/context-bound acknowledgement and best-effort secret clearing. Immediate self revocation can lose POST/results: 401/disconnect is uncertain, inspect after relogin before retry. Browser-shell restrictions unchanged. Review's only finding, missing generated-route registration, is fixed as an independent optional endpoint with failure isolation/restart coverage: **23 handlers**, six sockets/no LRU unchanged. Parent PASS canonical `accounts.py`, boundary `run.py`, parser **294**, cookie `--accounts` **9 plus shared**, `--serial-settings` **10**, transport **25**/tickets **12**, store `--serial` and diff check. UI agent **57 plus CSP** (four added beyond 53); route agent lifecycle **15** pass. These UI/lifecycle results are agent-attributed, not claims of the parent's additional reruns. Parent `pio run` **PASS 25.61 s, 95,908 B RAM / 1,694,237 B flash**, **+80/+9,880** vs slice 1 and **+200/+25,400** vs final 8D.9 UX. New timer runtime costs, heap reserves and stack margins remain unmeasured/unapproved. Record/checklist: `docs/phase8d10_implementation.md`; slice 1 below remains historical. This update is documentation-only; no source/test/build action by this documentation agent. No sanitizer validation, device/assets/commit/8D.11 action, full 8D.10 target signoff or prior-phase signoff/reserve approval inferred.
|
||||
|
||||
The following slice 1 entry is explicit historical evidence; its unavailable/next-slice statements and build/counts do not describe current slice 2.
|
||||
|
||||
- **8D.10 first slice complete (2026-09-08), host-tested/build-verified; target pending, phase incomplete:** User requested 8D.10; selected plan's pre-edit list/role/delete versus create/password split. `web_account_settings.{c,h}` provides admin-only compact list and other-account role/delete on one session-bound slot, executed by the existing dispatcher. Zero-wait `user_database_get_accounts()` and mutation-lock identity checks preserve canonical final-admin/NVS semantics and reject stale/recreated targets; successful calls target-revoke web/SSH. Three optional routes, 22 handler budget, six sockets/no LRU, unchanged tasks/stacks/queue depth. Accounts subview has confirmations, bounded auto-completion/refresh (10 GETs/15 s), uncertainty recovery and navigation/session fencing without broker lease effects. Final parent cookie `--accounts` (5 groups/shared auth), canonical accounts, console boundary, server lifecycle (14), UI (41 + CSP), transport/tickets and store/serial pass; prior settings/serial/admin/policy/lifecycle reruns also pass. Final `pio run` **25.00 s / 95,828 B RAM / 1,684,357 B flash**, **+120 / +15,520 B** vs final 8D.9 UX; diff check PASS. Exact evidence/contracts/target checklist: `docs/phase8d10_implementation.md`. No independent review/sanitizer/device/assets/commit action or reserve/prior-phase signoff. Next is second 8D.10 slice: create/password, one-time generated secrets and safe own-account changes; these remain unavailable. No 8D.11 work or full 8D.10 completion.
|
||||
|
||||
- **8D.9 UX refinement (2026-09-08), host-tested/build-verified; target pending:** User reported successful Apply required awkward manual Check Result then Refresh and approved automatic flow/removing routine popups. `web_ui.c` now keeps settings visible/stale while pending, checks acknowledged operations every 1 s up to 10 GET attempts/15 s overall, then refreshes working values while preserving outcome. Errors/exhaustion/lost ack use manual recovery; no POST retry, no resume after navigation. Only Reset retains a saved-NVS overwrite confirmation; inline discard semantics remain. Current-view selection is a no-op so repeated Settings clicks cannot cancel work. Parent UI **35 groups + renderer/CSP**, `pio run` **10.91 s / 95,708 B RAM / 1,668,837 B flash**, diff check PASS; **0 / +2,112 B** vs original 8D.9. No backend/assets/device/commit action or new signoff. Details/evidence limits in `docs/phase8d9_implementation.md`; target automatic-completion, failure/manual-recovery and full-mix latency checks pending.
|
||||
|
||||
- **8D.9 continuation complete (2026-09-07), implemented / reviewed / host-tested / build-verified; target/signoff pending:** Preserved inherited implementation; typed Apply/Start/Stop/Save/Load/Defaults/Reset uses one session-bound slot and the existing dispatcher queue, 256-byte JSON POST and 96-byte explicit GET results. No new tasks/stacks/queue depth or broker lease semantics; 19 URI handlers, six sockets/no LRU. Fixed persistent uncertain-result warnings and consistent zero-wait `/api/status` serial state (`running:null` when unavailable). Final parent Serial 10, Settings/status 6, UI 27/CSP, console boundary and lifecycle 13 groups pass; additional transport/tickets/store/admin/account/lifecycle regressions passed during review. Final `pio run` 23.73 s, **95,708 B RAM / 1,666,725 B flash**, **+128 / +12,196 B** vs 8D.8. Deadline is dequeue admission only; blocked dispatcher retains pending slot, admitted NVS work may finish after logout, completed results can be replaced. Exact contracts/tests/limits and pending target checklist: `docs/phase8d9_implementation.md`. No target/device/asset/commit action, reserve approval or 8D.8/8D.9 signoff. Stop before separately requested 8D.10; M2 acceptance and existing deferred restrictions/admission followups stand.
|
||||
|
||||
- **8D.9 original handoff (2026-09-07; superseded by completion above):** User explicitly requests end-to-end Serial typed edits/persistence despite pending 8D.8 target validation. This is continuation authorization, not 8D.8 target signoff. Plan: verify canonical serial and concurrency contracts; implement a single bounded session-bound operation slot on the existing dispatcher with nonblocking HTTP admission/result reads; add explicit Serial controls and focused regressions; finite build, diff review and accurate resource/target handoff. No later domains/popovers/generated assets/device/commit actions. Existing modification to `docs/phase8d8_implementation.md` belongs to parent/user and is preserved. M2 acceptance, deferred restrictions, unresolved admission and unapproved memory/stack followups stand.
|
||||
|
||||
- **8D.8 separately authorized, implemented / host-tested / build-verified (2026-09-07), target/signoff pending:** Admin-only Settings/Serial in the existing document, bodyless server-authorized `GET /api/settings/serial`, eight typed working values with 256-byte response bound. Nonblocking serial-state snapshot avoids blocking HTTPD on console reconfiguration; no mutations/NVS/broker/terminal lifecycle side effects. Selection preserves both sockets/lease and hidden output; explicit refresh, safe errors, cancellation/identity/expiry and concurrent-reconnect fencing. One optional URI (17 total), six sockets/no LRU unchanged. Private IDF adapter stages new-route descriptor/name before publication to avoid installed public registration's dangling entry on strdup failure; only the new Settings route uses it, broader inherited registration audit remains a followup. Final `pio run` **24.31 s, 95,580 B RAM / 1,654,529 B flash**, **0 / +5,952 B** vs recorded 8D.7; **+1,048 / +54,556 B** vs 8D.0. Target snapshot/descriptor 36/24 B; requested route/table heap +49 B before overhead, handler local frame 416 B excluding callees, no stack/task/queue/capacity increase. UI **21**, Settings **5**, lifecycle **12**, transport **25**/tickets **12** and auth/store/login/parser/console/policy regressions pass; diff check passes. No device, sanitizer, generated-asset or commit action; inline loader/CSP hashes unchanged and verified. Task/todowrite tools unavailable; implementer review fixes rerun, no independent-agent review claim. See `docs/phase8d8_implementation.md` for exact tests/resources/limits/pending target checklist. **M2 signoff, deferred restrictions, accepted unresolved admission issue and unapproved memory/stack followups stand. Stop before separately requested 8D.9; no new-phase signoff inferred.** This supersedes the older next-8D.8/wait-for-request instructions below, not their signoff/evidence.
|
||||
|
||||
- **M2 explicitly signed off by the user (2026-09-07):** After 8D.7 implemented-scope validation and discussion of read-only settings next, the user says "Jupp, sign M2 off". This supersedes all earlier M2-open statements and continuation instructions below; accepted M2 does not require revalidation or imply full browser command parity. Browser self-target/generated-password/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 or diagnosed. Numeric memory reserves and stack margins remain unapproved follow-ups, not blockers reopening M2. **Next: 8D.8 read-only settings entry and Serial page, only when separately requested; this sign-off alone authorizes no implementation.** Evidence/history: `docs/phase8d7_implementation.md`. Documentation only; no source/tests/build/device/commit action.
|
||||
|
||||
Earlier development entries below are historical; the latest M2 sign-off supersedes their pending status and next-work instructions, not their evidence.
|
||||
|
||||
- **8D.7 validated by explicit user sign-off (2026-09-07), implemented scope only:** User explicitly says "Ok, mark 8D.7 as validated." Supersedes historical target-pending/acceptance-blocking and continuation instructions below for the implemented stop/reboot, certificate and other-account slices. User reports thorough testing, verified certificate rotation and web start/stop with lifecycle via UART0/SSH admin/web admin (restart after browser stop via another route), and full mix without broker drops up to **230400 baud** after correcting external adapter baud. Intermittent supported two serial + one admin WebSocket admission failures have recently not recurred and are accepted nonblocking, not fixed or diagnosed. Preserve investigation/telemetry below. No separately reported reboot-specific or individual mutation/injection results; do not invent checklist passes. Self/generated/key/legacy-credential and other owner parity remain deferred and restricted; bootstrap/recovery remain UART0-only. Numeric reserves/stack margins and **M2 acceptance remain open**; no full-parity claim. See `docs/phase8d7_implementation.md`. Documentation-only sign-off; no new implementation authorized. Wait for a separate request.
|
||||
|
||||
Earlier development entries below are historical; the latest sign-off supersedes their pending status and next-work instructions, not their evidence.
|
||||
|
||||
- **8D.7 target admission investigation (2026-09-07):** User reports 60-second clean boot, then intermittent browser-admin timeouts with SSH writer/admin, USB and two web observers; removing one observer allows admin, final observer eventually reconnects. Boot internal/DMA/PSRAM free 70,860/63,104/8,246,176 B; loaded free 33,428/25,672/8,084,100 B, minima 5,280/344/8,058,464 B, largest 23,552/23,552/7,995,392 B. Boot SSH stack minimum-free 18,472 B. Exact revision/browser arrangement/loaded duration unknown. TLS -0x0050 is reset, -0x7280 EOF, not allocation errors (installed SDK verified). Leading unproven hypothesis: six shared HTTPD slots occupied by WebSockets plus retained ordinary HTTP connections; full IDF server stops accepting, recv timeout does not expire idle slots. Synchronous handshakes and memory/global socket pressure may contribute; TLS already allocates externally. Need failed-stage browser timings, count-only HTTPD occupancy/allocation-failure correlation and paused-traffic comparison. No runtime change/build/device operation; record in `docs/phase8d7_implementation.md`. Full-mix reliability and 8D.7/M2 acceptance remain open; do not attribute to account slice or claim OOM without evidence.
|
||||
|
||||
- **8D.7 third account slice review completed (2026-09-07), target pending:** Inherited uncommitted code/tests enable browser other-account interactive add/password and forced delete/role; self/generated/key/bootstrap/recovery remain blocked. Independent production/test reviews found no actionable findings; prior precommit-liveness finding withdrawn. Contract is post-prompt, pre-database-API operation-admission currentness: admitted work may commit and target-revoke after disconnect/expiry, subsequent stale work rejects. Documentation now records uncertain-result recovery and deterministic-test limits. Boundary/accounts/policy (review agent), lifecycle, transport 25/tickets 12 and store/serial (parent) pass. Parent incremental `pio run` PASS 3.35 s, **95,580 B RAM / 1,648,577 B flash**, 0/+516 B versus certificate slice. No production edits in continuation, device operation, sanitizer pass, asset regeneration or commit. See `docs/phase8d7_implementation.md`. Supersedes older all-account-blocked/next-account notes; remaining owner/credential parity, target checks, numeric reserves and 8D.7/M2 acceptance remain pending. No next slice started.
|
||||
|
||||
- **8D.7 second certificate slice implemented / host-tested / build-verified, target pending:** Exact parsed browser `web certificate rotate --force` is enabled. Typed deferred-request union preserves queue capacity; immutable owner `dispatcher_actions` transfers certificate work after bounded drain/200 ms delay to the existing 12 KiB dispatcher, not the 4 KiB control stack. Pending-input gate survives handoff; token/principal/session revalidation and executing-slot reservation fence stale work/self-detach reuse. WEB uses transactional certificate commit → stop → start, returning on early errors and retaining ownership on failed stop. SSH/UART0 unchanged; other account/network/credential/SSH mutations remain blocked. No new tasks/depth/routes/assets/stacks. 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. Parent final rerun passes boundary (`run.py` including `certificate.c`), lifecycle, policy, transport **25**/tickets **12**, server **11**, cookie/admin, store/serial, UI **17** plus renderer/CSP and diff checks; independent reviewer has no actionable findings. Sanitizers unavailable (missing libasan/libubsan); no hardware validation. Owner mask/local scratch and target stack margins remain unmeasured; host sizeof is not target proof. Drain/acknowledgement is bounded, not execution latency or peer receipt. **User explicitly authorized continuing to stack the next bounded slice, not target sign-off. Next: credential/account, then other owner slices; 8D.7/M2 acceptance and numeric reserves pending.** See `docs/phase8d7_implementation.md` for separate slice histories and certificate force/trust/relogin/USB/SSH/failure checklist. Supersedes the first-slice next-step/continuation-pending instruction below. Production/tests/docs updated and parent build/regressions executed; no device operation, asset regeneration or commit.
|
||||
|
||||
- **8D.7 first increment implemented / host-tested / build-verified (2026-09-06), target pending:** User requests starting 8D.7. Scope split enables only browser `reboot` and `web stop` through existing bounded deferred control; WEB revalidates session/principal/token before lifecycle APIs. Adds browser-origin query and pending snapshot flag; HTTPD discards deferred input, latching disposition before receive. Review fixed second-frame-before-poll and cancellation-during-receive races; final reviewer confirms correction. Canonical handler/console/policy, transport **23**/tickets **12**, lifecycle **11**, cookie/admin, store/serial and UI **17** suites pass. Final `pio run` **12.44 s, 95,580 B RAM / 1,647,025 B flash**, **0 / +536 B** vs 8D.6. No tasks/routes/capacity/assets/UI/credential changes or device operations. `docs/phase8d7_implementation.md` holds behavior, restrictions, validation limits/resources and disruptive target checklist. **8D.7/M2 incomplete; next slice remains safe HTTPS identity/certificate handling**, then remaining owner/account restrictions. Obtain target regression or explicit user decision before stacking runtime changes. `web stop` intentionally closes both browser routes; restart via UART0/admin SSH. Prior 8D.6 sign-off stands; internal/DMA minima **6,516 / 1,580 B** and numeric reserves remain follow-ups.
|
||||
|
||||
- **8D.6 validated by explicit user sign-off (2026-09-06):** After 60-second boot/full-mix/partial-cleanup telemetry, user confirms all discussed remaining checks and requests wrap-up. Closes 8D.6 including Open admin and toolbar-order fixes, superseding older target-pending notes below. Evidence in `docs/phase8d6_implementation.md`: full mix at **230400 baud**, browser admin + admin SSH, two web serial/SSH/USB broker clients; web24 sole writer. Loaded internal/DMA/PSRAM free **33,900 / 26,144 / 8,087,468 B**, minima **6,516 / 1,580 / 8,063,428 B**, largest **24,576 / 24,576 / 7,995,392 B**; SSH stack minimum-free **16,280 B**. Partial cleanup retains serial, internal/DMA largest returns to **31,744 B**. One SSH handshake failure and rejected input byte remain unattributed. Exact revision/browser/counts/soak duration not separately supplied; no invented detailed execution or reserve approval. Numeric reserves and low lifetime minima remain follow-ups, not blockers reopening signed-off 8D.6. Last toolbar build/tests: **7.60 s, 95,580 B RAM / 1,646,489 B flash**, 17 UI groups/CSP/diff pass. This handoff changes documentation only. **Wait for separately requested 8D.7 — bounded web-shell lifecycle parity and M2 acceptance.** Preserve current restrictions until their safe owner paths are implemented; no settings/M2 completion or new implementation is implied.
|
||||
|
||||
- **8D.6 Open admin bug fix (2026-09-06), target retest pending:** Confirmed frontend/backend ticket mismatch: `openAdmin()` required 32 URL-safe characters (serial format), while admin issuance returns 64 lowercase hex characters. Valid POST responses therefore threw before WebSocket construction with the user's exact generic message. Fixed only the admin validator; split the previously shared serial/admin test fixture, assert exact admin query URL and reject malformed/serial-format tickets without closing serial. Correct-format fixture reproduced pre-fix failure; final production-renderer/CSP + 17 UI groups, cookie/admin endpoint integration, transport 19/tickets 12 and lifecycle 11 pass. These are separate frontend and backend harnesses, not real-browser/on-wire integration. CSP loader hash matches the reported `o6St...` policy; rejected UUID/index.js scripts' origin is unverified, not grounds to relax CSP. Final bounded `pio run` passed in **7.96 s**, **95,580 B RAM / 1,646,489 B flash**, unchanged sizes. Diff check passes. No assets, upload, commit, backend/admission changes or target pass. Short bug record in `docs/phase8d6_implementation.md`; parent independent review and user target retest pending.
|
||||
|
||||
- **8D.6 final re-review complete (2026-09-06):** Independent reviewer confirmed session-identity isolation and bounded fit-readiness fixes and their regressions, with no remaining actionable findings in the reviewed changes. Final build/test results below stand; no further source change or build for this documentation update. Browser/target validation and numeric reserves remain pending, not phase sign-off or M2 completion. Stop before 8D.7 until separately requested after the validation decision.
|
||||
|
||||
- **8D.6 independent review fixes implemented / host-tested / build-verified (2026-09-06), target pending:** Fixed both P2 findings without unrelated scope. Before session adoption, compare retained username/role/session-stable CSRF; mismatch hides old terminals, closes both routes/fences work and replaces the document at `/`, including live admin and logout paths. Pagehide keeps buffers hidden until same-session validation; unchanged identities retain scrollback. Fit caches bounds only after valid measurement/success, with three bounded readiness retries, teardown cancellation and generation fencing. Expanded UI harness passes **16 groups**; login eight groups, cookie/admin integration, parser 268 and store/serial reruns pass. Final finite `pio run` after all fixes: **21.35 s, 95,580 B RAM / 1,646,489 B flash**; review delta **0 / +1,376 B**, versus 8D.5 **0 / +9,216 B**, versus 8D.0 **+1,048 / +46,516 B**. `git diff --check` passes. See `docs/phase8d6_implementation.md` for exact regression cases and unchanged pending target checklist. Parent reviews fixes only; no target pass, reserve approval, sign-off or M2 completion. Existing worktree edits preserved; no server/protocol/assets/8D.7/upload/commit action. Supersedes initial 8D.6 counts/build below.
|
||||
|
||||
- **8D.6 implemented / host-tested / build-verified (2026-09-06), target pending:** Admin-only Serial/Admin selector in authored `web_ui.c`, explicit Open/Close admin using existing protected endpoints, lazy separate 5,000-line terminals with 64 KiB pending writes each and visible browser-drop counts. Switching preserves serial socket/client/lease and drains hidden output; selected input only, writer controls visible in both. Admin closure is isolated; logout/expiry/pagehide cancel both with listener cleanup and stale-generation fencing. Existing 8D.5 restrictions/server/capacities unchanged; no assets/upload/commit. 13 UI groups, eight login groups, cookie/admin integration, parser 268, store/serial, admin transport 19/tickets 12, server lifecycle 11 and console boundary suites pass. Final finite `pio run`: **22.22 s, 95,580 B RAM / 1,645,113 B flash**, **0 / +7,840 B** versus 8D.5, **+1,048 / +45,140 B** versus 8D.0. Diff check passes. See `docs/phase8d6_implementation.md` for exact contracts, test limits/resource accounting and pending desktop/mobile/prompt/full-mix/soak checklist. Parent independent review pending; no target execution, numeric reserve approval, 8D.6 sign-off or M2 completion. Prior 8D.5 validation stands. Stop before separately requested 8D.7. This supersedes older planned-selector notes below.
|
||||
|
||||
- **8D.5 validated by explicit user sign-off (2026-09-06):** User supplied settled cold-boot telemetry and reports successful **15-minute full-client-mix active-use soak at 230400 baud**, with a few broker drops under extremely fast/dmesg output, then explicitly closes 8D.5. Supersedes older pending/incomplete notes below. Boot internal/DMA/PSRAM free **70,876 / 63,120 / 8,246,360 B**, minima **59,560 / 51,804 / 8,242,140 B**, largest **31,744 / 31,744 / 8,126,464 B**; SSH stack minimum-free **18,472 B**. Admin initialized/attached with ESP_OK and 167/240/1,552 B static/ticket/payload accounting; no active clients at boot. Full mix/soak is user-reported, not evidenced by the idle snapshot; exact drop count/client, flashed revision and loaded/post-soak/cleanup telemetry not supplied. See `docs/phase8d5_implementation.md`. These limits do not reopen sign-off or imply zero-drop operation. Numeric reserves/runtime socket cost remain open. No new build/device/source action for sign-off. Wait for separate **8D.6** request; M2 not yet complete.
|
||||
|
||||
- **8D.5 backend implemented / host-tested / build-verified (2026-09-06), target pending:** Preserved interrupted tickets/transport/server/shared-console/SSH/policy/revocation work and inherited test tooling. One admin socket, two tickets, existing two shared console slots, 1,552 B PSRAM-only payload, 20 ms ESP timer/no new task, six HTTPS sockets/no LRU eviction, 16 URI handlers. Added real cookie/store/ticket/transport endpoint integration tests. Final IDF lifecycle review found queued `httpd_sess_trigger_close` retains a reusable socket-slot pointer; changed the admin path to HTTPD-owned `shutdown`, with HTTPD read cleanup and retry/reuse regressions. Transport 19 groups + tickets 12, server lifecycle 11, combined endpoints, both console suites, store/serial, cookie/parser (268), login UI (8), serial UI (9) all pass; diff check passes. **Parent reports sequential final `pio run` after the shutdown fix passed in 23.55 s at 95,580 B RAM / 1,637,273 B flash: +416/+9,224 versus prerequisite, +496/+10,100 versus 8D.4, +1,048/+37,300 versus 8D.0.** This supersedes the continuation's earlier pre-fix build; history remains in the implementation record. Parent's final independent security integration reviewer reported no actionable findings. Target ELF payload/slot/ticket sizes 1,552/80/96 B, ticket state+lock 240 B, transport static symbols 167 B before padding, ESP timer internal allocation 32 B before heap overhead. See `docs/phase8d5_implementation.md` for exact commands, inherited-versus-final fixes, resource accounting, restrictions and manual target/client checklist. Client `--help` only exercised in the continuation; this final evidence update changed documentation only, with no source/tests/build/device/network/asset/UI/upload/commit action. **Stop before 8D.6; target acceptance, runtime socket cost, numeric reserves and M2 remain open.** This supersedes the prerequisite-only pause below.
|
||||
|
||||
- **8D.5 prerequisite resumed / host-tested / build-verified (2026-09-06), target pending:** User requested continuation. Preserved existing uncommitted dispatcher/owner-currentness and prompt-cleanup changes in the console/SSH adapter; extended tests to production SSH snapshot/principal publication and wiping. Both console suites pass; independent production review found no actionable defects. `pio run` passes in **38.46 s**, **95,164 B RAM / 1,628,049 B flash** (**+80 / +876 B** versus recorded 8D.4); map confirms 80 B for two principal copies. No new tasks/routes/sockets/UI or backend yet. See `docs/phase8d5_implementation.md` for contract, scope split, test limits and target checklist. **8D.5/M2 incomplete.** Obtain target regression or explicit user decision before stacking live backend work. Exact next increment is still 8D.5 tickets/HTTPD-owned admin transport/protected admission/revocation/restrictions and integration checks, not 8D.6. Previous 8D.4/M1 sign-offs stand; numeric reserves remain open. This supersedes older wait-for-8D.5-request notes below.
|
||||
|
||||
- **8D.4 validated by explicit user sign-off (2026-09-06):** User confirms successful empty Enter and soak testing and explicitly closes Phase 8D.4. This supersedes older pending/in-progress notes below, including the 8D.3 empty-Enter target retest. Boot/full-client-mix evidence and sign-off are in `docs/phase8d4_implementation.md`. Exact soak duration/revision, post-soak/cleanup telemetry and other detailed checklist results were not supplied; these are evidence limitations, not blockers to the user-approved closure or claims of execution. Numeric reserves remain open. No new build/device operation for sign-off. Wait for a separate **8D.5** request; no browser admin backend or M2 completion is claimed.
|
||||
|
||||
- **8D.4 target validation in progress (2026-09-06):** User supplied clean-boot/full-client-mix UART0 evidence, recorded in `docs/phase8d4_implementation.md`. Loaded internal/DMA/PSRAM free **34,632 / 26,876 / 8,089,284 B**, minima **20,648 / 12,892 / 8,077,516 B**, largest **25,600 / 25,600 / 7,995,392 B**; SSH stack minimum-free **16,280 B** (boot **18,472 B**). Four broker clients at 115200 baud: SSH writer, USB and two web observers; user/admin SSH and both browser roles admitted successfully, no reported I/O/transport failures. One cumulative SSH broker revocation retained without diagnosis. Exact flashed revision/duration not supplied. Console-specific regressions, lifecycle/soak/cleanup and explicit phase sign-off remain pending; numeric reserves remain open. No 8D.5 request or implementation.
|
||||
|
||||
- **8D.4 implemented / host-tested / build-verified (2026-09-06), target pending:** User separately requested the small console boundary. Added transport-qualified identity and immutable output-drain/lifecycle owner callbacks in `admin_ssh_console`, retained the SSH admission API in `ssh_transport`, and kept two shared slots, one dispatcher and all capacities/stacks. Shared completion scratch now has nonblocking ownership; occupied-slot replacement and deferred input fail closed. `python3 tests/admin_console_boundary/run.py` and existing policy suite pass; independent review found no defects. `pio run` passes in **43.61 s**, **95,084 B RAM / 1,627,173 B flash** (**-424 / +1,448 B** versus recorded latest 8D.3). See `docs/phase8d4_implementation.md` for contracts, host-test limits, accounting and target checklist. No browser admin routes, uploads or 8D.5 work. Prior M1 sign-off stands; numeric reserves and 8D.4 target regression remain pending. Stop for validation/user decision before separately requested 8D.5.
|
||||
|
||||
- **8D.3/M1 completed by explicit user sign-off (2026-09-06):** User closes M1 after post-soak evidence, superseding all older pending/blocked/in-progress statements below. Four broker clients remain active at 115200 baud (SSH writer; USB/two web observers), both SSH routes and two cookie sessions active. Post-soak internal/DMA/PSRAM free **38,656 / 30,900 / 8,138,320 B**, minima **13,756 / 6,000 / 8,072,744 B**, largest **25,600 / 25,600 / 7,995,392 B**; SSH stack minimum-free **16,288 B**. Minima/largest unchanged from earlier loaded sample; no reported transport failures. Record/evidence limits in `docs/phase8d3_implementation.md`. Sample is still connected, not disconnected cleanup; exact soak duration/revision not supplied. Unrecorded detailed checks do not reopen signed-off M1 or become claims of execution. Numeric reserves remain open; empty-Enter fix target retest remains unrecorded separately. **Wait for a separate 8D.4 request; no M2 implementation performed.**
|
||||
|
||||
- **8D.3 both-role target login confirmed / mixed-client evidence (2026-09-05):** User reports successful HTTPS user+admin login after Origin fix; previous login blocker is resolved. Settled internal/DMA/PSRAM free **71,204 / 63,448 / 8,247,744 B**. Mixed load free **33,868 / 26,112 / 8,089,060 B**, minima **13,756 / 6,000 / 8,072,744 B**, largest **25,600 / 25,600 / 7,995,392 B**; SSH stack minimum-free **16,288 B**. 115200 baud, four broker clients (SSH sole writer, USB + two web observers), user/admin SSH active. No reported web transport or SSH I/O errors; 6 login attempts/3 invalid credentials/1 logout, zero security rejections. Two identical loaded heap samples are not a soak/leak or reserve proof. Full details/provenance in `docs/phase8d3_implementation.md`. **M1 validation in progress, not signed off.**
|
||||
- **Admin SSH empty Enter fix (2026-09-05):** User's empty line was classified as UART0-restricted because `remote_command_allowed` required argc>0. Changed only helper classification to allow empty input through normal quiet IDF handling; currentness and physical-only commands remain protected. New `python3 tests/admin_ssh_policy/run.py` passes 15 cases using production helper/installed parser. Build passes **21.04 s**, **95,508 B RAM / 1,625,725 B flash** (+20 B flash). Not uploaded or target-tested. Ask for empty Enter/normal-command smoke on next flash; no 8D.4 refactor.
|
||||
|
||||
- **8D.3 Origin-null fix (2026-09-05), target retest pending:** User confirmed challenge200/login403 with `Origin: null`, same-origin Fetch Metadata and pre-login cookie; post-attempt counters show 0 password attempts and 7 security rejections. Root cause is non-CORS fetch POST under no-referrer. Login fetch now uses `mode:'cors'`; app helper uses cors for POST tickets/logout, unchanged GET mode. Same-origin credentials/fixed paths/redirect denial/CSP/no-referrer and strict server Origin/CSRF remain intact. Login CSP hash updated atomically. Both UI suites and cookie-policy suite pass; build **95,508 B RAM / 1,625,705 B flash**, **14.30 s** (+16 B flash). No upload; actual Firefox Origin header/login/serial/logout retest and M1 acceptance still pending. See implementation record; supersedes the speculative diagnosis below.
|
||||
|
||||
- **8D.3 target login blocker (2026-09-05):** User supplied clean-boot/60-second settled telemetry, then reports both user/admin login rejected with the page's HTTP-403 message. Record in `docs/phase8d3_implementation.md`: internal free/min/largest 69,004/66,752/31,744 B; DMA 61,248/58,996/31,744 B; PSRAM 8,223,116/8,218,204/8,126,464 B; SSH stack minimum-free 18,464 B; no active sessions/broker clients, UART stopped. Boot counters precede login attempts; no post-attempt result yet. Exact flashed revision not supplied. Production login-renderer/CSP test passes. Browser warnings name other script hashes (possibly injected scripts), plus denied favicon/file URL; do not relax CSP based on these alone. Need failed endpoint/status/error code and nonsecret Origin/Sec-Fetch-Site. Investigation hypothesis: same-origin fetch mode plus no-referrer policy may serialize POST Origin as null; confirm wire headers before changing request policy. **M1 blocked, not signed off.** No corrective firmware change for this report yet.
|
||||
|
||||
- **8D.3 live cutover implemented / host-tested / build-verified (2026-09-05), M1 target pending:** Resumed another agent's uncommitted completed server/browser implementation; preserved it, verified installed IDF header/upgrade semantics, and fixed right-aligned pending-buffer cleanup with an actual-IDF-reader regression. Cookie login/logout is live, Basic/cache removed. Final build **95,508 B RAM / 1,625,689 B flash** (+248 / +23,764 B versus 8D.2). See `docs/phase8d3_implementation.md`. This supersedes older inert/planned statements below. **Stop for M1 target/browser sign-off before 8D.4; numeric reserve gates remain open.**
|
||||
|
||||
- **8D.3 inert login renderer completed (2026-09-05):** User requested continuation after the parser split. Added standalone `web_login_ui.{c,h}` plus production-renderer/Node tests; no live route or Basic-auth change. Build and focused suites pass; 8D.3/M1 remains incomplete. Remaining work is the atomic server/app cutover, then mandatory browser/target validation. See active task below.
|
||||
|
||||
- **8D.3 preparatory parser split (2026-09-05):** User requested continuation. Per the plan's 600–800-line scope review, selected inert private request parsing before the larger atomic login/logout cutover. `src/web_auth_parse.{c,h}` and focused host tests added; no live HTTP callers or authentication changes. See active task below. 8D.3/M1 is **not complete**; Basic remains active. Prior 8D.2 user sign-off stands; numeric reserves remain open.
|
||||
|
||||
- **8D.2 validated by user sign-off (2026-09-05):** User explicitly closed 8D.2 after post-flash boot and full-client-mix samples. This supersedes older target-pending/in-progress statements below. Evidence and limits are in `docs/phase8d2_implementation.md`; unrecorded regression details are not claims of execution or blockers to the signed-off checkpoint. Numeric reserves remain open. Wait for a separate 8D.3 request.
|
||||
|
||||
- **8D.2 target samples received:** User reports post-flash clean boot and full client mix at 115200 baud, recorded in `docs/phase8d2_implementation.md`. Loaded free internal/DMA/PSRAM **39,200 / 31,444 / 8,138,284 B**, minima **18,784 / 11,028 / 8,109,712 B**, SSH stack minimum-free **16,296 B**. Four broker clients confirmed, SSH writer; no reported SSH I/O or web failures. One cumulative SSH broker revocation and an initial console usage error retained without diagnosis. Target validation is in progress, not signed off; lifecycle/revocation/soak/cleanup evidence remains pending. No 8D.3 request.
|
||||
|
||||
- **8D.2 implemented / host-tested / build-verified (2026-09-05), target regression pending:** User requested 8D.2 only after 8D.0/8D.1 sign-off, with numeric reserve gates still pending. Session-bound serial tickets/slots, session-specific cleanup, and account/global store invalidation are implemented; Basic remains the only public auth route. This supersedes older next-task and dormant-ticket-binding statements below. See `docs/phase8d2_implementation.md`. Stop before 8D.3.
|
||||
|
||||
- **8D.1 validated by user sign-off (2026-09-05):** Following boot/full-client-mix samples, the user reports a long-lasting command with output at full **115200-baud line speed and no dropped broker packets**, and explicitly closes 8D.1 validation. This supersedes older in-progress/not-target-verified statements below. Detailed duration/command/byte totals were not supplied; do not invent them or reopen the signed-off phase solely for unrecorded regression details. Numeric reserve gates remain open; cookie authentication remains dormant/planned. Stop before 8D.2 until requested. See `docs/phase8d_baseline.md` for evidence and sign-off.
|
||||
|
||||
- **8D.1 target validation in progress (2026-09-05):** User supplied fresh-boot (UART stopped) and full-client-mix (115200 baud, SSH writer, admin SSH, USB and two web observers) samples, recorded separately in `docs/phase8d_baseline.md`. Loaded internal/DMA/PSRAM free bytes **41,420 / 33,664 / 8,162,824**, minima **19,384 / 11,628 / 8,115,028**; SSH stack minimum-free **16,288 B**. No reported SSH I/O or web failures; one cumulative SSH broker revocation retained without diagnosis. These supersede older statements below that no target samples exist, but are not full 8D.1 sign-off. Exact changed-firmware hash, lifecycle/HTTPS restart and soak/cleanup coverage remain pending. No continuation to 8D.2 requested.
|
||||
|
||||
- **8D.0 sign-off (2026-09-05):** User marked the baseline validated and identified tested source as latest checked-in revision `d8999cd4a96e477fabd392ced02d810c3cd22d0f`. Current samples and authoritative sign-off are in `docs/phase8d_baseline.md`; they supersede older baseline measurements/provenance notes below. User attributes SSH I/O errors to out-of-spec **460400-baud** testing, distinct from transcript UART configuration **460800**. Numeric reserve approval and 8D.1 post-change target validation remain pending; do not reopen 8D.0 solely for unrecorded checklist details or claim those checks were executed.
|
||||
|
||||
Based on checked-in source plus `README.md` and `docs/roadmap.md`:
|
||||
|
||||
- Hardware characterization, serial service, session broker, USB CDC, Wi-Fi, HTTPS/WebSocket, SSH serial transport, and local display/control are implemented and documented as target-hardware validated.
|
||||
- Phase 8A role-based user storage/UART0 administration and Phase 8B role-aware HTTPS/SSH authentication and targeted revocation are documented as target-hardware validated.
|
||||
- Phase 8C admin SSH is implemented in source, uses the shared `esp_console` registry, and has passed target-hardware validation.
|
||||
- Phase 8D.0–8D.6 and M1 are validated by user sign-off; 8D.7 implemented scope is user-validated and M2 explicitly signed off on 2026-09-07. Browser administration retains deferred parity restrictions; numeric reserves/stack margins remain unapproved. Follow `docs/phase8d_plan.md`: next is separately requested 8D.8 read-only settings entry and Serial page, with no implementation authorized by sign-off alone. Changing terminal modes must preserve the browser serial broker client and any writer lease. The roadmap retains the full end-state requirements.
|
||||
- Security/production hardening, OTA, BLE evaluation, advanced networking, and optional filesystem features remain future roadmap work.
|
||||
- Reserved OTA, coredump, NVS-key, and storage partitions do not imply those runtime features are implemented.
|
||||
|
||||
## Recent memory audit
|
||||
|
||||
- Fixed failed-initialization ownership leaks for wolfSSH, partial HTTPS startup, and TinyUSB teardown. Failed teardown now retains ownership and blocks unsafe duplicate initialization.
|
||||
- Serial-service RX/TX stream payloads (16 KiB and 8 KiB effective capacity) now prefer PSRAM with internal fallback; FreeRTOS controls and UART driver buffers remain internal.
|
||||
- The 5,360-byte transactional user-database candidate now prefers PSRAM with internal fallback while the live database remains internal. Candidate contents are wiped after each transaction and wiped/freed on initialization or recovery failure.
|
||||
- UART and admin-SSH completion formatter buffers were reduced from 2 KiB to 1 KiB each; current worst-case output is 890 bytes and overflow remains fail-closed.
|
||||
- Linked RAM fell from 99,508 to 92,188 bytes (7,320 bytes). PSRAM placement of serial payloads additionally removes about 24 KiB of normal internal-heap pressure on the target.
|
||||
- `pio run` passes. A preliminary target run reports significantly more free memory and stable, improved operation after these changes. This is useful evidence but not completion of Phase 8C validation.
|
||||
- The reviewed mDNS-enabled build uses 94,532 bytes of linked static RAM, 2,344 bytes above the earlier 92,188-byte baseline, and 1,599,765 bytes of flash. Minimizing the managed component saved 112 bytes of linked RAM and about 5.9 KiB flash versus the first mDNS build. Its 4 KiB task stack remains internal, while checked-in settings move general mDNS allocations to PSRAM and disable unused browse, component CLI, AP/ETH, and multiple-instance features. Runtime heap impact still requires target measurement.
|
||||
- Remaining targeted checks include stored/migrated/recovered user-database mutations, USB enumeration, HTTPS start/stop failure recovery where injectable, SSH initialization/login, completion display, and sustained multi-transport serial traffic while checking `memory` telemetry.
|
||||
|
||||
## Clearly incomplete or transitional areas
|
||||
|
||||
- Phase 8C hardware validation passed, including route separation, shared command serialization, history/completion, prompts, output backpressure, revocation during queued work, deferred SSH lifecycle/reboot actions, and full concurrent transport operation. At 460800 baud with SSH and WebSocket clients in parallel, substantial packet drops and slow display controls were observed under load, without memory exhaustion; no baud-rate reduction is planned.
|
||||
- Current HTTPS UI retains status/serial for both roles and exposes an admin-only selector in 8D.6. The 8D.7 policy permits bounded other-account mutations, deferred stop/reboot and certificate rotation; unsupported self/generated/key/legacy-credential and other owner-specific paths remain restricted.
|
||||
- Browser authentication uses cookie login/logout without Basic fallback. M1 and 8D.4–8D.6 are signed off; 8D.7 implemented scope is user-validated and M2 explicitly signed off on 2026-09-07. Full parity is deferred, numeric reserves/stack margins remain unapproved, and no new implementation is authorized.
|
||||
- NVS encryption, secure boot/flash encryption review, production certificate/provisioning policy, and OTA are not implemented. HTTPS login has a bounded global five-verifications/60-second throttle, not comprehensive cross-transport DoS protection.
|
||||
|
||||
## Known inconsistencies
|
||||
|
||||
These observations should be checked when touching the relevant area; they are not automatically bugs requiring unrelated cleanup.
|
||||
|
||||
- Some source comments still call shared commands UART0-only or call the current local status/control task read-only.
|
||||
- Legacy-removal module contracts now make `USER_DATABASE_LOAD_EMPTY` a successful persisted-empty initialization result; older migration/sentinel observations are superseded. Final startup integration/build evidence is pending.
|
||||
- SSH startup is currently gated on successful `web_security` initialization even though SSH uses separate host-key material. **Needs verification:** whether this coupling is intentional recovery policy or an accidental startup dependency.
|
||||
|
||||
## Items to verify in future work
|
||||
|
||||
- Confirm task-local Newlib standard-stream behavior if ESP-IDF/Newlib configuration changes; admin SSH command output relies on dispatcher-task stream redirection.
|
||||
- Re-audit the private HTTPD adapter on SDK changes (including same-version patches): parsed-header layout, right-aligned pending data, explicit handshake/frame installation and log suppression. Host tests do not establish real socket behavior.
|
||||
|
||||
## Completed Task - Phase 8D.3 Live Authentication Cutover
|
||||
|
||||
- **Inherited implementation:** `web_cookie_auth.{c,h}`, `web_httpd_adapter.{c,h}`, parser optional-cookie validation, server/console/transport integration, browser app/session recovery, and focused suites. No Basic authorization/cache remains. Four one-hour sessions, four 120-second pre-login challenges, five password checks per 60-second window, four non-evicting 30-second tickets/two serial sockets. Fourteen URI slots, unchanged six HTTPS sockets and application task/stack/queue capacities. No generated assets or SDK files changed.
|
||||
- **HTTPD decision:** Other agent chose an isolated **private-IDF adapter**, not the previously proposed SDK patch. Verified first-only header getters, append-only pointer-backed Set-Cookie (six-header successful login), auto-101-before-handler flow and private frame installation against installed 5.5.0. Serial URI uses ordinary GET until authenticated ticket/currentness/broker admission, then explicit handshake. Exact version guard requires re-audit on update; not a source-hash guarantee. HTTPD logs above ERROR compiled out to avoid secrets/ticket queries. Durable boundary recorded in architecture/design decisions and implementation record.
|
||||
- **Fix in this continuation:** Pending HTTPD bytes are right-aligned. Inherited wipe preserved the wrong end, risking pipelined HTTP/early-frame corruption. Fixed consumed-prefix wipe and tested 0–128 pending lengths plus partial reads using extracted installed `httpd_recv_pending`. Kept all other inherited source work intact.
|
||||
- **Validation:** `python3 tests/web_cookie_auth/run.py`, parser 268-case suite, login UI eight Node groups, serial app nine Node groups, and store `--serial` integration mode pass. Cookie suite also executes store tests and extracts installed header getter/setter/pending-reader functions; handshake/network/tasks remain doubled. Final `pio run` passed **17.62 s**, **95,508 B RAM / 1,625,689 B flash**; +248/+23,764 B versus 8D.2, +976/+25,716 B versus recorded 8D.0. Auth symbols 637 B before placement padding (including 576 B challenges); Basic cache/key removal offsets much of it. No runtime reserve or stack margin inferred.
|
||||
- **Handoff (updated at sign-off):** `docs/phase8d3_implementation.md` contains policy, source verification, accounting, user-provided login/mixed-client/post-soak evidence and explicit **M1 closure on 2026-09-06**. No agent device operation or new build was performed to record sign-off. Earlier missing-check notes are evidence limitations/regression coverage, not blockers to this closure. Numeric reserves remain open. Next implementation is **8D.4 only when separately requested**; preserve prior sign-offs and the distinction between connected post-soak data and disconnected cleanup.
|
||||
|
||||
## Previous Task - Phase 8D.3 Inert Login Rendering
|
||||
|
||||
- **Scope:** Second permitted preparatory split after scope review of remaining challenge/throttle/HTTP-route/application work. Added only `src/web_login_ui.{c,h}`, CMake registration, `tests/web_login_ui/` and documentation. Standalone renderer has no live HTTP caller or URI registration; Basic cache/auth, existing app and serial protocol, capacities, task stacks and generated assets are unchanged. No upload, erase, commit or branch change.
|
||||
- **Behavior when integrated:** No fetch on page load; explicit Sign in obtains challenge with `X-Login-Bootstrap: 1`, then POSTs JSON with CSRF. Same-origin credentials/mode, no-store fetch, redirect rejection, 512-byte response bound and UTF-8 field/body limits. Generic safe-text errors, bounded Retry-After display/manual backoff, no automatic credential retry, fixed success navigation to `/`. Inputs disabled while pending; password fields/references cleared best-effort, attempt aborted on every exit, 15-second deadline, pagehide/pageshow generation guards. No localStorage/cookie access or logging; JavaScript/browser memory cannot be securely wiped.
|
||||
- **Policy/resources:** Standalone 7,387-byte HTML document plus terminator in its object; no protected asset dependencies or template interpolation. Five additional headers (no-store, nosniff, no-referrer, frame denial, matching SHA-256 CSP), below existing eight-header limit. Rendering itself allocates no module heap/session/task. Unused renderer leaves final linked RAM/flash **95,260 / 1,601,925 B**, unchanged versus 8D.2 and parser split. Future linked/read-only data, HTTPD send/TLS and runtime stack/heap costs remain cutover measurements; no runtime reserve approval implied.
|
||||
- **Validation:** `python3 tests/web_login_ui/run.py` passes production-C NULL/header/send-failure checks, exact rendered CSP hash and eight Node VM behavior groups. `python3 tests/web_auth_parse/run.py` passes 268 cases; existing store and `--serial` host modes pass. Independent review caught/fixed unread error-body lifetime and re-entered password retention; regression tests cover abort signals/modeled streams and pending-input cleanup. Final `pio run` passed in **8.25 seconds**, unchanged sizes. `git diff --check` passed. No real browser/CSP enforcement/bfcache, HTTPD sockets, TLS or target exercise.
|
||||
- **Next exact work:** Continue **8D.3 atomic live cutover**, not more login rendering or 8D.4. Reuse prepared parser/renderer/store/serial binding. Implement bounded challenge/throttle and complete cookie/session/CSRF/Origin policy, replace Basic for every app/asset/status/ticket/upgrade route together, add `/login`, challenge/login/session/logout routes, and integrate existing application's session validation/logout/401 recovery without changing explicit serial reconnect. Retain no-live-eviction ticket policy cutover, fail-closed initialization and account/session-specific revocation. Duplicate-header detection and two Set-Cookie behavior must be verified against actual IDF HTTPD, not assumed. Keep response header/route/body budgets explicit. Then M1 target/browser pause and resource evidence; no M2 work.
|
||||
- **Remaining gates:** Full M1 checklist in plan/baseline applies only once live: both roles/fresh and former Basic profiles, direct IP/mDNS, challenge/session/capacity/throttle/Origin/CSRF errors, logout/account switch/expiry/reboot/revocation isolation, repeated serial cycles, UART0/USB/user+admin SSH and soak/cleanup memory. Numeric reserves remain pending; 8D.2 user sign-off stands.
|
||||
|
||||
## Previous Task - Phase 8D.3 Inert Request Parsing
|
||||
|
||||
- **Scope:** Private allocation-free helpers for canonical Host/Origin comparison, unique named 64-lowercase-hex cookie extraction, and strict login JSON. CMake registration plus `tests/web_auth_parse/`; no live route, UI, store, transport, task, socket, stack-size or generated-asset change. Split before coding because full challenge/throttle/routes/UI/tests cutover exceeds the work-unit guideline. No commit, upload or erase.
|
||||
- **Contracts:** Exact bounded byte spans; canonical origin max 128 bytes after optional default-port normalization, ASCII DNS/IPv4 authorities only (IPv6 rejected), mandatory matching HTTPS Origin. Cookie header max 1024 bytes, strict unquoted values even for unrelated cookies. Login body max 512 bytes, exactly username/password strings, decoded limits 16/64 bytes; proper UTF-8/JSON escape/surrogate validation. Unknown/duplicate fields and NUL rejected. Database policy remains authoritative. Failures clear complete outputs; successful credentials and original body require caller wiping. No dynamic allocation or persistent state.
|
||||
- **Validation:** Focused production-C host suite, existing store and `--serial` modes passed. Independent review found no memory-safety defect; default-port maximum-length normalization was fixed and regression cases added. Final rerun/build results recorded in the plan. Host tests are not real HTTPD/browser, sanitizer or target validation.
|
||||
- **Resources:** Build reports **95,260 B linked RAM / 1,601,925 B flash**, unchanged from 8D.2. Unused helpers are not live firmware paths; their future linked flash and request-stack cost must be measured at cutover. No new runtime allocation, task, route, queue or socket. Numeric reserve gates remain pending.
|
||||
- **Next exact work:** Continue 8D.3, not 8D.4: wire the complete challenge/session/login/logout/CSRF/Origin policy and browser recovery atomically, or first take the permitted inert login rendering split if the remaining change still exceeds scope. Never expose partly protected cookie routes. Integration must enforce duplicate HTTP header rejection, bounded complete body reads, content type/method/Fetch Metadata checks, and wipe request buffers on every exit. Bootstrap's permitted missing-Origin GET requires separately validated Host handling, not weakening mutation/upgrade Origin checks. Keep account validation tied to existing database capacities. Remove Basic only with the complete security cutover, then stop for mandatory M1 browser/target sign-off.
|
||||
- **Target checks:** No hardware execution for this inert split. On the live cutover run the full M1 checklist in `docs/phase8d_plan.md` and `docs/phase8d_baseline.md`: both roles, usable errors/logout/account switching, expiry/reboot/revocation/isolation, direct IP/mDNS, capacity/throttle/CSRF, serial reconnect, UART0/USB/user+admin SSH and repeat-cycle/soak memory evidence. No browser-authentication success is claimed here.
|
||||
|
||||
## Previous Task - Phase 8D.2 Serial Session Binding
|
||||
|
||||
- **Checkpoint/scope:** Clean `93eef0e67641f2672c56692a7785e50f31cf236d`; only `web_serial_transport.{c,h}`, `web_session_store.{c,h}`, one Basic ticket call in `web_server.c`, focused host tests and documentation changed. No upload/erase/commit or generated assets. Existing `user_console` and legacy-sync callers already reach the extended revocation hook.
|
||||
- **Implementation:** Non-reused 64-bit originating IDs in four tickets/two slots, zero explicitly Basic-only. Matching-ID upgrade and exact principal binding, expiry/currentness at sensitive boundaries and existing periodic owner checks. Session-specific invalidation/cleanup, account-name/all-session invalidation even if serial init failed, reserved-slot revocation and non-wrapping ticket publication epoch. No new task, heap allocation, route, capacity or stack-size change; Basic cache/currentness retained.
|
||||
- **Validation:** `python3 tests/web_session_store/run.py` and `python3 tests/web_session_store/run.py --serial` pass using OpenSSL SHA-256 and deterministic dependency interleavings. Integration exercises production transport private steps with the real store, not real HTTPD sockets/tasks. Expanded checks cover isolation, mismatched identity fields, expiry/lost notifications/DB failure, close-queue retry, slot reuse and mint/admission races. Optional `--serial --sanitize` cannot link because this host lacks ASan/UBSan runtime libraries and static archives; not a pass.
|
||||
- **Build/resources:** Final source `pio run` passed in 7.92 seconds at **95,260 B RAM / 1,601,925 B flash**, **+56 / +1,420 B** versus 8D.1 and **+728 / +1,952 B** versus 8D.0. Target symbol/DWARF: ticket 96 B x 4 (+32 B array), slot 1,664 B x 2 (+16 B array), new epoch 8 B; store remains 664 B. Bound resolver frame 192 B excluding callees; no runtime stack/heap reserve claim.
|
||||
- **Target/handoff:** `pio device list` sees device CDC and USB serial adapter, but this diff was not flashed or exercised on target. Follow `docs/phase8d2_implementation.md` for two-role Basic five-cycle regressions, mutation isolation, five HTTPS restarts, USB/user+admin SSH/UART0 smoke, 15-minute 115200-baud full mix and 60-second cleanup/memory evidence. Numeric floors remain pending. Cookie-route session logout/expiry/origin validation remains dormant until separately requested 8D.3; obtain target sign-off or explicit user decision before stacking it. No later phase work performed.
|
||||
|
||||
## Previous Task - Phase 8D.1 Internal Session Primitives
|
||||
|
||||
- **Objective:** Phase 8D.1 only. User explicitly requested continuation with remaining reserve/validation gates pending, then supplied a second pre-change full-client-mix sample. No browser auth cutover or continuation to 8D.2.
|
||||
- **Context:** The user reports that the earlier experimental implementation was moved to a separate branch and `devel` rolled back after repeated agent interruptions, a roughly +10k/-1k-line diff, broken browser login, and severe memory pressure. These symptoms have not been independently diagnosed. Do not import that branch wholesale.
|
||||
- **Changes completed:** Added `src/web_session_store.{h,c}`, CMake registration and small admitted-HTTPS-start/stop hooks in `web_server.c`. Four static digest-only sessions, copied principals, separate CSRF/origin state, absolute expiry, generation-safe IDs/epochs, invalidation/prune and count-only snapshot APIs. Database/crypto outside portMUX, post-call revalidation. No issuing HTTP callers, ticket/socket/revocation binding, new routes/tasks, UI change or regenerated assets. Added focused `tests/web_session_store/` runner and updated baseline/plan/architecture/code map.
|
||||
- **Baseline:** Clean `devel` at `af89dd1bd96cdd97d8d57eee7a29f68e3874506b`, PlatformIO espressif32 6.12.0 / IDF 5.5.0, N16R8 release. `pio run` passed in 36.08 seconds: 94,532 B linked RAM and 1,599,973 B flash (historical RAM unchanged, flash +208 B, cause not diagnosed). `pio device list` returned no ports. No agent upload or target/browser test was performed. The user subsequently supplied a runtime heap baseline; stack measurements remain pending.
|
||||
- **User-provided runtime memory baseline (2026-09-05):** Administrative SSH transcript with one browser serial writer (broker 8), one public-key user SSH observer (broker 9), and one public-key admin SSH session; web 1/2, SSH 2/2, zero tickets, no USB broker client listed. Heap bytes in free/minimum-free/largest-block order: internal 8-bit **39,668 / 13,876 / 18,432**; internal DMA **31,912 / 6,120 / 18,432**; PSRAM **8,138,380 / 8,107,324 / 7,995,392**. Accepted as the runtime baseline, not reserve floors or full-workload validation. Flashed revision, baud, duration, traffic and stack margins were not supplied. See `docs/phase8d_baseline.md` for provenance and remaining checks.
|
||||
- **Follow-up pre-change full-client-mix sample:** Two web sessions (broker 8 writer, 10 observer), USB broker 11 observer, user SSH broker 9 observer and admin SSH; four broker clients, SSH 2/2, web 2/2, no tickets. Internal 8-bit **41,868 / 13,876 / 18,432 B**; DMA **34,112 / 6,120 / 18,432 B**; PSRAM **8,162,784 / 8,107,324 / 7,995,392 B** (free/minimum/largest). Cumulative web/SSH traffic with no reported transport I/O/protocol/queue failures; one web auth failure and two rejected SSH RX bytes retained without diagnosis. This is not timed-soak or byte-integrity evidence; see baseline record.
|
||||
- **8D.1 validation/accounting:** Started at clean `02fdeee3453654680c11096c9a6224c26233eced`. `python3 tests/web_session_store/run.py` passes with OpenSSL SHA-256, deterministic dependency doubles and injected callback races; no hardware or real multicore scheduling claim. Final `pio run` passed in 8.90 seconds: **95,204 B RAM (+672 B)**, **1,600,505 B flash (+532 B)**. Target object/DWARF: record 152 B × four, state 656 B + lock 8 B = **664 B**, no module heap/new task/socket/route cost, Basic cache retained. Only init/stop currently survive firmware linker GC; full primitive flash/stack costs become relevant when future callers are added. Independent review found/fixed store reinit by rejected concurrent start. Post-change target run pending.
|
||||
- **Planned contract decisions:** Four one-hour absolute sessions without idle/sliding renewal, four 120-second pre-login CSRF challenges, four 30-second serial tickets, no live-record eviction and no Basic compatibility after cutover. Strict same-origin mutation/upgrade checks, session-specific logout versus account-wide revocation, bounded global login throttle. These are functional choices, not measured memory budgets or implemented behavior.
|
||||
- **Next:** Stop at 8D.1. Obtain post-change Basic browser/serial/USB/user+admin SSH/UART0 regression and comparable `memory`/counters/`ssh status` evidence; five serial disconnect/reconnect cycles per role and HTTPS stop/start checks. Timed soak/cleanup and reserve floors remain pending. Next implementation is 8D.2 session-specific ticket/socket binding, only after checkpoint or explicit user decision; M1/M2 pauses remain. Host tests cover dormant primitives, not browser logout/currentness integration. ID/epoch exhaustion, collision rejection and private memory wiping are source-reviewed only.
|
||||
- **Risks / things to remember:** Six HTTPD client slots include WebSockets; HTTPD infrastructure plus HTTPS and SSH can consume twelve of sixteen descriptors before other services/transients. Existing LRU purge is enabled and must not evict retained serial to admit M2 admin sockets. Nine current route slots have no spare capacity; the proposed M1 contract adds five. Cookie sessions, tickets, sockets and request scratch are separate costs. Only SSH exposes stack margin today. Numeric runtime floors and per-chunk/cumulative budgets remain pending, not guessed.
|
||||
|
||||
## Previous task — mDNS (target checks still pending)
|
||||
|
||||
- **Objective:** Announce a configurable `sak-<suffix>.local` hostname through mDNS when Wi-Fi STA has an IPv4 address, without changing the Wi-Fi NVS blob schema.
|
||||
- **Relevant files:** `src/mdns_config.{c,h}`, `src/mdns_service.{c,h}`, `src/mdns_console.{c,h}`, `src/wifi_manager.{c,h}`, `src/main.c`, `src/CMakeLists.txt`, `src/idf_component.yml`, `dependencies.lock`, completion and command documentation.
|
||||
- **Findings:** `wifi_manager` already serializes all meaningful STA transitions through its permanent task; callbacks only enqueue events. This is the appropriate lifecycle owner for mDNS, while a separate configuration module preserves the existing `wifi_app/config` wire format.
|
||||
- **Decision:** Persist a fixed v1 record under `mdns_cfg/config`, separate from Wi-Fi configuration. Defaults derive a safe lower-case hexadecimal suffix from the STA MAC. The manager initializes mDNS at most once after validating `IP_EVENT_STA_GOT_IP`; the managed component's own handlers withdraw/restore the STA responder across connectivity changes, and online hostname changes use `mdns_hostname_set()` without teardown. Initialization failure is latched instead of retried because the resolved upstream 1.12.0 component has an unsafe partial low-memory initialization path. mDNS errors cannot fail Wi-Fi, UART0, UART1, or native USB.
|
||||
- **Changes completed:** Added the `espressif/mdns` managed dependency (resolved to 1.12.0 on IDF 5.5), mDNS config/service/console modules, `mdns status|suffix|save|load|defaults|reset`, completion, CMake integration, and command/architecture documentation. Minimized the component to STA-only responder use, moved general allocations to PSRAM, retained the internal task stack, and removed reconnect-time free/reinit churn. Final `pio run` passes at 94,532 bytes linked RAM and 1,599,765 bytes flash.
|
||||
- **Remaining work:** Target-hardware verification: associate a station and resolve the default `sak-<mac>.local`; change/save/load a suffix and confirm live reannouncement plus reboot persistence; stop Wi-Fi or remove the STA lease and confirm the record withdraws. Verify serial, native USB, and UART0 remain available if mDNS initialization fails.
|
||||
- **Risks / things to remember:** Hostnames are STA-only and are intentionally not announced by fallback AP mode. NVS changes to `mdns_cfg/config` are independent of the unchanged `wifi_app/config` blob. mDNS remains allocated after first successful initialization (including its internal 4 KiB task stack) to avoid fragmentation and unsafe repeated initialization; measure free/minimum/largest internal heap and mDNS stack margin during reconnect stress.
|
||||
|
||||
### Handoff template
|
||||
|
||||
- **Objective:**
|
||||
- **Relevant files:**
|
||||
- **Findings:**
|
||||
- **Decisions made:**
|
||||
- **Changes completed:**
|
||||
- **Remaining work:**
|
||||
- **Risks / things to remember:**
|
||||
Validation completed: independent Python local-link/anchor audit PASS across 29 authored Markdown files (122 local links, 53 Markdown fragments); 213 authored source/test/document files checked with zero obsolete Phase 8D filename references. Earlier path audit resolved127 expanded navigation references. GPT logs, vendored/generated trees and remote URL fetching excluded. Twenty obsolete phase documents removed, with no forwarding stubs/archive dump. Git diff/scope checks confirm documentation-only changes. No firmware tests/build/device commands run.
|
||||
|
||||
@@ -4,17 +4,15 @@ Only constraints supported by implementation or current project documentation be
|
||||
|
||||
## Display configuration has an owner reservation separate from button activity
|
||||
|
||||
**Decision (8D.14):** `local_status_ui` owns a nonwrapping RAM config generation and a zero-wait reservation shared by typed Display operations and canonical CLI/legacy Apply. Compare/reserve and publish occur in short timing critical sections; NVS occurs outside them. Save stabilizes selected bytes, Load retains canonical fallback, and Reset commits defaults before RAM publication, including CLI. This replaces Reset's apply/rollback race with failure-before-publication semantics.
|
||||
**Decision:** `local_status_ui` owns a nonwrapping configuration generation and a zero-wait reservation shared by typed Display and CLI/legacy Apply. NVS runs outside timing critical sections; Save stabilizes selected bytes and Reset commits defaults before RAM publication. Load retains canonical fallback. Buttons/diagnostic holds update activity, not configuration generation.
|
||||
|
||||
**Consequence:** Browser mutations must carry the selected nonzero generation, including Save; never silently replace intervening CLI edits. Buttons and diagnostic holds retain independent activity state, not config generation or persistence ownership. Do not hold timing locks across storage or I2C, add display-presence prerequisites to configuration, or mistake a successful config API for physical panel success. The existing dispatcher carries IDs only; the secret-free slot uses a30-second dequeue admission deadline, not a new timer or hard cancellation. Preserve explicit result uncertainty and no automatic mutation replay. `docs/phase8d14_implementation.md` records the exact contracts and pending target gates.
|
||||
**Consequence:** Compare before mutation, never overwrite intervening CLI edits, and do not make physical panel presence a configuration prerequisite. A successful config API is not proof of display IO. [Owner and persistence contract](../web_administration.md#serial-and-display).
|
||||
|
||||
## Typed Network edits preserve manager ownership and current secret bytes
|
||||
|
||||
**Decision (8D.12/8D.13):** `web_network_settings` admits bounded typed operations into one login-bound slot; the existing dispatcher carries IDs only and calls canonical generation-checked Wi-Fi/mDNS APIs. HTTPD reads only zero-wait secret-free projections. Wi-Fi mutex-local compare/merge/validation preserves omitted PSKs and prevents stale edits undoing CLI/local changes; queue admission precedes RAM publication. Save stabilizes selected bytes under the mutex; browser Wi-Fi Load reads stored configuration only, never generates fallback AP secrets. mDNS uses its own generation and reports RAM-applied/reannouncement-not-queued separately.
|
||||
**Decision:** HTTPD uses zero-wait secret-free projections and an ID-only dispatcher. Wi-Fi compare/merge/whole-candidate validation and queue-before-publication preserve omitted secrets and reject stale changes; its manager alone owns radio/reannouncement. mDNS generation/persistence is independent. Save stabilizes selected RAM; Wi-Fi Load is stored-only, not fallback-secret generation.
|
||||
|
||||
**Consequence:** Keep SSIDs byte-reversible (UTF-8 text must first become bytes; arbitrary existing bytes require hex), password omission/Replace/disabled-STA Clear distinct, and AP clear denied even while off. No default/reset/secret-export route or explicit-index connection selection: only canonical Next. One-second timer/30-second queued expiry plus scheduling latency is not hard cancellation of admitted work. `accepted` is owner admission, not online or verified DNS; acknowledgements may be lost before network disruption. Recovery/confirmation and no automatic mutation replay are correctness requirements, not UI polish. Optional route failures must not gate unrelated services; browser-shell policy remains separate. No task/stack/queue/schema expansion; new timer/slot/buffer costs still require target heap and HTTPD/dispatcher margin evidence.
|
||||
|
||||
**Relevant files and full contract:** `src/web_network_settings.{c,h}`, `src/wifi_manager.{c,h}`, `src/mdns_service.{c,h}`, `src/web_ui.c`, `docs/phase8d12_13_implementation.md`.
|
||||
**Consequence:** Keep SSIDs reversible bytes, omitted/Replace/disabled-STA Clear distinct, AP clear denied, and Next profile separate from editor selection. mDNS may report RAM applied but reannouncement not queued, without rollback. A one-second queued-secret timer is not hard cancellation; `accepted` is not online/DNS. Recovery and no automatic replay are correctness requirements. [Network contract](../web_administration.md#network).
|
||||
|
||||
## One broker mediates all production serial transports
|
||||
|
||||
@@ -34,7 +32,7 @@ Only constraints supported by implementation or current project documentation be
|
||||
|
||||
**Consequence for future changes:** Do not replace fan-out with a blocking shared queue. Any added transport must tolerate partial/no-progress reads and expose drop/backpressure counters.
|
||||
|
||||
**Throughput observation and controlled experiments:** The initial diagnostic baseline used CPU160MHz; a CPU240MHz-only experiment reduced but did not eliminate browser queue overflow. Combining binary WebSocket header/payload into one bounded session-override send eliminated reported drops, and the user signed off230400-baud full-client-mix operation after returning to160MHz. Retain the combined send, not the frequency increase; evidence and limits are in `current-state.md`. Preserve scheduling/priorities and 4096/512-byte broker/web buffers while gathering per-client HWM/drop attribution and independent opt-in web binary-TX timing. Fixed-slot epoch/generation-fenced aggregates avoid stale attribution; no new runtime allocations. Clear preserves queued data and seeds broker HWM; disconnected rows disappear while global discard counts remain. Callback timestamps precede the transport lock; synchronous send return is not peer receipt. Completion-to-read intervals include broker/mutex/control work and possible idle, even when the first read is nonempty; never label them pure scheduling latency or proof of backlog at completion. Compare enabled/disabled target captures before drawing overhead conclusions. Contracts and reproduction: `docs/web_throughput_diagnostics.md`.
|
||||
**Throughput observation and controlled experiments:** The initial diagnostic baseline used CPU160MHz; a CPU240MHz-only experiment reduced but did not eliminate browser queue overflow. Combining binary WebSocket header/payload into one bounded session-override send eliminated reported drops, and the user signed off230400-baud full-client-mix operation after returning to160MHz. Retain the combined send, not the frequency increase; evidence and limits are in `current-state.md`. Preserve scheduling/priorities and 4096/512-byte broker/web buffers while gathering per-client HWM/drop attribution and independent opt-in web binary-TX timing. Fixed-slot epoch/generation-fenced aggregates avoid stale attribution; no new runtime allocations. Clear preserves queued data and seeds broker HWM; disconnected rows disappear while global discard counts remain. Callback timestamps precede the transport lock; synchronous send return is not peer receipt. Completion-to-read intervals include broker/mutex/control work and possible idle, even when the first read is nonempty; never label them pure scheduling latency or proof of backlog at completion. Compare enabled/disabled target captures before drawing overhead conclusions. Contracts and reproduction: [throughput diagnostics](../web_throughput_diagnostics.md).
|
||||
|
||||
**Relevant files:** `src/session_broker.c`, `src/session_broker.h`, `docs/roadmap.md`
|
||||
|
||||
@@ -50,27 +48,15 @@ Only constraints supported by implementation or current project documentation be
|
||||
|
||||
## Resource IDs are generation-safe
|
||||
|
||||
**8D.19 SSH ordinary-control decision:** A transport disconnect must use the owning transport's handle, never an arbitrary broker client ID or socket fd. SSH retains its encoded session ID but retires exhausted generations instead of wrapping, preserving exact-ID owner-close consumption across reuse. Typed lifecycle confirmation adds a distinct saturated service generation advanced by canonical lifecycle admission, including CLI; compare and start/stop admission retain the existing command mutex. Published zero-wait snapshots do not scan owner/task state. HTTPD only admits current-admin bounded typed work to the existing dispatcher; it never waits for SSH or invokes wolfSSH. Disconnect success is an owner request, lifecycle timeout is not cancellation, and admitted work may finish after revocation. All-SSH stop explicitly includes new admissions before execution. Existing browser-shell SSH deferral restrictions stay intact. SSH-only first slice, not all-service8D.19 or8D.20; exact bounds/contracts: `docs/phase8d19_implementation.md`.
|
||||
**Decision:** Broker/SSH/WebSocket slots, originating web sessions, queued admin operations and account principals carry distinct generation/identity fences. Browser cookie-session identity is not interchangeable with account identity. Invalidate session records before socket cleanup and retain authoritative currentness checks even when notifications fail.
|
||||
|
||||
**Decision:** Broker clients, SSH/WebSocket slots, queued admin work, and user principals carry generations or random stable IDs to reject stale references and slot reuse.
|
||||
|
||||
**Rationale/evidence:** Broker IDs encode slot generation; transports track slot generations; admin tokens include session/slot generation; user principal currentness includes account ID and authentication generation.
|
||||
|
||||
**Consequence for future changes:** Preserve transport-slot generations and account-authentication generations as distinct concepts. Validate tokens immediately before side effects and discard late work after disconnect/reuse/revocation.
|
||||
|
||||
Phase 8D.2 adds a third identity: non-reused 64-bit originating web-session IDs in serial tickets/slots. 8D.3 rejects zero IDs; Basic authentication/cache are removed. Session-specific cleanup must not become account-wide cleanup; account-name notification intentionally covers deletion/recreation. Invalidate cookie records before requesting transport cleanup, and retain authoritative session/principal checks when notification fails. The transport epoch cancels in-flight ticket publication without taking store and transport locks together.
|
||||
|
||||
**Relevant files:** `src/session_broker.{h,c}`, `src/ssh_transport.c`, `src/web_serial_transport.c`, `src/admin_ssh_console.c`, `src/user_database.{h,c}`
|
||||
**Consequence:** Never turn selected transport IDs into arbitrary broker IDs/fds, rebase a stale confirmation or wrap a published token. Exhausted broker/SSH slots retire; operation/reservation IDs do not reuse; saturated service/lease generations fence ABA without disabling canonical recovery. Reboot invalidates old logins. Validate/reserve at the owner immediately before effects, not snapshot-check/unlock/unconditional mutation. [Broker](../web_administration.md#broker-and-contextual-controls) and [service](../web_administration.md#service-lifecycle-and-identity-rotation) contracts.
|
||||
|
||||
## Confirmed writer transfer compares a lease version inside the broker lock
|
||||
|
||||
**8D.16 decision:** A client ID alone cannot authorize a stale browser confirmation: the writer may release and reacquire while a dialog is open. The compact management projection copies clients, writer and a separate lease generation under one zero-wait mutex acquisition. Conditional assignment validates target and lease generation in the same force-writer lock before any changes; existing unconditional console/recovery APIs remain available.
|
||||
**Decision:** Compare the connected nonreused target and separate lease generation inside the force-writer lock. A client ID alone cannot fence release/reacquire ABA. Lease generation saturates, survives counter clear and advances before advisory event delivery, potentially twice for force transfer.
|
||||
|
||||
**Wrap contract:** Three-slot-bit/29-generation-bit broker IDs now retire exhausted slots instead of wrapping. A32-bit lease generation saturates at UINT32_MAX, survives counter clear, and advances for each grant/release/revoke emission before advisory queue delivery. Forced transfer can advance twice; it is an opaque version, not a count. Saturation rejects typed assignment but never prevents normal release/disconnect/request or recovery force. Future writer transitions must preserve the central event-emission invariant. Reboot resets broker state but invalidates web sessions, so authorized old browser work cannot span boots.
|
||||
|
||||
**8D.18 browser selection contract:** Live refresh preserves explicit target identity and its original lease token, not just the select value. Lease/identity mismatch, absence or failed read latches invalidation; later matching snapshots cannot resurrect validity or silently reselect a missing target. Only explicit reselection captures a new token, and transfer still requires separate confirmation. One shared quick/full controller fences reads/operations; contextual activation never destroys full-page drafts. Focused controls remain focusable with aria-disabled while independent action guards reject unavailable work. See `docs/phase8d18_implementation.md` for bounds and test limits.
|
||||
|
||||
**Consequence:** Never implement snapshot-check-unlock-force, compare only current writer ID, or renew a confirmation implicitly during Refresh. UI must require explicit target selection and confirmation, retain uncertain-outcome handling and never retry mutations automatically. Bounded login-isolated result slots and the existing dispatcher remain the typed HTTP boundary. Details/tests: `docs/phase8d16_implementation.md`, `src/session_broker.{c,h}`, `src/web_broker_settings.{c,h}`.
|
||||
**Consequence:** Saturation rejects typed assignment but preserves ordinary request/release/disconnect and recovery force. Accepted serial TX is not recalled. Live contextual refresh must retain the selected versions and sticky stale/absence state until explicit reselection. [Wrap and UI contract](../web_administration.md#broker-and-contextual-controls).
|
||||
|
||||
## UART0 is the physical recovery authority
|
||||
|
||||
@@ -104,79 +90,45 @@ Phase 8D.2 adds a third identity: non-reused 64-bit originating web-session IDs
|
||||
|
||||
## Selected self-affecting admin SSH actions use bounded deferred control
|
||||
|
||||
**8D.21 SSH host identity decision:** Reuse canonical deferred SSH self-rotation and typed19 SSH routes/dispatcher, not command strings or another executor. Compare/reserve service then identity before stop or storage; retain the existing command mutex and task-bound nonreused security token through canonical stop→commit→conditional restart. Direct security rotate/reset share identity exclusion. Crypto/NVS run outside security locks/spinlocks; failed stop must not mutate identity or attempt start, and failed persistence may already have disconnected SSH. Never roll back committed identity after restart failure. Retain wolfSSH context until every slot retires; only owner frees it before clearing cleanup admission, and start refuses orphan handles. Public metadata is zero-wait/atomic within security, not a cross-owner authorization; UI confirms both generations and warns changed known_hosts/trusted UART0 `ssh host-key info`, partial effects and no replay. HTTPS remains accessible, so existing SSH queue/response order is retained without20's self-cutting ACK gate. Canonical reset/recovery preserved but excluded from browser as duplicate healthy rotation plus distinct recovery semantics. Details and test limits: `docs/phase8d21_implementation.md`.
|
||||
**Decision:** SSH self-close/reboot/lifecycle/host-key actions use existing bounded application-drain control; browser shell stop/reboot and typed certificate handoff use owner adapters, not command replay. Drain is not peer receipt. Crypto/NVS browser rotation runs on the existing dispatcher, not the small control stack; executing slots remain reserved across self-detach.
|
||||
|
||||
**8D.20 integrated typed HTTPS/reboot decision (supersedes prerequisite-only status below):** A successful synchronous HTTP response send return is the ACK boundary, not peer receipt. Queue exactly one nonreused-ID HTTPD callback after sending; that callback only submits an ID to the existing dispatcher, never waits or runs lifecycle. No captured request/fd/reusable slot pointer. A lost callback remains reserved even after its two-second admission deadline; only its arrival or successful HTTPD destruction releases that reservation. Do not retry queue submission or permit callback accumulation. Original-login/current-admin/post-validation30-second deadline precede canonical generation-conditional owner admission; no cancellation claim after admission, including detach failures and deliberately login-invalidating reserved restart. Typed reboot uses shared `esp_restart()` outside locks after HTTPS generation reservation, not a console string/self-cleanup wait or new runner. UI requires explicit confirmation, fences15-second whole requests and late results, retains unknown/duplicate gates across navigation, never restores/retries mutations and requires fresh login after HTTPS restart. Network controls remain the sole Wi-Fi domain; USB is UART1 serial recovery, not administration or uninterrupted whole-device reboot. Exact contracts and target limits: `docs/phase8d20_implementation.md`.
|
||||
**HTTPS typed ACK:** Successful synchronous send return precedes one nonreused-ID HTTPD callback to the existing dispatcher. No captured request/fd/reusable operation pointer or lifecycle wait on HTTPD. Lost accepted work retains one reservation until callback or successful destruction; never release speculatively on timeout. Original-login/current-admin/dequeue deadline checks precede admission. Reserved restart may deliberately invalidate that login; subsequent revocation is not cancellation. Reboot invokes canonical `esp_restart()` outside locks without self-console cleanup.
|
||||
|
||||
**Historical 8D.20 HTTPS owner prerequisite (superseded above):** Conditional HTTPS stop/restart compares an expected saturated lifecycle generation under the canonical server mutex, not snapshot-check-unlock-unconditional-stop. Restart retains transition ownership through stop and start; a failed stop never admits start, and failed cleanup requires canonical recovery. Repeated init must not clear the retained lifecycle failure; counter clear must not reset generation. The zero-wait management projection does not authorize a request or prove reachability. All lifecycle execution remains off HTTPD and outside the server mutex during owner waits. Future typed ACK handling must precede admission on the existing dispatcher, with original-login currentness; later revocation is not cancellation of an admitted restart. No ACK/API/UI/reboot integration exists in this prerequisite, and the full phase remains incomplete. `src/web_server.{c,h}`, `docs/phase8d20_implementation.md`.
|
||||
**Identity replacement:** Compare/reserve service then task-bound nonreused identity token, shared by canonical/direct callers. Crypto/NVS run outside security locks. HTTPS commits before stop/start; SSH stops before commit/restart. Failed SSH stop skips mutation/start; persistence failure can follow client disconnection. Committed material is never rolled back on restart failure. SSH owner retains context until all slots retire; start rejects orphan handles. Public fingerprint projections do not authorize mutation, and stored/served HTTPS identity can differ after failed stop.
|
||||
|
||||
**Decision:** Admin SSH `exit`, remote reboot, SSH stop/disconnect, and host-key rotate/reset are deferred until command state and administration/transport application buffers appear drained, with a ten-second limit and short final delay.
|
||||
|
||||
**Rationale/evidence:** `admin_ssh_console` has a separate bounded control task and pending-action state. The check is a best-effort application-buffer heuristic, not peer-delivery confirmation. User account mutations and their immediate revocation calls do not use this path.
|
||||
|
||||
**Consequence for future changes:** Actions that would invalidate their own SSH transport should integrate with deferred control when acknowledgement preservation matters. Prevent new input while an action is pending, keep the wait bounded, and do not describe it as guaranteed delivery.
|
||||
|
||||
Phase 8D.4 routes drain/lifecycle operations through a firmware-lifetime immutable owner adapter on the existing control task, outside console locks. Tokens include a transport namespace; owners revalidate full identity and marshal to their transport APIs. `SELF_CLOSE` targets the invoking frontend while existing SSH action meanings remain unchanged. Unsupported actions must fail before side effects. The two console slots remain a shared bounded pool, with no hypothetical browser capacity allocated. The 8D.5 prerequisite additionally requires owner currentness on the dispatcher, outside console locks, before commands and during prompts; account currentness alone cannot establish originating browser-session liveness. Recheck token identity after external validation, reject revoked submitted replies, and wipe consumed output. Polling is not a hard cancellation deadline and cannot roll back arbitrary handlers; owners retain admission/input/output/lifecycle responsibilities.
|
||||
|
||||
**8D.7 first slice:** WEB also supports reboot and explicit HTTPS stop on the existing control task, with originating-session/principal validation after drain/delay. Stop is service-wide, not admin-socket-only; serial isolation applies to selector/SELF_CLOSE, not explicit HTTPS shutdown. Buffered input observed during deferral is wiped, including an incoming frame whose payload read races cancellation. Keep unsupported identity/credential/network paths blocked until separately implemented; no new executor or delivery guarantee.
|
||||
|
||||
**8D.7 second certificate slice:** Exact parsed browser `web certificate rotate --force` schedules a typed action, not command replay. Use the existing request-queue union and immutable owner `dispatcher_actions` mask to hand off after drain/200 ms to the existing 12 KiB dispatcher: crypto/NVS must not run on the 4 KiB control stack. Preserve queue capacity, pending-input gating through execution, token/principal/session revalidation and executing-slot reservation across self-detach. Zero mask preserves legacy SSH execution. WEB revalidates before transactional certificate commit → stop → start; early errors short-circuit and failed stop retains HTTPD ownership without start. Lifecycle failure after commit does not restore the old identity. Browser trust/relogin and UART0/SSH recovery are explicit operational consequences; USB/SSH are not stopped. Bounded acknowledgement/drain is neither an execution deadline nor receipt proof. No stack-size/task/route expansion; owner mask and local scratch still need target accounting/high-water evidence, not host sizeof assumptions. Other credential/account/network/SSH mutations remain blocked pending bounded owner slices.
|
||||
|
||||
**Relevant files:** `src/admin_ssh_console.c`, `src/system_console.c`, `src/ssh_console.c`, `src/ssh_transport.c`, `src/web_admin_transport.c`, `src/web_console.c`
|
||||
**Consequence:** Explicitly communicate partial effects, trusted UART0 fingerprint verification and fresh HTTPS login after restart. Preserve CLI recovery/reset but do not add browser Reset/export as if it were ordinary rotation. Native USB is independent UART1, not administration or uninterrupted reboot. [Lifecycle/identity contract](../web_administration.md#service-lifecycle-and-identity-rotation).
|
||||
|
||||
## Authentication uses copied principals and fail-safe currentness checks
|
||||
|
||||
**8D.5 web owner extension:** Browser and runtime SSH admission allocate from the same two console slots; a physical SSH slot is not a console index. WEB supports owner-relative self-close only and rejects unsupported network/lifecycle/account mutations at parsed command policy before execution. One web-admin socket and two tickets do not increase six-socket HTTPD capacity; disable LRU rather than evict retained serial clients. The optional owner uses one PSRAM-only payload and ESP timer scheduling, not a new task. HTTPD alone sends/shuts down its verified current fd. Do not use IDF's queued raw-`sock_db *` close from admin polling: free/reuse before that work executes could close a replacement. Detach must fence queue submissions before HTTPD stop; retire queued markers only after successful stop, retaining ownership across failures. No browser UI or generic HTTP command runner is part of this boundary.
|
||||
**Decision:** Network sessions retain secret-free copied principals. Database/account ID/auth generation and originating web-session identity must be current at admission, before sensitive input and during reconciliation. Best-effort target notifications supplement, never replace, these checks and cannot roll back committed mutations.
|
||||
|
||||
**Decision:** Network sessions retain secret-free copied principals. Account mutations invalidate generations/IDs; after commit, the command layer requests best-effort targeted transport revocation, while ongoing currentness checks are authoritative.
|
||||
|
||||
**Rationale/evidence:** `user_database` issues principals without secrets; web/SSH check currentness during admission and active sessions. Mutating console paths call transport revocation hooks.
|
||||
|
||||
**Consequence for future changes:** Do not retain pointers to database records or treat login as permanently authoritative. New authenticated sessions/transports must revalidate at admission, before sensitive input, and periodically or on relevant events. Database mutation APIs alone do not perform transport notification, and notification failure must not roll back an already committed mutation.
|
||||
|
||||
**Relevant files:** `src/user_database.{h,c}`, `src/user_console.c`, `src/web_server.c`, `src/web_serial_transport.c`, `src/ssh_transport.c`
|
||||
**Consequence:** Shared remote-console slots require transport-qualified tokens and immutable owner adapters. Validate owner currentness outside console locks, then recheck identity. Owner-side HTTPD/SSH IO and generation-safe cleanup remain mandatory; session liveness checks do not cancel executing handlers. Browser-shell permissions are parsed and narrower than typed Settings. [Authentication](../web_administration.md#authentication-and-admission), [console policy](../web_administration.md#browser-shell-policy).
|
||||
|
||||
## Typed serial mutations share the administration dispatcher
|
||||
|
||||
**8D.9:** HTTPD performs bounded typed admission/result reads only; serial reconfiguration and NVS execute on the existing dispatcher so CLI commands cannot interleave. One global pending slot rejects concurrent work; copied session/principal plus non-reused ID fence stale queued work. A 30-second deadline is checked on dequeue, not a cancellation timer or execution limit. Admitted mutations may finish after revocation; completed results can be replaced. Keep explicit uncertain-outcome recovery and never automatically retry mutations. Apply/Defaults are RAM-only, Save persists working device state rather than browser drafts, and Reset follows canonical apply/persist/best-effort-rollback ordering. Navigation preserves broker clients/writer lease, while explicit serial reconfiguration can discard serial-service pending data. No generic command runner/job history is exposed. See `src/web_serial_settings.{c,h}` and `docs/phase8d9_implementation.md`.
|
||||
**Decision:** Typed domains queue IDs to the existing serialized dispatcher, never CLI strings or secrets. One original-login slot per domain and a nonreused ID fence stale work; session/deadline checks precede canonical admission. Results are replaceable observations, not durable history/idempotency.
|
||||
|
||||
**Consequence:** No automatic mutation replay, including after navigation, timeout or logout. Ordinary deadlines limit dequeue admission, not execution. Accounts/Network queued-secret timers wipe only non-executing inputs; locals wipe after admitted work returns. Explicit RAM/NVS/reset semantics and partial-effect uncertainty must match each canonical owner. [API bounds and lifetime](../web_administration.md#typed-settings-api-and-operation-lifetime).
|
||||
|
||||
## Typed account selection is checked inside the database mutation lock
|
||||
|
||||
**8D.11:** Apply the same conditional target identity contract to authorized-key add/delete/clear, sharing canonical CLI validation/commit paths. Expose fingerprint metadata only through a zero-wait snapshot; never return stored key blobs. Treat key indices as stable, potentially sparse slots, not response-array positions. Listing uses protected JSON POST to reuse bounded target admission, not a new query parser. Public-key import is bounded to 384 decoded text bytes within the existing 768-byte body, with canonical blob/curve validation on the existing dispatcher. Target revocation/self uncertainty and browser-shell restrictions are unchanged. See `docs/phase8d11_implementation.md`.
|
||||
**Decision:** Target username/account ID/auth generation compare occurs inside the canonical mutation lock for role/delete/password/key changes. HTTPD uses compact zero-wait secret-free projections, not the blocking CLI snapshot. Successful changes notify only the target's web/SSH sessions, including self.
|
||||
|
||||
**Current 8D.10 slice 2:** Extend conditional identity checks to password replacement; create uses canonical duplicate/capacity/commit policy. Keep generation separate from commit: the protected bodyless generated-value POST returns one transient value, performs no mutation and retains no retrieval state. Browser saved acknowledgement is context-bound UX, not delivery proof or server authorization. Queued credentials require a one-second periodic timer to cancel/wipe non-executing work at the 30-second deadline plus scheduling latency; execution copies then wipes shared inputs, with local wiping after admitted database work returns. Neither timer nor logout cancels admitted commits. Self password/role/delete uses immediate canonical target revocation, not deferred acknowledgement: 401/disconnect is uncertain and requires relogin/inspection before any explicit retry. Generation is independently optionally registered, preserving failure isolation and restart behavior at 23 handlers. No shell restriction change, secret result/history, new executor or 8D.11 work. Implementation is complete, host-tested/build-verified, not target accepted; timer runtime costs remain unmeasured. See contracts, build and attributed test evidence in `docs/phase8d10_implementation.md`.
|
||||
|
||||
The following first-slice exclusions are historical and superseded by slice 2:
|
||||
|
||||
**8D.10 first slice:** Accounts HTTPD routes expose a compact zero-wait list without password/key data and submit role/delete IDs to the existing dispatcher. Do not use the larger blocking CLI snapshot on HTTPD. Initiating-session currentness is checked before operation admission; target username/account ID/auth generation is compared under the database lock before candidate staging. Conditional and CLI mutations share invariant/commit logic. Notify only the target's web/SSH sessions after successful calls; notification failure does not undo persistence. Separate bounded Serial/Accounts slots do not create another executor. Completed results remain replaceable, no mutation auto-retry, and navigation is not cancellation. Self-target and create/password/generated-secret delivery are intentionally excluded until the next slice defines safe delivery/reconnect semantics. `src/web_account_settings.{c,h}`, `src/user_database.{c,h}`, `docs/phase8d10_implementation.md`.
|
||||
**Consequence:** Separate generated-value delivery from mutation and retain no retrieval history; context-bound saved acknowledgement is UX, not receipt proof. Self-revocation can deny results, so 401/disconnect cannot mean success or cancellation. Key slots are stable and sparse, fingerprint-only on output; import shares canonical validation. Final-admin invariants and UART0 provisioning/recovery remain. [Accounts contract](../web_administration.md#accounts-and-authorized-keys).
|
||||
|
||||
## Browser authentication has a narrow version-pinned HTTPD boundary
|
||||
|
||||
**8D.8 read-only settings:** Reuse bodyless GET cookie/current-admin policy and the existing bounded browser API/errors; no CSRF mutation semantics on a read. Obtain working serial config/running atomically with a zero-wait existing serial mutex, never block HTTPD on stop/reconfiguration or inspect NVS. Navigation changes view/input only, preserving both terminal sockets/lease/output; Settings session validation must not supersede serial-admission checks. One optional exact-GET URI raises only handler capacity to 17. The private adapter stages both descriptor/name allocations before publishing, avoiding the installed 5.5.0 public registration's freed table pointer on strdup failure. Serialized startup/exact matcher only, normal HTTPD allocation/free ownership; re-audit this boundary on SDK changes. Existing public registration callers are not refactored by this phase.
|
||||
**Decision:** `web_httpd_adapter` alone accesses private IDF 5.5.0 parsed-header/session state. Reject duplicate/ambiguous headers; defer 101 until cookie/Origin/ticket/transport admission; wipe consumed scratch while preserving unread bytes. Stage optional Settings descriptor/name allocations before publication. Compile header/ticket debug logging out.
|
||||
|
||||
**8D.6 terminal separation:** Browser selection never reconnects serial or requests/releases a writer lease. Hidden connected terminals continue draining with separate bounded scrollback/pending writes and visible browser-drop accounting; only selected keyboard input is sent. Admin admission/reopen is explicit, close is isolated, and logout/expiry/pagehide tears down both routes. Keep the two page-lifetime input subscriptions stable across switches and remove socket callbacks on close. UI role hiding complements, never replaces, backend authorization. Existing unsupported WEB lifecycle/account-command restrictions remain for 8D.7.
|
||||
|
||||
**Decision:** 8D.3 uses `web_cookie_auth` plus digest-only session/challenge stores, mandatory Origin/CSRF mutations and no live session/challenge/ticket eviction. Four one-hour absolute sessions deliberately interrupt long serial connections at expiry. No Basic compatibility path remains.
|
||||
|
||||
**Browser Origin serialization:** Authentication POST fetches use `mode: 'cors'` while retaining fixed same-origin URLs, `credentials: 'same-origin'`, redirect rejection and CSP `connect-src 'self'`. Under `no-referrer`, non-CORS POST mode can serialize Origin as `null` (confirmed in Firefox during M1 testing). Do not fix that by accepting null server-side or weakening CSP/referrer policy; no cross-origin server permission is added.
|
||||
|
||||
**HTTPD boundary:** `web_httpd_adapter` alone includes private ESP-IDF 5.5.0 structures. Public request getters expose only the first field, so the adapter validates bounded parsed headers/rejects duplicates. `/ws/serial` is an ordinary GET until authenticated transport admission explicitly sends 101 and installs the frame handler; automatic HTTPD upgrades happen before URI handlers. Preserve right-aligned unread pending bytes when wiping request memory. Two Set-Cookie calls append pointer-backed fields, whose distinct buffers must survive through send. HTTPD logs above ERROR are compiled out to prevent header/ticket exposure.
|
||||
|
||||
**Ordinary HTTPS idle lifecycle:** Keep six sockets and LRU disabled. `web_httpd_idle` queues at most one owner sweep each second; `web_httpd_adapter` alone reads the installed successful request-plus-purge completion marker. Fifteen seconds of observed ordinary idle (three normal five-second browser polls), current SDK WS/async exemption, pending/readable-input checks and synchronous TLS-create fd invalidation authorize current-owner `shutdown`, never queued `sock_db *` close. Do not use response events, diagnostic wrappers or connection age as the completion/idle boundary. All ordinary response work must finish synchronously or retain the SDK async exemption. Submission fencing precedes HTTPD stop; only successful stop retires queued state and admits a nonreused generation. No tracing dependency, forced per-response close, hard request deadline, arbitrary admission eviction or capacity increase. A reported queue failure retries; accepted-but-lost UDP work stays reserved until successful restart rather than accumulating potentially delayed probes. Exact safety/liveness limits and target checklist: `docs/https_idle_cleanup.md`.
|
||||
|
||||
**Consequence:** The version guard is not a source-hash guarantee. Re-audit layout, scratch/pending ownership, logging and handshake/frame dispatch on SDK changes; do not scatter private accesses through application code or assume host doubles prove real socket behavior. No SDK patch is currently applied. See `docs/phase8d3_implementation.md` for verification and target gates.
|
||||
|
||||
**Relevant files:** `src/web_cookie_auth.{c,h}`, `src/web_session_store.{c,h}`, `src/web_httpd_adapter.{c,h}`, `src/web_server.c`, `src/web_serial_transport.c`, `src/CMakeLists.txt`.
|
||||
**Consequence:** Re-audit SDK assumptions on upgrade; never patch around Origin `null` by weakening same-origin policy. Browser authentication POST uses CORS mode with fixed same-origin URLs/credentials because no-referrer non-CORS POST can serialize Origin as null. Digest-only cookie/challenge sessions replace Basic without fallback or live-record eviction. CSP loader hashes and authored scripts change atomically. Navigation preserves terminals/lease, while session-identity changes require a clean document before showing retained buffers. [Authentication and terminal contracts](../web_administration.md#authentication-and-admission).
|
||||
|
||||
## Security material and configuration use bounded, versioned NVS records
|
||||
|
||||
**8D.21 HTTPS identity owner decision:** Reserve the service transition BEFORE conditional identity admission or generation, not rotate-then-conditional-restart. `web_server_replace_identity()` shares this composition with CLI reset/rotate and deferred browser-shell rotation; `web_security` reserves a nonreused token across generate/commit and service stop/start, excluding direct canonical security mutation too. Short normal-mutex admission/publication protects state, but crypto/NVS and HTTPD waits run without held locks. Failed generation/storage never publishes or stops HTTPD; committed material is not rolled back if later stop/start fails. Identity generation increments only on successful commit; service generation advances on admitted replacement and canonical stop/start, saturating without reuse. Reservation-token exhaustion rejects mutations until reboot; persisted identity exhaustion retains the existing fail-closed behavior. Zero-wait public metadata is the stored identity, not proof of the retained HTTPD certificate after failure. Extend only the existing ACK-safe typed lifecycle slot/controller with `rotate` and required identity generation; keep UART0 unavailable-material recovery and duplicate healthy Reset out of browser UI. Explicit trust/fingerprint verification through trusted UART0 and fresh login, uncertainty and no replay are correctness requirements. SSH identity work remains separately requested. See `docs/phase8d21_implementation.md` for exact API, bounds and host-versus-target evidence.
|
||||
|
||||
**Decision:** Application settings, users, and identities use separate fixed/versioned NVS blobs. Serial, Wi-Fi, mDNS-hostname, and local-UI working edits are RAM-only until explicitly saved. User mutations and HTTPS/SSH identity changes commit directly as part of the operation. Invalid ordinary configuration generally selects RAM defaults without erasing storage; malformed security material fails closed and needs explicit reset.
|
||||
|
||||
**Rationale/evidence:** Serial, Wi-Fi, local UI, web security, users, and SSH security each validate schema/size and own their namespace. User/security mutations build and validate candidate state before committing it; security modules avoid silently replacing an established identity. The live user database remains internal while its 5,360-byte candidate is a persistent PSRAM-preferred allocation with internal fallback and is wiped after every transaction.
|
||||
|
||||
**Consequence for future changes:** Add schema versions and transactional candidate validation. Do not overwrite unknown records automatically; provide explicit migration/reset behavior. Preserve the distinct persistence contracts: explicit save/load/default/reset for working configuration and per-blob commit-before-live-install for user and identity mutation. Keep candidate ownership mutex-local and wipe/free it on initialization or recovery failure. Recheck external-buffer staging in the flash/NVS implementation when upgrading from the pinned ESP-IDF 5.5 baseline. Legacy credential synchronization and reconciliation are removed. Missing user storage commits empty; valid user v1 bytes remain compatible, with private `v1_admin_marker` derived from admin count, not a public bootstrap contract. HTTPS v1 (1,392 bytes) migrates through a private validated reader to TLS-only v2 (1,340 bytes), preserving exact DER/fingerprint/generation and committing before publication. Failures fail closed without fallback regeneration or overwriting rejected records. See `docs/legacy_credential_removal.md`.
|
||||
**Consequence for future changes:** Add schema versions and transactional candidate validation. Do not overwrite unknown records automatically; provide explicit migration/reset behavior. Preserve the distinct persistence contracts: explicit save/load/default/reset for working configuration and per-blob commit-before-live-install for user and identity mutation. Keep candidate ownership mutex-local and wipe/free it on initialization or recovery failure. Recheck external-buffer staging in the flash/NVS implementation when upgrading from the pinned ESP-IDF 5.5 baseline. Legacy credential synchronization and reconciliation are removed. Missing user storage commits empty; valid user v1 bytes remain compatible, with private `v1_admin_marker` derived from admin count, not a public bootstrap contract. HTTPS v1 (1,392 bytes) migrates through a private validated reader to TLS-only v2 (1,340 bytes), preserving exact DER/fingerprint/generation and committing before publication. Failures fail closed without fallback regeneration or overwriting rejected records. See [legacy compatibility](../legacy_credential_removal.md).
|
||||
|
||||
**Relevant files:** `src/serial_config.c`, `src/wifi_config.c`, `src/mdns_config.c`, `src/mdns_service.c`, `src/local_ui_config.c`, `src/web_security.c`, `src/user_database.c`, `src/ssh_security.c`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user