@@ -110,6 +110,8 @@ A WebSocket connection requires a one-time, principal-bound ticket with a maximu
`web_httpd_adapter` is the sole private ESP-IDF 5.5.0 boundary. Its compile-time version guard requires review on upgrades. It validates NUL-separated parsed headers because public getters return only the first field, and rejects duplicates/ambiguous framing. The serial URI is registered as ordinary HTTP GET so cookie/ticket/principal/broker admission precedes explicit 101 and frame-handler installation; automatic IDF WebSocket routing would send 101 too early. Cleanup wipes consumed scratch but preserves right-aligned unread pending data. CMake compiles HTTPD logs above ERROR out to prevent header/ticket logging. No SDK patch or component copy exists. See `docs/phase8d3_implementation.md` for source verification, tests and pending on-wire checks.
`web_diagnostics` observes public synchronous HTTPS create/close callbacks without replacing socket/transport cleanup, and wraps only the four serial/admin ticket/upgrade handlers. Six always-maintained post-TLS metadata records supply a console-safe occupancy snapshot without querying HTTPD off-owner; an opt-in 32-entry numeric ring adds timing/heap/HTTPD stack samples. Firmware-lifetime connection sequences and capture epochs survive clear/restart and fence fd reuse/toggle races. No tasks/probes/event subscriptions; no authentication/request data retained. UART0/admin SSH commands never wait for HTTPD; browser policy remains unchanged. This is successful-TLS occupancy, not preaccept or failed/in-progress handshake instrumentation; exact limits/overhead in `docs/phase8d11_implementation.md`.
Web serial initialization is failure-isolated from the base HTTPS service: if the transport cannot initialize, `web_server_init()` can still succeed and serve authenticated non-WebSocket routes.
`web_ui.c` contains authored index/application strings and response policy; it validates `/api/session` before connect/restore, adds explicit Sign out, and cancels stale work on 401/logout/page exit. `web_login_ui` is a standalone public page without protected-asset dependencies. Both authentication documents and app script are no-store. Its restrictive CSP contains a hard-coded hash of the inline loader, so those two must change atomically; preserve same-origin connections, no-referrer behavior, frame denial, and the existing cache policy. `web_assets_data.c` contains checked-in generated arrays for vendored compressed xterm assets and the logo. Normal builds compile these arrays directly; they do not regenerate assets.
@@ -4,6 +4,8 @@ This file is working memory. Update it during active work and before handoff; do
## Development state
- **Authorized 8D.11 admission diagnostic slice complete (2026-09-08), host-tested/build-verified; target pending:**`web_diagnostics.{c,h}` uses public synchronous HTTPS create/close callbacks and four ticket/upgrade wrappers, six fixed metadata slots and a 32-record opt-in numeric ring. Console `web diagnostics enable|disable|show|clear` via UART0/admin SSH; browser policy unchanged. No close/open override, queue probe/task, capacity/timeout/log-level/assets/SDK change. Post-TLS owner-published occupancy only; TLS failures/in-progress handshakes and preaccept backlog remain invisible. Final `pio run` PASS **10.30 s, 99,156 B RAM / 1,707,597 B flash**, **+3,080/+3,912 B** versus recorded key-slice baseline. Ring/table target symbols 2,816/192 B; diagnostic handler/record/TLS callback/show local frames 144/144/160/528 B excluding callees, runtime margins unmeasured. PASS diagnostic **12+1**, server lifecycle **16**, canonical console lifecycle/policy/boundary, canonical account/key transactions, cookie/admin and cookie/accounts, admin transport **25**/tickets **12**, browser **68 plus renderer/CSP**, store/serial and diff check. Exact files/commands/contracts and reproduction checklist in `docs/phase8d11_implementation.md`. Existing key work preserved; no device/commit/upload/erase, timeout diagnosis, target acceptance or reserve approval claimed. Next is user target capture, not capacity changes or 8D.12.
- **8D.11 partial target evidence (2026-09-08):** User supplied 60-second fresh-boot/full-mix telemetry; no functional/sign-off claim. Full mix eventually established (two SSH public-key sessions, SSH writer + USB/two web observers, both admins), but last browser admission again required retries. Three TLS errors `-0x0050` verified against installed mbedTLS as NET_CONN_RESET, not allocation failure; cause unknown. Loaded internal/DMA free 32,596/24,840 B, lifetime minima 5,468/532 B, largest 22,528 B; per-region minima are conservative/non-simultaneous. Settled free is similar to prior 8D.10, minima much lower; no causal regression attribution. SSH stack minimum-free 18,468 boot/16,276 loaded B. Web send failure/close 1, expired tickets 3; SSH handshake/auth failures zero; no broker drop counters. Exact memory/counters/evidence limits in `docs/phase8d11_implementation.md`. Previously accepted admission issue recurred, not fixed; investigate timing/socket occupancy/allocation correlation before capacity changes. Key algorithms/new UI provenance, mutation checklist, HTTPD/dispatcher margins, cleanup/soak and target sign-off remain pending. Documentation-only update; no source/build/device action.
- **8D.11 implemented (2026-09-08), host-tested/build-verified; target sign-off pending:** Accounts now supports fingerprint listing and bounded Ed25519/P256 public-key import/delete/clear. Conditional database wrappers check target identity under the canonical lock; existing dispatcher/account slot and target-only revocation remain. Optional POST `/api/settings/accounts/keys` brings handler budget to 24, six sockets/tasks/stacks/queue depth unchanged. Sparse stable key slots are supported; review finding fixed and regressed. Parent canonical accounts, cookie accounts, UI, lifecycle (16), build and diff checks passed; final agent UI extension passes 68 groups plus CSP. Final production build 14.85 s, 96,076 B RAM / 1,703,685 B flash (+168/+9,448 vs 8D.10). Exact API, evidence attribution and pending target checklist: `docs/phase8d11_implementation.md`. No device/assets/commit action, reserve approval, full M3 claim or 8D.12 work. Supersedes the older wait-for-8D.11 instruction below, not accepted 8D.8–8D.10/M2.
Documentation-only evidence update: no new build, source/config edits, upload, erase, device commands or sign-off. Next diagnostic evidence should correlate connection attempts with heap/allocation failures and browser network timing/socket occupancy rather than assume heap exhaustion or increase socket capacity blindly. Key-workflow checklist and reserve approval remain open.
Separately user-authorized instrumentation for the recurring third-webshell timeout, **not a timeout fix or a key-workflow sign-off**. Implemented in `src/web_diagnostics.{c,h}`, wired through `web_server.c`, `web_console.c`, `console_completion.c` and `src/CMakeLists.txt`. No generated assets, SDK patch, broad HTTPD debug logging, broker/serial change, new task/timer/queue, socket/URI capacity change, timeout change, LRU change or TLS cleanup override.
### Usage and interpretation
Use UART0 (preferred during network stalls) or authenticated admin SSH:
```text
web diagnostics clear
web diagnostics enable
web diagnostics show
```
Enable before reproducing the failed third connection. At failure, run `web diagnostics show` promptly, alongside existing `memory`/`web counters`/browser Network timing evidence; repeat the snapshot if admission remains stalled. Then `web diagnostics disable` freezes event retention (live occupancy continues updating); `web diagnostics show` prints retained history, and `web diagnostics clear` erases history/counters without changing live sockets or enable state. The setting is RAM-only and defaults off after boot. Existing browser-shell policy deliberately denies these commands, even though shared completion offers the fixed forms. There is no HTTP diagnostic endpoint.
- Six connection records are maintained from boot even with capture disabled, so enabling on an already loaded server does not mislabel existing sockets. Each successful TLS connection gets a monotonically increasing, non-wrapping, firmware-lifetime 64-bit `conn` sequence independent of fd, cookie, ticket, broker or user identity. Clear, disable and HTTPS restart do not reset it. Internal TLS object identity is used only during synchronous cleanup; no pointer is exported or dereferenced by the console.
- Snapshot output gives its boot-relative `snapshot_us`, post-TLS occupancy split ordinary/serial-WS/admin-WS and each live fd/connection sequence/open time/age. `kind=0/1/2` means ordinary/serial/admin. Ordinary includes every successful TLS socket not yet observed as upgraded, including idle keep-alives, assets/login/settings and sockets not yet used for HTTP. Classification is actual public `httpd_ws_get_fd_info()` state after upgrade-handler return, **not inferred from `ESP_OK`**. No URI string is inspected.
- The fixed 32-entry overwrite ring records successful TLS open, TLS transport-context close, and enter/result for serial/admin ticket and upgrade handlers. `t_us` is boot-relative observation time; result `dt_us` measures the underlying handler only (including its work/IO, excluding entry resource sampling); close `dt_us` is successful-TLS connection lifetime. Open/enter durations are zero. Open/result occupancy includes the connection; close occupancy excludes it. Event IDs survive clear; `overwritten` counts evicted retained records since clear. `unmatched` counts duplicate create/unmatched close; `lost` counts untrackable creates (getter failure, metadata/sequence exhaustion). Nonzero anomaly counters mean occupancy is not trustworthy as complete evidence. Counters saturate.
- `rc` is the exact handler return, **not HTTP status or ticket issuance outcome**: sending a 401/403/503 can return `ESP_OK`. Match browser HTTP status and existing rejection/issuance counters; there is no ticket-value correlation, request ID, authentication identity, header/body/query logging, or allocation-failure attribution. Upgrade success is visible in the occupancy classification. Ring overwrite or clear/toggle during an operation can leave unpaired enter/result records; do not invent a duration for a missing result.
- Each retained event samples free/largest bytes for internal 8-bit, internal DMA and PSRAM 8-bit, plus the current HTTPD task's minimum-free stack **in ESP-IDF bytes**. Capability scans run outside the diagnostic lock. Samples are sequential, not an atomic heap snapshot; overlapping internal/DMA pools must not be added. Stack watermark includes diagnostic call overhead and is not dispatcher margin. `show` does not query a live task handle: resources are historical event samples, not fresh heap values at show time. Use `memory` for current system heap.
- Console snapshots copy only local diagnostic metadata under a short portMUX, never inspect HTTPD session internals or wait for its owner. Printing occurs outside the lock. At most 32 ID-qualified rows and six live records are printed, even during churn; concurrent clear/overwrite is reported as “no longer retained”. Capture epochs reject samples crossing enable/disable/clear, and sequence checks prevent a stale upgrade result from reclassifying a reused fd. No queued diagnostic work exists, hence no outstanding probe or stale queue lifetime to retire on restart. Minimal connection bookkeeping and upgrade classification remain active while disabled; heap scans and event recording do not. Instrumentation still has CPU/static RAM cost, not zero perturbation.
### SDK audit and explicit blind spots
Audited installed PlatformIO ESP-IDF **5.5.0**, `components/esp_https_server/src/https_server.c` (`httpd_ssl_open`, `httpd_ssl_close`), `components/esp_http_server/src/httpd_sess.c` (`httpd_sess_delete`) and `httpd_main.c`. HTTPS performs synchronous TLS creation before the configured `open_fn` and `user_cb` create callback. It installs a transport-context destructor; that destructor invokes the public close callback before deleting TLS/freeing the context. HTTPD's default close closes the fd, then clears contexts, then frees its session slot. The diagnostic close therefore marks a cleanup observation, not a FIN timestamp or causal close reason. It uses the stored fd, not a getter on an already-closed socket. **Neither `open_fn` nor `close_fn` is replaced**, preserving all existing HTTPS cleanup ownership and failure behavior. Callbacks execute synchronously under the existing HTTPD lifecycle; successful stop finishes cleanup before restart, while failed/partial stop retains remaining live metadata. No asynchronous fd-only events are consumed, avoiding event-delay/fd-reuse ambiguity.
This deliberately bounded first slice does **not** measure TCP connect/accept/listen backlog, pending clients when IDF stops accepting at capacity, handshake begin/duration/failure, TLS allocation failure or aggregate lwIP socket pressure. The public configured open hook is post-TLS too; adding it would not fix these blind spots. No owner-queued client-list probe is added. Occupancy is an owner-published **successful-TLS lower bound**, not the complete HTTPD session table while a handshake is in progress. A clean **6/6** snapshot supports established-connection saturation at that instant (three WS + three ordinary is directly distinguishable); fewer than six does not exonerate admission/TLS/global socket pressure. Correlate time with browser evidence and existing secret-free TLS errors; do not call this pre-TLS tracing or claim the root cause is proven. Existing capacities, receive/send/handshake timeouts and accepted admission issue remain unchanged.
### Diagnostic validation and target follow-up
Focused host harness compiles the entire production module plus the four actual server wrappers with deterministic public-API fakes. Twelve groups cover disabled bookkeeping, routes/return preservation/actual WS state, six-slot saturation, duplicate/stale/reused fd, clear/toggle publication races, full ring/exact overwrite, formatted-output secrecy, bounded show/clear interleaving, stale upgrade completion, partial stop/restart, invalid callbacks/commands and sequence/counter exhaustion; one additional source-guard group checks forbidden APIs and callback wiring. Real TLS/network scheduling, heap/stack values and target stalls are not simulated. Server lifecycle harness additionally checks callback configuration while preserving all 16 existing groups; canonical console lifecycle and policy verify CLI dispatch and unchanged browser restrictions.
Final implementer-run validation (all PASS):
| Command | Evidence |
|---|---|
| `python3 tests/web_diagnostics/run.py` | 12 runtime groups + 1 production integration/secrecy guard group |
| `python3 tests/web_admin_transport/run.py --tickets` | 25 transport groups + 12 ticket groups |
| `python3 tests/web_ui_session/run.py` | 68 browser behavior groups + production renderer/HTML/CSP checks |
| `python3 tests/web_session_store/run.py --serial` | Store lifecycle/races and serial/session binding/isolation |
| `pio run` | Final production source build **10.30 s**, **99,156 B RAM / 1,707,597 B flash** |
| `git --no-pager diff --check` | PASS |
Firmware delta versus recorded pre-instrumentation phase8D11 **96,076 B RAM / 1,703,685 B flash**: **+3,080 B RAM / +3,912 B flash**. `xtensa-esp32s3-elf-nm -S .pio/build/esp32-s3-devkitc-1-n16r8/src/web_diagnostics.c.o` verifies **2,816 B ring** (32 × 88), **192 B live table** (6 × 32) and **8 B portMUX**; remaining static metadata/alignment contributes to the build delta. `xtensa-esp32s3-elf-objdump -d` on that same target object gives local entry frames: handler **144 B**, record **144 B**, TLS callback **160 B**, show **528 B**, command **32 B**. These exclude callees and are **not** end-to-end stack margin proof. No task/stack-size increase or diagnostic dynamic allocation. Static RAM is paid even while disabled; this matters against the already low observed internal/DMA minima. Capability scans can perturb enabled timing and memory pressure correlation, so compare with disabled runs rather than treating this instrumentation as free. Initial full build also emitted installed SDK FATFS Kconfig boolean-default notes; all production builds succeeded.
Exact changed-file inventory for this diagnostic task (key implementation untouched):
- Docs: `docs/phase8d11_implementation.md`, `docs/agent/current-state.md`, `docs/agent/code-map.md`, `docs/agent/architecture.md`. No change to durable design decisions was needed beyond documenting the new observation module in the architecture/code map.
Target pending: enable before full-mix reproduction; capture both successful and failed third admission with browser status/timing, ordinary-vs-WS occupancy and memory evidence; check live counts on disconnect/stop/restart, disabled capture and clear; retain UART0/USB recovery and one broker writer throughout. Compare enabled/disabled timing and memory under the same load. No new hardware validation, reserve approval, timeout diagnosis or phase sign-off is claimed.
## Scope and contracts
- Accounts settings lists selected-account key types, stable slot indices and SHA256 fingerprints; imports OpenSSH Ed25519/P-256 public-key text and explicitly deletes one slot or clears all keys.
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.