Add typed account and password settings

- Add admin account list, create, role, delete, and password workflows
- Execute identity-checked mutations through the existing dispatcher
- Bound queued credential lifetime and wipe transient secrets
- Add explicit password generation with saved-value acknowledgement
- Handle self-revocation and uncertain outcomes without automatic
  retries
- Register optional account routes without disrupting terminal
  transports
- Expand host regressions and document contracts and pending target
  checks

Validated host suites and pio run; hardware validation remains pending.
This commit is contained in:
2026-09-08 09:27:02 +02:00
parent 42548f6334
commit 94433ef975
30 changed files with 1864 additions and 48 deletions
+8
View File
@@ -152,6 +152,14 @@ The final administrator cannot be deleted or demoted. UART0 is trusted for initi
NVS is not encrypted. Password verifiers improve password storage, but Wi-Fi credentials, legacy recovery credentials, and TLS/SSH private keys remain recoverable under physical flash extraction.
## Typed Accounts settings (8D.10)
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-only bootstrap/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; bootstrap/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.
+4
View File
@@ -78,6 +78,10 @@ This is a semantic map, not a complete file inventory. Start here, then read the
- 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`.
+6
View File
@@ -4,6 +4,12 @@ This file is working memory. Update it during active work and before handoff; do
## Development state
- **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.
+8
View File
@@ -106,6 +106,14 @@ Phase 8D.4 routes drain/lifecycle operations through a firmware-lifetime immutab
**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`.
## Typed account selection is checked inside the database mutation lock
**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`.
## 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.
+109
View File
@@ -0,0 +1,109 @@
# Phase 8D.10 — Accounts and passwords
## Current second slice (2026-09-08)
**8D.10 implementation is complete, host-tested and build-verified, including create/password/generated-value/self workflows and route integration. Target validation and full 8D.10 signoff remain pending; this is not target acceptance. No 8D.11 work.** This supersedes slice 1 scope exclusions and next-work instructions, not its historical evidence. M2 acceptance stands; continuation is not prior-phase target signoff or reserve approval.
### Current contracts
- The admin-only list and single session-bound operation/result slot now support create/password/role/delete, including own-account password/role/delete. HTTPD authorizes/parses/queues; the existing dispatcher executes mutations. No new application task, stack allocation, queue depth, socket or broker client.
- Mutation JSON is **768 bytes / four receive calls**. Exact create schema: `{action, username, role, password}`; password replacement: `{action, username, user_id, auth_generation, password}`. Role/delete schemas are unchanged. Only password accepts JSON escapes; decoded values must be canonical **12-64 printable ASCII bytes**, without trimming. Spaces, quotes and backslashes are valid. Unknown/duplicate/extra fields, malformed encoding and invalid identities reject. Body, parsed credentials and consumed request scratch are wiped on exit.
- Create uses canonical `user_database_create()` duplicate/capacity/NVS policy. `user_database_set_password_current()` shares CLI mutation logic and checks target identity under the mutation mutex before staging, derivation, generation increment and commit-before-live-install. Role/delete retain conditional checks and final-admin/migrated-account protections. Secret-free **96-byte** `{id, action, state}` results add `duplicate` and `full`; results remain replaceable, originating-session-bound, not durable history or retry tokens.
- Credential admission requires one lazily created firmware-lifetime **one-second periodic ESP timer**; create/start failure rejects admission. At/after the **30-second admission deadline**, a tick cancels and wipes the current non-executing pending create/password slot, permitting replacement even while the dispatcher is blocked. Cleanup is nominally deadline plus up to one period **plus scheduling latency**, not a hard real-time guarantee. The callback inspects current ID/deadline, never cancels a replacement early or executing work, and does no database/network work. Role/delete retain dequeue-only expiry; obsolete queued IDs cannot execute replacement work.
- Dequeue marks execution, copies inputs locally and immediately wipes shared credentials/principal/target. Dispatcher revalidates originating session/admin and deadline before database API admission. The timer does not wipe executing local data; credentials are wiped after database return or rejection, and the full local operation at exit. Blocking/derivation/NVS duration is not bounded by the queue deadline. Already-admitted work may complete after logout/expiry; navigation/request abort is not backend cancellation.
- Separate bodyless admin/Origin/CSRF-protected **`POST /api/settings/accounts/generate-password`** calls `user_database_generate_password_value()` and rechecks session currentness before returning one **24-character base64url** value in a **96-byte**, no-store response. It performs no account mutation, slot reservation, NVS commit or revocation. Generated/response scratch is wiped on success and failure. There is **no retained secret or retrieval endpoint**. A lost generation response means nothing was applied; explicit regeneration yields another value. Generation precedes a separate commit, rather than commit-and-retrieve or guaranteed delivery.
- UI generation is explicitly not applied. Submission requires confirmation and, for generated values, saved-password acknowledgement bound to the value and operation/target context. Generated references expire after **60 seconds**, including monotonic admission checks for delayed timers. Edits/context changes invalidate acknowledgement; submission/cancellation, navigation/session/page lifecycle cleanup clears secrets and fences late replies. No routine secret storage/logging/history/clipboard writes. JavaScript/browser-managed copies cannot be securely zeroed; reference clearing is best effort.
- Successful non-create calls immediately best-effort target-revoke web/SSH; create does not notify. Successful role no-op still notifies. Self mutations may close all that account's web/SSH sessions, including this browser's serial/admin routes, before POST/result delivery. No deferred drain, proactive logout or guaranteed receipt. **401/disconnect proves neither success nor failure**: re-login with expected credentials/role and inspect through a surviving authorized route before retrying. Notification failure does not undo persistence; authoritative currentness remains the fail-safe. Ordinary navigation preserves socket/writer ownership; deliberate self revocation is distinct.
- Automatic result checking remains one-second delay, at most ten GETs/15 seconds overall, with manual uncertainty recovery and no automatic mutation retry. **No browser-shell restriction changes:** typed self/generated workflows do not enable those shell commands. SSH keys/raw database or secret export/legacy credential management are outside scope; bootstrap/recovery remain permanently UART0-only.
- The generated-password endpoint is registered independently as an optional route in `web_server.c`, bringing the budget to **23 handlers**, with six sockets/no LRU unchanged. Backend review found only the missing registration, now fixed. Route-agent lifecycle tests cover registration, optional failure isolation and restart; direct-handler tests alone are not registration proof.
### Continuation evidence
- Reported continuation `python3 tests/web_cookie_auth/run.py --accounts`: **PASS, nine Accounts groups plus shared auth/store/IDF adapter groups**. Includes credential parsing, timer creation/start failure, expiry/replacement/execution fences, cleanup, generation without mutation and self revocation. Database/queue/revocation/timer doubles are not target concurrency, flash or TLS proof.
- UI agent reports `python3 tests/web_ui_session/run.py`: **PASS, 57 behavior groups plus renderer/CSP checks**, four added beyond its earlier 53-group slice 2 run. Modeled DOM/fetch/timers/WebSockets do not establish real-browser/backend integration or actual CSP enforcement.
- Route agent reports `python3 tests/web_admin_transport/server_lifecycle.py`: **PASS, 15 groups**, including the corrected independent optional endpoint, failure isolation and restart. UI 57/CSP and lifecycle 15 are agent results; the parent's additional UI/lifecycle reruns have not been reported here and are not claimed as passes.
- Parent reports PASS: `python3 tests/admin_console_boundary/accounts.py`, `python3 tests/admin_console_boundary/run.py`, `python3 tests/web_auth_parse/run.py` (**294 cases**), `python3 tests/web_cookie_auth/run.py --accounts` (**9 plus shared**), `python3 tests/web_cookie_auth/run.py --serial-settings` (**10 groups**), `python3 tests/web_admin_transport/run.py --tickets` (**25 transport / 12 ticket groups**), `python3 tests/web_session_store/run.py --serial`, and diff check.
- Parent `pio run`: **PASS, 25.61 s; 95,908 B RAM / 1,694,237 B flash**. Delta versus slice 1: **+80 B RAM / +9,880 B flash**; versus final 8D.9 UX: **+200 B RAM / +25,400 B flash**. Slice 1 figures below remain historical. Static linker accounting is not runtime heap/stack proof: new timer and descriptor runtime costs, HTTPD/dispatcher stack margins and loaded heap reserves remain unmeasured/unapproved.
- This documentation task performs no source/test edits or build. Results are attributed parent/agent reports, not reruns by this documentation agent. No sanitizer validation, upload, erase, device action, asset regeneration, commit or 8D.11 work is claimed.
### Target checklist (pending)
1. Test desktop/mobile disposable-account create and supplied/generated password workflows: exact bytes, confirmation/context acknowledgement, expiry/cleanup and no application from Generate. Compare UART0 state and persistence after reboot.
2. Exercise other-account password/role/delete with active web/SSH sessions; verify target-only revocation and unrelated web/SSH/native USB continuity, plus navigation socket/writer preservation.
3. With a surviving administrator and UART0 recovery available, test self password/demotion/deletion, final-admin rejection and role no-op. Verify uncertain 401/disconnect handling, expected relogin and inspection before retry; missing result never proves no commit.
4. Mutate/delete/recreate selected targets through CLI and check stale rejection; exercise duplicate/full/protected failures without unintended writes or disclosure.
5. Queue credentials behind a long console prompt; observe deadline cleanup/replacement, logout/expiry, late responses, lost generation/POST acknowledgements and manual recovery without retry. Timer scheduling and executing-secret lifetime require target observation.
6. Verify fourth-route admission, injectable registration/start/stop failures, slow fragmented requests and full serial/admin/SSH/USB mix. Record loaded/cleanup internal/DMA/PSRAM free/min/largest and HTTPD/dispatcher stack margins. Host NVS doubles are not target failure evidence.
7. Obtain target acceptance separately from the completed host-tested/build-verified implementation. **Stop within 8D.10; no 8D.11 authorization or target signoff is inferred.**
## Historical first slice (2026-09-08)
All sections below retain slice 1 evidence, including then-current exclusions, 22-handler count, build and next-slice checklist. They do not describe current slice 2 scope or validation.
**Account list and other-account role/delete implemented, host-tested and build-verified; target validation pending. 8D.10 remains incomplete.** The user requested 8D.10 after the 8D.9 UI refinement. Before editing, selected the plan's explicit split between list/role/delete and create/password/secret delivery. No 8D.8/8D.9 target signoff is inferred from continuation. M2 acceptance stands.
### Available behavior
Settings now has Serial settings and Accounts subviews. Accounts shows at most eight usernames/roles, identifies the current login, and permits confirmed role changes or deletion of another account. Changes persist immediately; there is no Apply/Save staging. Successful operations use the same best-effort targeted web/SSH revocation as CLI commands. Unrelated accounts, UART0, native USB and navigation-related serial writer ownership remain unchanged. A role no-op retains canonical CLI behavior: no database commit, but successful-command target notification still occurs.
The UI submits once, checks acknowledged work automatically (one-second initial/inter-check delay, at most ten GET attempts and a 15-second overall deadline including revalidation), then refreshes the list on known terminal results. It retains failed/uncertain outcome messages. Errors, deadline exhaustion or lost acknowledgement require manual Check Result/Refresh; no automatic mutation retry. Navigating between domains, leaving Settings, logout/expiry/pagehide or changed session identity aborts/fences work and clears the account list/selection labels. Navigation is not backend cancellation. Re-selecting the current domain/view is a no-op.
**Not included:** create, supplied-password replacement, generated-password delivery, own-account changes, SSH keys, bootstrap/recovery, raw database export or any password/verifier/private-key fields. Self role/delete is denied by the server as well as the UI. Existing browser-shell restrictions are unchanged. The second 8D.10 slice must explicitly design create/password and own-account credential/reconnect behavior; this first slice is not full Accounts/password UX or phase acceptance.
## Backend contracts
- `src/web_account_settings.{c,h}` owns one static pending/result slot. It holds only an operation ID, copied originating session/principal, target identity, action/role and deadline/state—no credential material.
- `GET /api/settings/accounts`: current admin cookie policy, no query/body, no-store. Compact projection of username, role, account ID and authentication generation; no SSH key metadata or full database snapshot. `user_database_get_accounts()` copies under the existing mutex with **zero wait**, clearing output on failure. This narrow copy does not change the existing authentication/currentness APIs or claim all authentication paths are nonblocking.
- `POST /api/settings/account-operation`: admin cookie/current principal, strict Origin/CSRF policy; maximum **256 bytes / four receive calls**. Exact ASCII flat schema: `{action, username, user_id, auth_generation}` for delete; plus `role` for role changes. IDs are nonzero unsigned 32-bit decimal integers. Unknown/duplicate fields, escaping/nesting, invalid usernames/enums, fractional/exponential/overflow/coerced numbers and extra fields reject. Unread rejected bodies close; request scratch is wiped.
- `GET /api/settings/account-operation`: bodyless current-admin read of the originating login's retained `{id, action, state}` only. Results have a **96-byte buffer** and states idle/pending/ok/failed/cancelled/stale/protected. Another admitted operation may replace a completed result; this is not durable history or an idempotent retry API.
- `admin_ssh_console_submit_account_settings()` enqueues only the ID, with **zero queue wait**, to the existing four-entry dispatcher queue. The typed union adds no queue-item size and consumes no remote console slot. One pending account operation rejects another with 503/Retry-After. The Serial and Accounts pending slots are separate but executions serialize with each other and UART0/SSH/browser-shell commands on the same dispatcher.
- On dequeue, validate originating session/principal/admin role, reject self-target, and enforce a **30-second admission deadline**. A blocked dispatcher retains the pending slot until it dequeues the request; this is not a slot-release timer or execution timeout. Admitted database work may commit and notify after the initiating login expires. Subsequent stale work rejects. HTTPD never executes account commits or transport notification.
- `user_database_delete_current()` / `user_database_set_role_current()` compare target username/account ID/authentication generation **inside the mutation mutex** before staging or commit. Missing or changed targets return stale without mutating a replacement account. Existing CLI APIs call the same implementation without conditional identity arguments. Final-admin/migrated-admin protections, role generation changes, NVS commit-before-live-install and candidate cleanup remain canonical.
- Only successful database calls trigger target-name web and SSH revocation; notification failures do not roll back a committed account. Authoritative transport currentness remains the fail-safe. Result state reports database completion, not guaranteed notification delivery.
- `web_server.c` optionally registers list GET, result GET, then mutation POST, using the existing failure-safe private adapter. Failure cannot publish mutation without both read routes; failed POST cleanup can leave at most read-only routes. Allocation failure does not disable either terminal transport. No new SDK-private access.
## Validation performed
Final sequential parent run, all passed:
```sh
python3 tests/web_cookie_auth/run.py --accounts
python3 tests/admin_console_boundary/accounts.py
python3 tests/admin_console_boundary/run.py
python3 tests/web_admin_transport/server_lifecycle.py
python3 tests/web_ui_session/run.py
python3 tests/web_admin_transport/run.py --tickets
python3 tests/web_session_store/run.py --serial
pio run
git --no-pager diff --check
```
- Accounts HTTP: **five groups** plus shared cookie/store/IDF getter/adapter regressions. Actual parser/auth/store/handler with deterministic DB/queue/revocation doubles. Covers security/input bounds, max-width eight-account projection, failed list, slot/queue exhaustion, session isolation, obsolete/zero IDs, failure/stale/protected outcomes, deadline/session cancellation, missed account revocation, DB-currentness failure and admitted-work completion after invalidation. A failed database currentness check invalidates that cookie session, so subsequent test operations use a newly issued login.
- Canonical account tests compile production database mutation bodies and compact list getter, plus existing CLI handlers. Verify target generation/deletion/recreation rejection, no-write final-admin protection, unchanged live state and cleared candidate on NVS open/set/commit failure, and existing prompt/currentness/revocation behavior. RTOS/NVS/crypto are deterministic doubles, not actual flash or concurrency tests.
- Dispatcher boundary covers nonblocking Accounts admission and separate Serial/Accounts routing on the existing queue, alongside prior console/certificate/SSH-adapter cases.
- Server lifecycle: **14 groups**, including all three Accounts registration failure positions, failed unregister, restart recovery and unchanged transport isolation/six sockets/no LRU.
- UI: **41 behavior groups** (six new Accounts groups), plus production C renderer/headers/failure and exact inline-loader CSP checks. Covers admin-only list/schema, confirmed typed identity-bound actions, automatic completion/refresh, polling budget/manual recovery, cancellation, failure/uncertainty and session identity/401 isolation. DOM/fetch/timers/WebSockets are modeled; this is not real-browser/backend integration.
- Transport/tickets and session-store/serial integration reruns passed. Earlier in this slice, cookie `--settings`, `--serial-settings`, `--admin`, console `lifecycle.py` and `admin_ssh_policy/run.py` also passed.
Initial test failures were corrected: authored C/JS newline escaping, outdated lifecycle test route/count expectations, and new currentness tests using the existing fake's actual invalidation control. Final results above supersede those intermediate failures. Implementer source/diff review performed; no independent-agent review or sanitizer claim.
## Resources
Final `pio run`: **25.00 s; 95,828 B RAM / 1,684,357 B flash**.
- Versus final 8D.9 UX build (95,708 / 1,668,837): **+120 B RAM / +15,520 B flash**.
- Versus 8D.8 (95,580 / 1,654,529): **+248 / +29,828 B**.
- Static linker totals are not free/min/largest heap, allocation overhead, or stack high-water proof.
- Three additional optional URI descriptors bring the configured handler budget from 19 to **22**. Six HTTPD sockets/no LRU, two serial/one admin WebSockets, dispatcher depth, tasks and stack allocations remain unchanged.
- List response buffer **1,024 bytes**, operation request **256 bytes**, result **96 bytes**, at most eight compact records. Handler/dispatcher stack margins and additional descriptor/name/table runtime heap have not been measured on target. Four bounded receive calls still occupy HTTPD while receiving; full-mix responsiveness needs target validation.
No asset regeneration, upload, erase, device action or commit. Numeric reserves/stack margins remain unapproved; the accepted intermittent full-mix admission issue is unchanged and unresolved.
## Pending validation and handoff
1. Confirm Settings/Accounts desktop/mobile display and role gating on hardware; compare list/roles with UART0. Verify navigation preserves existing serial/admin sockets and writer lease.
2. Change/delete disposable other accounts while they hold web/SSH sessions; verify immediate persistence, only affected-account revocation, and unrelated USB/web/SSH continuity. Reboot and compare persisted state. Do not delete recovery/needed accounts casually.
3. Select an account, mutate or delete/recreate it through CLI, then confirm the stale browser request rejects. Verify self actions are unavailable, and canonical final-admin protection still works.
4. Queue behind a long console prompt; test logout, expiry, queue deadline, busy admission, missed/lost acknowledgement and manual recovery. Check that navigation does not imply cancellation and no mutation is automatically repeated.
5. Test optional route failure/slow fragmented HTTP requests and record loaded/cleanup internal/DMA/PSRAM free/min/largest plus HTTPD/dispatcher stack margins. Host failures are not target NVS-failure evidence.
6. Next is the **second 8D.10 slice**, not 8D.11: create/password workflows, bounded transient secret handling, one-time generated-password delivery and safe own-account changes. Establish the secret-delivery/acknowledgement/revocation contract before editing. Bootstrap/recovery remain permanently UART0-only. First-slice target validation and full 8D.10 signoff remain pending.
+6
View File
@@ -1,5 +1,11 @@
# Phase 8D — Incremental web administration plan
**Current slice 2 completion (2026-09-08):** **8D.10 implementation is complete, host-tested/build-verified, not target accepted; target validation/full signoff remain pending.** Create/password/self workflows use bounded 768-byte admission and periodic credential cleanup (30-second deadline plus one-second timer/scheduling latency); admitted executing work is not cancelled. Protected generation is separate before commit, with no retained retrieval. Self revocation may prevent results: 401/disconnect is uncertain, never grounds for automatic retry. Browser-shell restrictions remain 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**. Parent PASS canonical accounts/boundary, parser **294**, cookie accounts **9 plus shared**, serial-settings **10**, transport **25**/tickets **12**, store/serial and diff check. UI agent **57 plus CSP** and route agent lifecycle **15** pass; these are not claims of the parent's additional UI/lifecycle 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. Timer runtime costs and heap/stack margins remain unmeasured. [Current 8D.10 record](phase8d10_implementation.md) contains contracts/evidence/target checklist. No sanitizer validation, assets/device/commit/8D.11 work, M2 reopening, prior-phase signoff or reserve approval inferred.
The implementation/continuation entries below are historical evidence. In particular, slice 1's exclusions, next-slice instruction, 22-handler count and build figures do not describe current slice 2.
**Latest implementation (2026-09-08):** User-requested **8D.10 first slice is host-tested/build-verified; target pending and phase incomplete**. Pre-edit split follows the row below: Accounts list and other-account role/delete now implemented, with conditional target identity checks, existing dispatcher/target notifications and bounded automatic UI completion. Three optional routes, **22 handlers/six sockets**, **95,828 B RAM / 1,684,357 B flash**. [8D.10 record](phase8d10_implementation.md) covers tests/resources/limits. Next is the second 8D.10 slice (create/password/generated-secret/self workflows), not 8D.11. Supersedes historical stop-before-8D.10 instructions; no prior target signoff, M2 reopening or reserve approval inferred. Final 8D.9 UX baseline is recorded in [8D.9](phase8d9_implementation.md).
**Latest continuation (2026-09-07):** Separately authorized **8D.9 is implemented / reviewed / host-tested / build-verified**, with 8D.8/8D.9 target acceptance still pending. [8D.9 record](phase8d9_implementation.md): typed Serial framing/lifecycle/persistence, one session-bound operation slot on the existing dispatcher, manual bounded result recovery, 19 handlers/six sockets; final **95,708 B RAM / 1,666,725 B flash**. No new task/queue depth/stack or broker writer semantics. This supersedes older stop-before-8D.9 instructions, not M2 signoff or deferred restrictions, admission followups or unapproved reserves. Stop before separately requested 8D.10; no target signoff inferred.
**Previous implementation (2026-09-07):** Separately user-authorized **8D.8 is implemented / host-tested / build-verified**, with target/browser validation and new phase signoff pending. [8D.8 record](phase8d8_implementation.md): read-only admin Settings/Serial, nonblocking typed snapshot, 17 handlers/six sockets, final 95,580 B RAM / 1,654,529 B flash; exact tests/resources/limits and target checklist recorded. This supersedes older next-8D.8/wait-for-request instructions below, not M2 signoff or evidence. Deferred restrictions, accepted unresolved admission issue and unapproved memory/stack followups remain. Stop before separately requested 8D.9; no new signoff is inferred.