Files
ESP32_Serial_Swiss_Army_Knife/docs/phase8d10_implementation.md
T
Commander1024 22a7c7b0a5 Record 8D.8–8D.10 target sign-off
Document user-supplied telemetry, functional testing, and acceptance of
the implemented Serial and account settings scope.
2026-09-08 15:19:49 +02:00

153 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 8D.10 — Accounts and passwords
## Target acceptance — implemented 8D.88D.10 scope (2026-09-08)
**User sign-off:** after supplying settled clean-boot and full-client-mix telemetry, the user reports: “I also tested the serial parameter display and settings, as well user/account settings thoroughly. Implemented work can be signed off.” Record this as acceptance of implemented **8D.8, 8D.9 (including its automatic-completion UX), and 8D.10 (both Accounts slices)**. It supersedes target-pending/full-signoff-pending statements below and in the earlier phase records, not their evidence or implementation constraints. The current checkout/handoff includes the completed credential/self workflows and generated-password route; the exact flashed revision/build hash was not separately supplied. Do not turn the broad functional sign-off into invented individual checklist passes. M2 stands; no 8D.11 implementation is authorized by this sign-off.
### User-supplied memory samples
All values are **bytes**. Each heap cell is **free / minimum-free / largest-block**. Minimum-free is the firmware's conservative sum of matching regions' lifetime minima, not necessarily one simultaneous sample.
| Sample | Internal 8-bit | Internal DMA | External PSRAM |
|---|---:|---:|---:|
| Settled clean boot | 69,928 / 65,476 / 31,744 | 62,172 / 57,720 / 31,744 | 8,246,368 / 8,238,352 / 8,126,464 |
| Loaded sequence 1 | 46,016 / 19,832 / 31,744 | 38,260 / 12,076 / 31,744 | 8,235,292 / 8,225,380 / 8,126,464 |
| Loaded sequence 2 | 32,580 / 19,608 / 23,552 | 24,824 / 11,852 / 23,552 | 8,082,268 / 8,077,960 / 7,995,392 |
| Loaded sequence 3 | 32,444 / 19,452 / 23,552 | 24,688 / 11,696 / 23,552 | 8,084,008 / 8,069,816 / 7,995,392 |
| Loaded sequence 4 | 32,444 / 19,228 / 23,552 | 24,688 / 11,472 / 23,552 | 8,082,276 / 8,065,444 / 7,995,392 |
| Loaded sequence 5 | 32,556 / 19,228 / 23,552 | 24,800 / 11,472 / 23,552 | 8,086,084 / 8,065,444 / 7,995,392 |
| Loaded sequence 6 | 32,556 / 19,228 / 23,552 | 24,800 / 11,472 / 23,552 | 8,087,656 / 8,065,444 / 7,995,392 |
The first loaded sample precedes the settled later footprint; exact client occupancy at each memory command is not separately known. Later free/largest values are broadly stable across the supplied sequence, while lifetime internal/DMA minima fall through sample 4 and then remain unchanged. This short sequence is not a timed leak/fragmentation/soak proof or numeric reserve approval. SSH owner stack minimum-free is **18,476 B at boot / 16,284 B loaded**, of a 20,480-byte allocation. HTTPD/dispatcher stack margins were not supplied.
### Observed configuration and client mix
- Settled boot: HTTPS and SSH initialized/running, one successful start each, no start failures; web admin initialized/attached but inactive. No SSH/cookie/WebSocket/broker clients. USB attached but host-open/DTR/RTS false; UART service stopped and RS-232 owner idle. Five users/two admins. mDNS initialized and announced as `sak-1024.local`, ESP_OK.
- Serial configuration is **230400 baud, 8 data bits, no parity, 1 stop bit, RTS/CTS, DTR active, RTS threshold 96** at both boot and load. Loaded service owns the physical port; RX-available and TX-pending are zero at the snapshot. DSR/CTS and valid voltage asserted; DCD/RI not asserted.
- Loaded snapshot confirms **two SSH sessions**: role-user public-key broker writer **ID 8**, and public-key administrator on the separate console route. Four broker clients: SSH writer 8, USB observer 9, web observers 26 and 11. No queued client output or events in the supplied client table. This supports one writer/isolated observers; no broker drop-counter output was supplied.
- HTTPS has **two cookie sessions, two serial WebSockets, and one active admin WebSocket**. Web serial sessions belong to administrator and ordinary-user accounts, both observing. USB host-open/DTR/RTS true, broker observer. Reported USB line coding is diagnostic only; UART1 configuration remains explicit.
### Counters retained without unsupported diagnosis
- SSH: 3 TCP connects, 2 successful handshakes, **1 handshake failure**, zero handshake timeouts/auth failures/capacity rejection/IO failures. One successful broker admission/writer grant, one admin-console admission without failures/backpressure. Stream RX 73 accepted/0 rejected, TX 9,121. **One broker revocation**, zero session-revocations; cause unspecified.
- Web admin: 5 tickets issued/consumed, 5 connects/4 disconnects, one currently active; no capacity/auth/protocol/backpressure/send/queue failures. RX 0 / TX 408 bytes. These counters do not separately prove shell-command testing.
- Web serial: 4 connects/2 disconnects, no service-start/broker/connect failures. TX 75 binary frames / 12,478 bytes. Four writer requests denied while SSH holds the lease, consistent with single-writer policy. **One send failure and one close** are recorded alongside connection cycling; no cause or regression attribution is established. Queue/protocol failure counters are zero.
- General HTTPS: 86 requests/authenticated, zero reported auth/response failures in those counters; 6 serial tickets issued, 4 consumed, 2 expired. Cookie counters: 3 login attempts, **1 invalid credential**, zero throttle/capacity/CSRF-Origin rejections/logouts. No credential value is retained here.
- The final session snapshot has no closing or RX-pending serial clients. Admin-SSH command/output activity is consistent with running the status commands, not evidence of a wedged dispatcher.
### Acceptance and evidence limits
The user's thorough Serial/account functional testing and explicit sign-off close the implemented phase scope. Exact firmware hash, browser(s), elapsed settle/load/soak durations, mutation-by-mutation results, reboot-persistence/fault-injection/secret-expiry details and post-disconnection cleanup telemetry were not supplied separately. These limits do **not** reopen accepted phases or become invented test passes. Historical checklist items below remain useful regression guidance, not remaining acceptance blockers after user sign-off. Numeric reserves/runtime timer cost/HTTPD-dispatcher stack margins remain followups. The earlier accepted intermittent full-mix admission issue is neither diagnosed nor declared fixed by successful admission in this run.
The CLI's “no normal UI entry” and “shell/PTY only” labels are inherited status wording, not evidence that current UI/SSH behavior differs; source confirms those strings still exist. Endpoint/status-text cleanup remains a documentation followup, not a runtime change in this validation update.
This sign-off update changes documentation only: no firmware/test run, upload, erase, generated assets, source/config changes or commit. The user's existing `platformio.ini` edit is preserved. **Wait for a separately requested 8D.11; no full M3 or unrestricted browser-shell parity is claimed.**
## Second-slice implementation evidence (2026-09-08; before target sign-off)
**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.