- 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.
20 KiB
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 addduplicateandfull; 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-passwordcallsuser_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)
- 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.
- 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.
- 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.
- Mutate/delete/recreate selected targets through CLI and check stale rejection; exercise duplicate/full/protected failures without unintended writes or disclosure.
- 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.
- 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.
- 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; plusrolefor 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.coptionally 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:
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, consolelifecycle.pyandadmin_ssh_policy/run.pyalso 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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.