# 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. ## Development state - **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 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 `` 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-.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-.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:**