Add Typed Admin Network Settings
This commit is contained in:
@@ -23,6 +23,16 @@ SSH role=admin ------> shared administration dispatcher <------ UART0
|
||||
(does not join the broker)
|
||||
```
|
||||
|
||||
## Typed Network settings (8D.12/8D.13)
|
||||
|
||||
`web_network_settings` provides admin-only secret-free GET snapshot and GET/POST operation routes; `web_ui.c` adds Network without changing broker/terminal ownership. Wi-Fi working/runtime projection is zero-wait under one mutex; mDNS is a separate consistent projection. SSIDs use reversible byte JSON, with explicit UTF-8-text/hex UI conversion; passwords are never returned/prefilled, only `password_configured`. Omitted passwords preserve current secrets; explicit replacement and disabled-STA clear are distinct, AP clear is always denied.
|
||||
|
||||
Only an ID enters the existing administration dispatcher. Its session/deadline revalidation precedes canonical generation-checked mutations; `wifi_manager` remains radio and reannouncement owner. Wi-Fi generation compare/merge/validation and queue-before-publication occur under its mutex; Save holds selected bytes stable, Load is stored-only without default-secret generation. mDNS independently checks generation for Set/Save/Load/Defaults and queues reannouncement; changed RAM with queue failure is explicitly reported, not rolled back. Edits require explicit Save. Next profile follows canonical enabled-priority order, not the profile editor's selected index.
|
||||
|
||||
One static login-bound pending/result slot and one firmware-lifetime one-second timer bound queued secret retention to 30 seconds plus scheduling latency. Dequeued locals wipe on return; admitted work is not hard-cancelled by logout/deadline. `accepted` means apply/owner admission, not online/DNS completion. Response delivery before disruption is not guaranteed; recovery is STA/AP inspection plus independent UART0 administration/native USB UART1 access, never automatic mutation replay. UI navigation preserves serial traffic/lease; actual network loss can disconnect network clients.
|
||||
|
||||
Bounds: 768-byte/four-receive request, 2,048-byte snapshot, 128-byte result; 27 handlers/six sockets, no task/stack-size/dispatcher-item/queue-depth/schema growth. Optional staged Network registration failures preserve unrelated routes; timer failure denies mutation without gating snapshot reads. Timer heap and target HTTPD/dispatcher stack/memory floors remain unmeasured. Full fields, registration rollback, states, UI and validation limits: `docs/phase8d12_13_implementation.md`. Browser-shell restrictions remain unchanged.
|
||||
|
||||
## Startup and initialization
|
||||
|
||||
`app_main()` in `src/main.c` is the composition root. The implemented order matters:
|
||||
@@ -35,7 +45,7 @@ SSH role=admin ------> shared administration dispatcher <------ UART0
|
||||
6. Load/generate HTTPS material, then initialize the independent user database, committing an empty database when storage is missing. User-database failure makes network authentication fail closed.
|
||||
7. Initialize the HTTPS runtime, SSH host-key material, and permanent SSH owner task.
|
||||
8. Load Wi-Fi configuration and the independent mDNS hostname configuration, persist generated first-boot Wi-Fi defaults when appropriate, initialize the nonfatal mDNS configuration service and Wi-Fi manager, and start Wi-Fi when configured for boot. The Wi-Fi manager owns subsequent mDNS announcement transitions.
|
||||
9. Start HTTPS and SSH only when their startup gates pass. The Wi-Fi portion requires valid configuration and successful manager initialization and, when enabled at boot, successful submission of its asynchronous start request; it does not require association, an IP address, or reachability. Both gates also require HTTPS security readiness, and SSH additionally requires its own security/runtime readiness. The HTTPS-security gate on SSH is an implemented dependency even though SSH has a separate host key.
|
||||
9. Start HTTPS and SSH only when their startup gates pass. The Wi-Fi portion requires valid configuration and successful manager initialization and, when enabled at boot, successful submission of its asynchronous start request; it does not require association, an IP address, or reachability. HTTPS additionally requires its own security/runtime readiness; SSH independently requires its own security/runtime readiness, not HTTPS identity readiness. This reflects `main.c` after accepted legacy-credential cleanup.
|
||||
10. Start the local status/control task if button initialization succeeded.
|
||||
11. Construct ESP-IDF's UART REPL to initialize `esp_console`, but do not start the stock REPL task. Register command groups, install completion, and start the custom UART frontend that feeds the shared dispatcher.
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ This is a semantic map, not a complete file inventory. Start here, then read the
|
||||
|
||||
## Web and WebSocket serial
|
||||
|
||||
- **Current 8D.12/8D.13:** `web_network_settings.{c,h}` owns optional admin-only GET `/api/settings/network` and GET/POST `/api/settings/network-operation`; `web_ui.c` supplies Network, UTF-8/hex SSID editing and explicit transient-secret/connection controls. `wifi_manager` owns generation-checked secret-free snapshots/patch/save/stored-only load and radio transitions; `mdns_service` owns independent conditional hostname persistence, with manager reannouncement. Existing dispatcher receives IDs only. 768-byte request/2,048-byte snapshot/128-byte result, one slot/one-second timer with 30-second queued expiry plus scheduling latency; no hard cancellation. 27 handlers/six sockets, no task/stack/queue/schema growth. Backend/cookie PASS, UI agent 97+renderer/CSP and review PASS, lifecycle agent21 PASS; final parent build/tests and target/resource validation pending. Full contract/exclusions/checklist: `docs/phase8d12_13_implementation.md`. Both phases user-authorized together; no 8D.14/M3/sign-off claim. Older next-phase statements below are historical.
|
||||
|
||||
- **8D.11:** `web_account_settings.{c,h}` extends Accounts with fingerprint-only POST `/api/settings/accounts/keys` and key-add/key-delete/key-clear on the existing operation endpoint/dispatcher. `user_database.{c,h}` owns zero-wait target-checked snapshots and canonical conditional key mutations. `web_ui.c` handles confirmations, sparse stable indices and self-revocation uncertainty. 24 handlers, six sockets; no new task/stack/queue depth. Host-tested/build-verified, target pending. Contracts/tests/checklist: `docs/phase8d11_implementation.md`.
|
||||
|
||||
**Responsibility:** serve authenticated HTTPS UI/API, issue WebSocket tickets, and adapt browser serial sessions to broker clients.
|
||||
@@ -109,7 +111,7 @@ This is a semantic map, not a complete file inventory. Start here, then read the
|
||||
- Files: `src/ssh_transport.{h,c}`, `src/ssh_security.{h,c}`, `src/ssh_console.{h,c}`
|
||||
- Interfaces: init/start/stop, session snapshots/disconnect/revocation, host-key replacement, counters
|
||||
- Called by: startup, network clients, user revocation, console/local UI
|
||||
- Dependencies: user database, broker, admin SSH console, secure random, wolfSSH/wolfSSL; current boot start gate also depends on `web_security` readiness
|
||||
- Dependencies: user database, broker, admin SSH console, secure random, wolfSSH/wolfSSL; boot start gate requires Wi-Fi and SSH security/runtime readiness, independently of HTTPS identity readiness (verified in `main.c` after accepted legacy cleanup).
|
||||
- Flow: role `user` -> broker; role `admin` -> `admin_ssh_console`
|
||||
- Ownership: after caller-side library initialization, one task pinned to core 1 owns runtime wolfSSH contexts/sessions; two fixed generation-tagged slots.
|
||||
- Security constraint: an interactive shell request is required; exec and subsystems are rejected, and no project file-transfer or forwarding route exists. PTY is not explicitly required.
|
||||
@@ -146,7 +148,7 @@ This is a semantic map, not a complete file inventory. Start here, then read the
|
||||
|
||||
- Files: `src/wifi_config.{h,c}`, `src/wifi_manager.{h,c}`, `src/wifi_console.{h,c}`, `src/mdns_config.{h,c}`, `src/mdns_service.{h,c}`, `src/mdns_console.{h,c}`, `src/network_console.{h,c}`
|
||||
- Interfaces: config defaults/validate/load/save; manager init/start/stop/apply/reconnect/next-profile/snapshot
|
||||
- Called by: startup, console, local UI, ESP event callbacks
|
||||
- Called by: startup, console, local UI, ESP event callbacks; typed Network settings uses secret-free zero-wait projections and dispatcher-owned canonical conditional mutations (8D.12/8D.13).
|
||||
- Dependencies: secure random for default AP password, NVS, ESP-NETIF/Wi-Fi/events, Espressif mDNS, lwIP diagnostics
|
||||
- Lifecycle: permanent manager task and bounded queue; callbacks enqueue compact events only.
|
||||
- Constraint: application NVS is authoritative (`WIFI_STORAGE_RAM`); working edits are not persisted until save. Start/stop, including local controls, intentionally update the RAM `enabled_at_boot` field. Working-config copies contain PSKs and must be tightly scoped and wiped; routine status/local UI must use secret-free snapshots.
|
||||
|
||||
@@ -4,6 +4,8 @@ This file is working memory. Update it during active work and before handoff; do
|
||||
|
||||
## Development state
|
||||
|
||||
- **8D.12/8D.13 implemented together by user authorization (2026-09-08); host-tested/build-verified, target pending:** Backend and admin Network UI deliver 8D.12 nonsecret STA/AP/profile/mDNS edits and explicit persistence, then 8D.13 secret replacement/disabled-STA clear and connection controls. Exact API, byte SSID/UTF-8+hex UI, owner/generation/persistence/uncertainty contracts: `docs/phase8d12_13_implementation.md`. One 768-byte request, 2,048-byte snapshot, 128-byte result; one login-bound slot and one-second ESP timer, 30-second non-executing expiry plus scheduling latency, not hard cancellation. Existing dispatcher IDs/manager owner; accepted != online. Wi-Fi Load stored-only, no reset/default-secret/export; mDNS separate generation/Set/Save/Load/Defaults/reannouncement. 27 handlers/six sockets, no stack/task/queue/schema growth; staged optional route failures preserve unrelated routes. Reported backend/cookie Network PASS; backend P3 queue-drop-counter fix complete; UI agent97+renderer/CSP/review PASS; lifecycle agent21 PASS. Parent final reruns PASS Network five production-path groups, cookie Network five+shared/accounts/serial-settings/admin, console boundary/canonical accounts, lifecycle21, UI97+CSP, idle18+guards, transport25/tickets12, store--serial, diagnostics12+guard and diff check. Independent reviews no remaining actionable findings. Parent pio run PASS24.99s, 99,548 B RAM / 1,742,437 B flash (+288/+36,656 vs accepted legacy cleanup). Earlier pre-final-UI integration build emitted nonfatal FATFS_PRINT_FLOAT config warning; no unrelated config edits. Host owner paths use radio/scheduler/storage doubles, not real network validation. Timer heap, memory floors, HTTPD/dispatcher margins, live Wi-Fi/mDNS/DNS/trust and target checklist remain pending. Profile editor is not explicit-index connection selection: only canonical Next profile. UART0/USB recovery, danger confirmations, no same-response delivery guarantee and unchanged browser-shell restrictions documented. No 8D.14, full M3, target acceptance or reserve approval. Documentation agent touched only authorized docs, no source/tests/assets/build/device/commands; also corrected stale SSH-to-HTTPS startup dependency against `main.c` from accepted legacy cleanup. Older wait-for-8D.12 statements below are superseded, not earlier scoped sign-offs.
|
||||
|
||||
- **Legacy-credential cleanup signed off (2026-09-08):** User explicitly accepts cleanup, confirms unchanged HTTPS certificate fingerprint and continued use of preexisting test users, supplementing the clean full-mix telemetry below. Supersedes prior target-pending/fingerprint-unconfirmed status for this cleanup; idle-cleanup scoped acceptance stands. User suspects two boot auth failures were stale pre-flash sessions in two browser tabs; plausible, not traced/confirmed. Exact acceptance and limits: `docs/legacy_credential_removal.md`. No unreported blank provisioning/recovery/fault-injection/soak/all-key checks, numeric reserve approval or broader phase/M3 acceptance inferred. Documentation only; no source/config/build/test/device/assets/commit action.
|
||||
|
||||
- **Post-legacy-removal target evidence (2026-09-08):** User provides 60-second boot/full-mix telemetry and reports substantial traffic. Five accounts/two admins; two SSH public-key sessions and two browser password logins operational. USB writer16, SSH observer9, web observers10/11, both admin consoles active at 230400 8N1 RTS/CTS DTR active. Serial WS connect2/disconnect0, admin1/0, all supplied web send/queue/protocol/close and SSH handshake/auth/IO failure counters zero; tickets all consumed/no expiry. Two web auth failures already present at boot unchanged under load, zero invalid login credentials; request origin unknown. Boot internal/DMA/PSRAM free66,488/58,732/8,246,148 B; loaded31,820/24,064/8,112,076 B, minima15,740/7,984/8,074,196 B, largest20,480/20,480/7,995,392 B. SSH stack minimum-free18,476 boot/16,284 loaded B. Exact counters/evidence limits in `docs/legacy_credential_removal.md`. No certificate fingerprint comparison, blank provisioning/recovery, all-key verification, exact revision/duration or explicit sign-off supplied; browser RX0 and no broker drop counters prevent all-route/bidirectional/lossless claims. No source/build/device action; documents only.
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
Only constraints supported by implementation or current project documentation belong here. When original rationale is unknown, the entry describes the observable constraint without inventing intent.
|
||||
|
||||
## Typed Network edits preserve manager ownership and current secret bytes
|
||||
|
||||
**Decision (8D.12/8D.13):** `web_network_settings` admits bounded typed operations into one login-bound slot; the existing dispatcher carries IDs only and calls canonical generation-checked Wi-Fi/mDNS APIs. HTTPD reads only zero-wait secret-free projections. Wi-Fi mutex-local compare/merge/validation preserves omitted PSKs and prevents stale edits undoing CLI/local changes; queue admission precedes RAM publication. Save stabilizes selected bytes under the mutex; browser Wi-Fi Load reads stored configuration only, never generates fallback AP secrets. mDNS uses its own generation and reports RAM-applied/reannouncement-not-queued separately.
|
||||
|
||||
**Consequence:** Keep SSIDs byte-reversible (UTF-8 text must first become bytes; arbitrary existing bytes require hex), password omission/Replace/disabled-STA Clear distinct, and AP clear denied even while off. No default/reset/secret-export route or explicit-index connection selection: only canonical Next. One-second timer/30-second queued expiry plus scheduling latency is not hard cancellation of admitted work. `accepted` is owner admission, not online or verified DNS; acknowledgements may be lost before network disruption. Recovery/confirmation and no automatic mutation replay are correctness requirements, not UI polish. Optional route failures must not gate unrelated services; browser-shell policy remains separate. No task/stack/queue/schema expansion; new timer/slot/buffer costs still require target heap and HTTPD/dispatcher margin evidence.
|
||||
|
||||
**Relevant files and full contract:** `src/web_network_settings.{c,h}`, `src/wifi_manager.{c,h}`, `src/mdns_service.{c,h}`, `src/web_ui.c`, `docs/phase8d12_13_implementation.md`.
|
||||
|
||||
## One broker mediates all production serial transports
|
||||
|
||||
**Decision:** USB CDC, WebSocket, and role-`user` SSH access UART1 through `session_broker`; transports do not independently own the serial service.
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
# Phase 8D.12/8D.13 — Typed Network settings
|
||||
|
||||
## Status and scope (2026-09-08)
|
||||
|
||||
The user authorized both phases together. Backend and admin-only Settings/Network UI are implemented: **8D.12** delivers secret-free STA/AP/profile projections, non-secret edits, explicit persistence and mDNS; **8D.13** adds explicit Wi-Fi password replacement/disabled-STA clear and manager-owned connection controls. This supersedes older wait-for-8D.12 statements, not previous scoped acceptance. No 8D.14 work, M3 completion, target sign-off or numeric memory reserve approval is claimed.
|
||||
|
||||
Source authority: `src/web_network_settings.{c,h}`, `wifi_manager.{c,h}`, `wifi_config.{c,h}`, `mdns_service.{c,h}`, `mdns_config.{c,h}`, `admin_ssh_console.{c,h}`, integration in `web_server.c`/`src/CMakeLists.txt`, and authored `web_ui.c`. Contract/test details: `tests/web_network_settings/README.md`, `tests/web_ui_session/network.cjs`, cookie Network tests and server lifecycle tests. This documentation handoff changes no source, tests, generated assets or commands. Browser-shell Wi-Fi/mDNS restrictions are unchanged; typed routes do not grant general command execution.
|
||||
|
||||
## Routes, authorization and isolation
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|---|---|---|
|
||||
| GET | `/api/settings/network` | Secret-free working/runtime snapshot |
|
||||
| GET | `/api/settings/network-operation` | Latest result for initiating login |
|
||||
| POST | `/api/settings/network-operation` | One typed operation |
|
||||
|
||||
All require a current admin cookie/principal. Normal users are denied, including direct API access. Existing duplicate-header, framing and Fetch-Metadata protections apply. GET is bodyless; all routes reject queries. GET permits absent Origin but rejects a supplied mismatch. POST requires matching Origin, CSRF and exactly `application/json` or `application/json; charset=utf-8`. JSON responses are no-store, nosniff and no-referrer. There is no credential-export endpoint.
|
||||
|
||||
The three added method/path handlers bring the configured budget to **27 handlers**, with **six sockets**, LRU policy unchanged. Registration is optional and staged: snapshot failure skips Network operation registration; operation GET failure skips POST; POST failure unregisters operation GET while retaining the snapshot. This preserves unrelated Settings, login/status, serial and admin routes rather than making Network a base-service startup dependency. Snapshot reads do not depend on successful secret-timer admission. Optional transport failures retain their existing independence. Host lifecycle coverage is not proof of live low-memory behavior.
|
||||
|
||||
## Complete bounded snapshot
|
||||
|
||||
The JSON object has exactly these domains/fields:
|
||||
|
||||
| Object | Fields and meanings |
|
||||
|---|---|
|
||||
| `wifi` | `generation` (nonzero uint32), `enabled_at_boot` (boolean), `ap`, `profiles` |
|
||||
| `wifi.ap` | `policy` (`off`, `fallback`, `always`), `channel` (1..11), `ssid` (byte string), `password_configured` (boolean) |
|
||||
| each `wifi.profiles` entry | `index` (stable 0..3), `enabled` (boolean), `priority` (0..255), `security` (`mixed`, `wpa3`), `ssid`, `password_configured` |
|
||||
| `runtime` | `started` (boolean), `state`, `active_profile` (-1 means none, otherwise 0..3), `ip` (dotted IPv4 string), `ap_running` (boolean), `ap_clients` (count), `last_error` (numeric `esp_err_t`) |
|
||||
| `mdns` | `generation` (nonzero uint32), `suffix`, `hostname` (without `.local`), `announced` (boolean), `last_error` (numeric `esp_err_t`) |
|
||||
|
||||
All four profiles are always present. Runtime states are `stopped`, `starting`, `connecting`, `waiting-ip`, `online`, `backoff`, `ap-only`, `error`, with `unknown` fallback. `mixed` means WPA2-or-stronger, not open or a WPA2-only guarantee. `announced` is expected STA announcement state, not client-verified DNS.
|
||||
|
||||
Wi-Fi working configuration and runtime are copied together under the Wi-Fi mutex; mDNS is a separate consistent projection, **not an atomic cross-domain snapshot**. Both acquisitions are zero-wait. Contention/unavailability returns 503 `snapshot_unavailable`, never inferred partial values. HTTPD performs no driver/NVS call or secret-bearing configuration read. Neither projection structs nor JSON contain saved PSKs or PSK lengths; only `password_configured` is exposed to support staging/enabling validation.
|
||||
|
||||
### SSIDs are reversible bytes, not JSON Unicode text
|
||||
|
||||
SSID limits are **0..32 decoded bytes**. AP and enabled STA SSIDs must be nonempty. Empty STA SSID requires disabled status and no password.
|
||||
|
||||
The wire codec accepts printable ASCII, standard single-character JSON escapes (`\"`, `\\`, `\/`, `\b`, `\f`, `\n`, `\r`, `\t`) and case-insensitive `\u00HH`; each decoded codepoint is one byte. Raw non-ASCII, non-byte Unicode, surrogates and malformed escapes are rejected. Snapshot encoding uses `\u00hh` for nonprintable/non-ASCII bytes, quote and backslash. Thus `"A\u0000\u00ff"` represents `41 00 ff`, including embedded NUL and non-UTF-8 bytes.
|
||||
|
||||
The UI offers **UTF-8 text** and **literal hex byte pairs**. It UTF-8-encodes text before byte-preserving JSON serialization; it does not submit raw JS Unicode strings as SSIDs. Existing bytes enter text mode only after fatal UTF-8 decoding and exact re-encoding (including BOM preservation), with control bytes excluded; otherwise hex is selected. Failed conversion preserves the original input. Hex accepts byte pairs with optional single spaces; the decoded limit remains 32 bytes. Summaries display printable ASCII SSIDs as quoted text (empty SSID as `""`), with exact hex fallback when any byte is outside ASCII 0x20..0x7e. Quotes/backslashes are escaped for unambiguous display; rendering uses DOM text, not HTML. No silent replacement decoding, double encoding or truncation is intended.
|
||||
|
||||
## Complete POST contract
|
||||
|
||||
One flat JSON object, at most 13 distinct keys; unknown/duplicate fields are rejected. No nested config, arrays, nulls, signed/fractional/exponent integers or leading-zero numbers. Booleans are JSON booleans. Every optional patch field preserves the current value when omitted; patches require at least one patch field. A request selects one domain/target only.
|
||||
|
||||
| `action` | Required fields besides `action` | Optional fields |
|
||||
|---|---|---|
|
||||
| `wifi-patch` | Wi-Fi `generation` | `enabled_at_boot`, `ap_policy` (`off/fallback/always`), `channel` (1..11), `ssid`, `password`, `clear_password:true` |
|
||||
| `profile-patch` | Wi-Fi `generation`, `profile` (0..3) | `enabled`, `priority` (0..255), `security` (`mixed/wpa3`), `ssid`, `password`, `clear_password:true` |
|
||||
| `wifi-save`, `wifi-load` | Wi-Fi `generation` | none |
|
||||
| `start`, `stop`, `reconnect`, `next-profile` | none | none |
|
||||
| `mdns-set` | mDNS `generation`, `suffix` | none |
|
||||
| `mdns-save`, `mdns-load`, `mdns-defaults` | mDNS `generation` | none |
|
||||
|
||||
Generation is the selected domain's nonzero uint32 snapshot value. Replacement password is **8..63 printable ASCII bytes**; empty replacement is invalid. Omission means Keep, never clear. Replacement and clear cannot coexist; `clear_password:false` is rejected. A disabled STA password can be cleared, including a single patch that disables and clears. Enabled STA requires a valid password. AP clear is canonically invalid **even with AP policy off**; no open-AP path exists. Syntactically admitted but canonically invalid requests can return 202 followed by `invalid`.
|
||||
|
||||
### Ownership, concurrency and persistence
|
||||
|
||||
HTTPD validates/adopts a bounded request; **only its operation ID** enters the existing administration dispatcher. The dispatcher rechecks initiating session/principal/admin currentness and dequeue deadline, then invokes canonical APIs. The existing Wi-Fi manager task remains the radio/event/mDNS-transition owner; no second driver owner or generic job executor is added.
|
||||
|
||||
Wi-Fi patch checks generation, merges omitted fields against current secret bytes and validates the whole candidate **under the configuration mutex**. Required restart queue admission precedes publication; queue failure leaves RAM unchanged. Generations do not wrap/reuse. CLI applies and local Start/Stop participate, so stale browser edits cannot undo newer state. Save holds the selected generation stable under the mutex during canonical persistence. Load reads only the existing canonical stored blob and conditionally installs it; missing, invalid/incompatible or failed storage does not generate/install a new AP secret or change RAM.
|
||||
|
||||
Edits are RAM-only until explicit Save. Disabled-profile-only edits do not restart the radio; enabling/disabling and enabled-profile/AP changes follow canonical asynchronous restart policy. `enabled_at_boot` alone changes next-boot policy, not immediate radio state. Start/Stop also change RAM `enabled_at_boot`; Save persists that choice. Reconnect/Next are no-ops when stopped. **Next profile** means the next enabled profile in canonical priority order, wrapping. The UI profile selector chooses the configuration to edit, **not the profile to connect to**; it labels the connection action Next profile rather than promising explicit-index selection.
|
||||
|
||||
mDNS has its own mutex/generation and conditional Set/Save/Load/Defaults. Suffix is 1..55 lowercase ASCII letters/digits/hyphens with no leading/trailing hyphen; hostname is `sak-<suffix>`. Set/Load/Defaults change RAM and request manager-owned reannouncement; Save persists. Load may select deterministic MAC-derived defaults and reports that outcome. Offline edits are applied to an already-initialized responder on the next STA IP. mDNS is STA-only and failure is nonfatal. A RAM change followed by reannouncement queue failure is not rolled back. Existing NVS remains unencrypted; logical clear/replacement is not secure flash erasure.
|
||||
|
||||
## Admission, result states and secret lifetime
|
||||
|
||||
Successful POST returns HTTP 202; GET returns HTTP 200. Both contain exactly `id`, `action`, `state`, `error`, for example `{"id":42,"action":"profile-patch","state":"pending","error":0}`. Only the initiating login can retrieve the slot. Other logins/no retained result see `{"id":0,"action":"none","state":"idle","error":0}`. No query ID or history exists: UI compares acknowledged ID/action. Later admission replaces the previous result. IDs never wrap; exhaustion denies admission until reboot.
|
||||
|
||||
| State | Meaning |
|
||||
|---|---|
|
||||
| `idle` | No result retained for this login |
|
||||
| `pending` | Queued or executing |
|
||||
| `accepted` | RAM apply/owner queue request accepted; **not** association, DHCP, online, completed radio transition or verified DNS |
|
||||
| `ok` | Explicit Wi-Fi/mDNS Save succeeded |
|
||||
| `failed` | Canonical/owner/storage failure |
|
||||
| `cancelled` | Queued expiry or session/currentness/dequeue deadline denial before canonical admission |
|
||||
| `stale` | Selected generation mismatched |
|
||||
| `invalid` | Canonical configuration rejected patch/load |
|
||||
| `loaded_defaults` | mDNS Load selected deterministic RAM defaults and queued reannouncement |
|
||||
| `applied_not_queued` | mDNS RAM changed but reannouncement queue failed; refresh, do not assume rollback |
|
||||
|
||||
`error` is numeric `esp_err_t`, not arbitrary input/error-text echo or a state override; cancellation can have zero error. Later runtime errors appear in fresh snapshots, not by rewriting `accepted`.
|
||||
|
||||
Existing HTTP errors include 400 framing/query/body errors, 401 authentication, 403 Origin/CSRF/admin denial, and 503 auth unavailable; unsupported handler methods return 405. Backend errors are 400 `invalid_network_request`, 503 `timer_unavailable`, 503 `busy` with `Retry-After: 1`, and 503 `snapshot_unavailable`. Malformed input never queues; unread-body/receive failures close rather than drain.
|
||||
|
||||
One static session-bound pending/result slot has an executing reservation under a short portMUX. One firmware-lifetime **one-second ESP timer** cancels and wipes non-executing inputs at **30 seconds plus scheduling latency**. Shared input wipes on dequeue before auth checks; dispatcher-local inputs wipe on every return. HTTP body/parser/operation inputs wipe on rejection and before response IO. Already-admitted work may finish after logout/disconnect/deadline: no hard cancellation, transactional session-liveness or hard wall-clock erasure guarantee. Expired IDs cannot execute a replacement operation. Queue entries never carry credentials.
|
||||
|
||||
## UI behavior and connection-loss safety
|
||||
|
||||
Network is an admin-only Settings subview with strict snapshot/result shape validation, independent request ownership and stale/session/navigation fencing. Apply submits changed fields for the selected target. Refresh discards drafts; Save persists device working state, not unsubmitted browser inputs. Stale/unavailable snapshots disable mutation instead of inferring values.
|
||||
|
||||
Passwords are never fetched/prefilled: explicit Keep/Replace/Clear, with Clear restricted to disabled STA. Replacement input has a **60-second context-bound browser lifetime** and best-effort clearing on expiry, context/navigation/session change, refresh, submission and rejection. This does not promise secure erasure of immutable JS/browser copies. AP clear is unavailable in UI and denied by canonical validation.
|
||||
|
||||
Disruptive Start/Stop/Reconnect/Next/Load, AP changes and enabled-profile changes require explicit confirmation and recovery warnings; mDNS Load/Defaults confirm replacement of working state. Persistence and hostname consequences remain explicit. No automatic mutation replay. After an acknowledged POST, checks run at one-second intervals, at most ten GETs/15 seconds, with session checks; known terminal results refresh the snapshot. Manual Check Result/Refresh handles pending, replaced or uncertain results. A lost acknowledgement may leave the latest result attributable to an earlier request/another tab; an unknown ID must not be treated as proof of completion.
|
||||
|
||||
There is **no same-response delivery guarantee**: HTTPS, SSH and both browser WebSockets can disconnect before the POST acknowledgement or result arrives. `accepted`, a lost response, 401 or disconnect proves neither online nor cancellation. Reconnect through the available STA/AP address and inspect state before retrying. Changed hostname requires DNS verification and browser trust/login review at the new origin; host-only cookies do not move with the name. UART0 remains administrative recovery; native USB remains network-independent UART1 access, not a replacement admin console.
|
||||
|
||||
Settings navigation itself does not close terminals, release writer ownership or reconfigure UART1. Hidden terminal draining and selected-keyboard rules remain. Actual network disruption can close network transports and consequently release their broker client/lease; it does not intentionally stop the serial service or USB. Do not claim uninterrupted network serial delivery across a radio restart.
|
||||
|
||||
## Resources and evidence
|
||||
|
||||
- POST maximum **768 bytes**, at most **four receives**, **13 keys**, **64-byte parser value scratch**; no heap JSON tree.
|
||||
- Snapshot buffer **2,048 bytes**; backend maximum escaped fixture has **1,877 payload bytes** (fixture bound, not runtime heap measurement).
|
||||
- Result buffer **128 bytes**; **one slot and one small persistent timer**.
|
||||
- **27 handlers/six sockets**; no task count, task stack size, dispatcher item size, queue depth or persisted schema growth. Added state/timer/buffers are not zero-cost: runtime timer heap, internal/DMA/PSRAM floors, allocation overhead and HTTPD/dispatcher stack margins remain pending.
|
||||
|
||||
Reported evidence, not reruns by this documentation agent:
|
||||
|
||||
- Backend agent: Network backend and cookie Network suites PASS; its contract README records additional cookie/settings/account/admin and canonical console regressions. Backend P3 queue-drop-counter finding fixed, preserving failed queue-admission observability.
|
||||
- UI agent: **97 groups plus renderer/CSP checks PASS**, review PASS.
|
||||
- Integration/lifecycle agent: **21 groups PASS**.
|
||||
- Backend sanitizer attempt could not link because host ASan/UBSan libraries were missing; no sanitizer pass claimed.
|
||||
- **Final parent integrated validation PASS:** `python3 tests/web_network_settings/run.py` (five production-path groups), cookie `--network` (five Network groups plus shared auth), `--accounts`, `--serial-settings`, `--admin`; canonical console boundary `run.py` and `accounts.py`; server lifecycle **21**; browser UI **97 plus renderer/CSP**; idle cleanup **18 + SDK guards**; admin transport **25**/tickets **12**; session-store `--serial`; diagnostics **12 + integration/secrecy**; `git diff --check`. The backend queue-drop projection finding is fixed and covered in these reruns. Independent backend and UI reviews reported no other actionable findings.
|
||||
- Parent `pio run` **PASS, 24.99 s**, **99,548 B RAM / 1,742,437 B flash**, **+288 B RAM / +36,656 B flash** versus accepted legacy-cleanup build (99,260 / 1,705,781). Earlier integration-only build was 99,548 / 1,718,721 before final UI; it emitted a nonfatal `FATFS_PRINT_FLOAT` boolean-configuration warning. The final parent incremental build did not emit it. No unrelated configuration change was made.
|
||||
- UI agent measured authored rendered HTML **23,184 B (+5,245)** and app.js **86,535 B (+23,843)**. These are uncompressed renderer sizes, not separate target heap measurements. Generated embedded vendor assets were not regenerated.
|
||||
- No upload, erase, hardware validation, commit, heap reserve or target sign-off. Real association/DHCP/AP transitions, mDNS announcement, concurrent radio-owner behavior and HTTPD/dispatcher stack floors remain pending; host manager tests exercise extracted production paths with driver/scheduler/storage doubles rather than a full real radio loop.
|
||||
|
||||
## Pending target checklist
|
||||
|
||||
Record revision/browser/client mix and only nonsecret evidence. Prepare UART0 and native USB before deliberate network disruption; use disposable profile changes with an explicit recovery plan. This is a procedure, not completed validation.
|
||||
|
||||
1. Verify admin-only UI and direct-route normal-user denial, missing/wrong Origin/CSRF, expiry/logout, unavailable snapshot and malformed/boundary fields. Check no PSK/value/length leakage through JSON, UI summaries, logs, completion or local display.
|
||||
2. Round-trip printable, UTF-8, BOM, control/NUL, non-UTF-8 and maximum 32-byte SSIDs in text/hex; verify failed conversions preserve drafts. Test Keep/Replace/disabled-STA Clear, combined disable/clear, enabled-STA and AP-clear denial, expiry/context changes and failed submissions without stored-secret prefill.
|
||||
3. Race browser generations against CLI/local Start/Stop and another tab. Exercise queue saturation/drop accounting, stale patches and save/load. Confirm failed admission leaves RAM unchanged. Test stored-only Wi-Fi Load with missing/invalid/read/commit failures without default-secret generation; distinguish working edits, explicit Save and reboot persistence.
|
||||
4. Exercise Start/Stop/Reconnect/Next and AP policies, stopped no-ops, canonical priority/wrap, and editing versus connection selection. Cancel confirmations. Deliberately lose acknowledgements/results, revisit Settings and use Check Result/Refresh without replay. Verify STA/AP recovery and UART0/USB availability.
|
||||
5. Exercise live and offline mDNS Set/Save/Load/Defaults, stale generation, queue failure (`applied_not_queued`), responder init/live failures and next-STA-IP reconciliation. Verify actual client DNS withdrawal/reannouncement, changed-hostname trust/login and separate IP/name origins; `announced` alone is insufficient.
|
||||
6. Keep USB, two browser serial clients, SSH serial, and both admin routes active where possible. Check one writer/isolated observers, hidden output draining and no navigation-induced serial disruption. Separate expected losses from actual network changes from unrelated serial/broker regression; capture broker drops and transport errors, not merely UI responsiveness.
|
||||
7. Exercise optional route registration/allocation failure and stop/restart isolation on target where fault injection is available; retain base login/status, serial/admin and other settings. Confirm timer-unavailable admission fails safely and no queued stale ID mutates newer work. Delayed dispatcher/scheduler behavior is not a hard-cancellation test guarantee.
|
||||
8. Measure settled boot/full-mix internal/DMA/PSRAM free/minimum/largest blocks, memory floor during TLS/admission and Network reads/writes, timer/slot overhead, repeated-operation cleanup and soak. Capture **HTTPD and administration-dispatcher stack high-water margins**, not SSH alone; no stack/task/queue increase is authorized by this checklist. Completed parent build/tests and pending user target acceptance remain separate evidence.
|
||||
|
||||
## Explicit exclusions
|
||||
|
||||
No specific-index connection selection (only canonical Next), Wi-Fi reset/default generation, AP-open mode, secret export/fetch, durable operation history/idempotency, cancellation endpoint, generic jobs/command runner, scans or new diagnostics workflow. No display settings/8D.14, M3 completion, browser-shell policy widening, new commands, generated-asset changes, schema migration, task/stack/queue expansion, factory erase or new security hardening. Accepted legacy-cleanup startup correction is documentary only: `main.c` independently gates SSH on Wi-Fi plus SSH security/runtime readiness, not HTTPS identity readiness.
|
||||
@@ -1,6 +1,8 @@
|
||||
# Phase 8D — Incremental web administration plan
|
||||
|
||||
**Latest implementation (2026-09-08):** User-requested **8D.11 implemented, host-tested/build-verified; target sign-off pending**. Accounts fingerprint listing and Ed25519/P256 import/delete/clear use canonical target-checked APIs and the existing dispatcher. Sparse-slot selection regression fixed; 24 handlers/six sockets, no new task/stack-size/queue expansion. Final build 96,076 B RAM / 1,703,685 B flash. [8D.11 record](phase8d11_implementation.md) contains API/bounds, test attribution and pending hardware checklist. Supersedes historical wait-for-8D.11 instructions below; 8D.8–8D.10/M2 remain accepted. No M3 completion or 8D.12 work.
|
||||
**Current implementation (2026-09-08):** User authorized **8D.12 and 8D.13 together**, backend and Network UI delivered. 8D.12 covers nonsecret STA/AP/profile/mDNS edits and persistence; 8D.13 adds explicit secret replacement/disabled-STA clear and connection controls. Profile selection means selecting a configuration to edit; connection control is canonical **Next profile**, not explicit-index selection. 27 handlers/six sockets, one bounded slot/timer, no task/stack/queue/schema growth. Backend/cookie Network PASS, UI agent97+renderer/CSP/review PASS, lifecycle agent21 PASS; backend P3 queue-drop-counter finding fixed. **Parent integrated suites and build PASS:** 24.99 s, 99,548 B RAM / 1,742,437 B flash (+288/+36,656 vs legacy-cleanup baseline). Parent UI97/CSP, lifecycle21, Network/HTTP policy, canonical console/accounts, transport/tickets, idle/store/diagnostics checks passed; exact attribution below. **Target behavior, timer heap/memory floors and HTTPD/dispatcher stack margins remain pending.** [8D.12/8D.13 implementation](phase8d12_13_implementation.md) is the exact API/SSID/secret/uncertainty contract and checklist. No Wi-Fi reset/default-secret/export, browser-shell policy widening, 8D.14 work, M3 completion or target sign-off. Supersedes historical next-request restrictions below; previous scoped acceptance stands.
|
||||
|
||||
**8D.11 implementation history (2026-09-08):** User-requested **8D.11 implemented, host-tested/build-verified; target sign-off pending**. Accounts fingerprint listing and Ed25519/P256 import/delete/clear use canonical target-checked APIs and the existing dispatcher. Sparse-slot selection regression fixed; 24 handlers/six sockets, no new task/stack-size/queue expansion. Final build 96,076 B RAM / 1,703,685 B flash. [8D.11 record](phase8d11_implementation.md) contains API/bounds, test attribution and pending hardware checklist. Supersedes historical wait-for-8D.11 instructions below; 8D.8–8D.10/M2 remain accepted. No M3 completion or 8D.12 work.
|
||||
|
||||
**Latest target sign-off (2026-09-08):** User reports thorough Serial parameter display/settings and user/account testing, supplies settled boot/full-client-mix evidence, and explicitly says implemented work can be signed off. **Implemented 8D.8–8D.10 are accepted**, including 8D.9 UX and both 8D.10 slices. Supersedes target/signoff-pending statements below, not build/test evidence or restrictions. [8D.10 target acceptance record](phase8d10_implementation.md) contains all six loaded samples, client mix, counters and evidence limits. Full mix at 230400 baud confirms SSH sole writer + USB/two web observers with both admin routes. Final internal/DMA free 32,556/24,800 B, lifetime minima 19,228/11,472 B, largest 23,552 B; isolated failures retained without diagnosis. Exact revision/browser/durations/individual cases are unspecified; no leak-free-soak or reserve approval inferred. M2 stands; full M3/browser-shell parity is not claimed. **Next is 8D.11 only when separately requested; this sign-off authorizes no implementation.**
|
||||
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
|
||||
This document retains phase-specific regression procedures. The Phase 8A and 8B sections describe the behavior of those historical implementation baselines; they are not the current end-to-end acceptance behavior. In current Phase 8C firmware, HTTPS and SSH authenticate through the user database, role-`user` SSH sessions receive the broker-backed serial stream, and role-`admin` SSH sessions receive the administration shell. Use the Phase 8C section for current routing and shared-console validation. Never include generated or entered passwords in test logs.
|
||||
|
||||
## Current Network settings regression procedure — 8D.12/8D.13
|
||||
|
||||
Both phases were authorized and implemented together. The [implementation record](phase8d12_13_implementation.md) defines the complete API, limits, result states, exclusions and pending target checklist. Backend/cookie Network PASS, UI agent97+renderer/CSP/review PASS and lifecycle agent21 PASS are reported evidence; final parent build/tests and target sign-off remain pending. Do not treat this procedure as executed or infer M3 completion/8D.14 authorization.
|
||||
|
||||
- Verify admin-only Settings/Network and direct-route normal-user denial, current cookie/principal, body/query/framing/Origin/CSRF checks, unavailable/contended snapshots and generation races against CLI/local controls.
|
||||
- Round-trip UTF-8 and arbitrary SSID bytes through text/hex, including NUL/BOM/non-UTF-8 and 32-byte boundaries. No saved PSK or length may appear in responses, status/logs/completion/local display. Keep omits credentials; Replace never accepts blank; disabled-STA Clear (including disable+clear) works; enabled-STA/AP clear is denied even when AP policy is off. Check transient-input expiry and context/session clearing.
|
||||
- Distinguish RAM Apply, explicit Save, stored-only Wi-Fi Load and reboot persistence. Missing/invalid/failing Wi-Fi storage must not install generated defaults or new AP secrets. Exercise stale generations, queue failure/drop accounting and NVS failures without secret logging. No Wi-Fi reset/default/export action exists.
|
||||
- Prepare UART0 and USB before confirming disruptive actions. Test Start/Stop (including RAM boot policy), Reconnect/Next, AP policies and stopped no-ops. The selected profile is an edit target, not explicit connection selection; Next uses canonical priority/wrap. Cancel confirmations and exercise lost ACK/401/disconnect, manual Check Result/Refresh, another-tab result replacement and no automatic replay. `accepted` is not online; delivery before disconnection is not guaranteed. Reconnect via STA/AP; UART0 administers recovery, USB preserves independent UART1 access.
|
||||
- Test mDNS generation/Set/Save/Load/Defaults, live/offline reannouncement, next STA IP, init/live failure isolation and `applied_not_queued`. Verify actual client DNS and changed-hostname browser trust/login, not merely `announced`. Confirm no unintentional Wi-Fi secret reset.
|
||||
- With USB/two web serial/SSH serial and both admin routes, verify hidden output draining and writer/observer preservation through Settings navigation. Separate actual network-disruption losses from serial/broker regressions. Exercise optional Network route/timer failure and stop/restart without taking down unrelated routes. Browser-shell restrictions remain unchanged.
|
||||
- Capture boot/full-mix internal/DMA/PSRAM free/minimum/largest blocks and memory floors during TLS/Network operations; timer heap/slot costs, repeated-operation cleanup/soak and **HTTPD/dispatcher stack margins** remain required. Record exact revision/client mix and nonsecret counters, including broker and manager queue drops. Host tests do not establish target reserve or hard scheduling/cancellation guarantees.
|
||||
|
||||
## Current legacy-removal regression procedure
|
||||
|
||||
The legacy bootstrap/credential/reconciliation instructions in the phase baselines below are **historical only**, superseded by [legacy credential removal](legacy_credential_removal.md) and the current [command reference](command_reference.md). Basic authentication is also historical; current HTTPS uses cookie login. The current overrides apply to later Phase 8C/browser procedures too: no `user bootstrap` or `web credentials` command remains, first-admin creation uses normal UART0 `user add`, and recovery rebuilds empty. Never treat the checklist below as evidence of execution.
|
||||
|
||||
Reference in New Issue
Block a user