277 lines
58 KiB
Markdown
277 lines
58 KiB
Markdown
# Phase 8D.0 — Baseline and M1 browser contract
|
||
|
||
Status: **8D.0 validated by user sign-off on 2026-09-05.** Documentation/source audit, baseline build and target runtime samples are recorded. Numeric reserve floors and future incremental budgets remain open engineering gates, not blockers to this user-approved baseline closure. The M1 browser contract below remains planned, not implemented authentication; 8D.1 target validation remains separate. See [execution plan](phase8d_plan.md) and [acceptance matrix](user_administration_tests.md#planned-phase-8d-integrated-web-administration).
|
||
|
||
## Validation sign-off
|
||
|
||
- The user explicitly marked **8D.0 validated** and identified the tested firmware with the latest checked-in project state, resolved at sign-off to Git revision **`d8999cd4a96e477fabd392ced02d810c3cd22d0f`**. This is user-confirmed source provenance, not an independently read-back device binary hash. The earlier reproducible build revision and SHA-256 table remain historical build evidence, not newly generated hashes for this revision.
|
||
- The user attributes the SSH I/O errors to testing at **out-of-spec 460400 baud**. Preserve that exact reported rate separately from the transcripts' **460800-baud UART configuration**; the differing rate may describe the test setup, and no firmware baud-support change or independently reproduced diagnosis is implied. The session-revocation counter remains recorded without a separately supplied causal explanation.
|
||
- This sign-off closes 8D.0 using the supplied evidence. Per-sample missing-provenance and unverified-check notes below describe the evidence available when collected; source-revision uncertainty is superseded by this sign-off. Unrecorded detailed checks remain regression coverage, not claims that the agent executed them or outstanding blockers to 8D.0 closure. Numeric memory/stack reserve approval and post-change 8D.1 validation are not implied.
|
||
|
||
## Reproducible build baseline
|
||
|
||
- Recorded 2026-09-05 on `devel`, revision `af89dd1bd96cdd97d8d57eee7a29f68e3874506b` (`Define staged Phase 8D delivery plan`). Working tree was clean before measurement. No experimental-branch inspection/import, firmware edits, asset regeneration, upload, erase, or device reconfiguration.
|
||
- `pio run` passed in 36.08 seconds (120-second timeout). Release environment `esp32-s3-devkitc-1-n16r8`, `platformio/espressif32@6.12.0`, ESP-IDF 5.5.0, Xtensa toolchain 14.2.0+20241119. Existing framework Kconfig invalid-bool-default notes appeared; they did not fail the build.
|
||
- N16R8: 16 MB QIO flash, 8 MB octal PSRAM at 80 MHz, custom partitions. Application flash budget is 4,194,304 bytes, not the full chip. Wi-Fi/lwIP allocations prefer external RAM; the configured mbedTLS allocator is PSRAM-only, without internal fallback. Software crypto configuration is retained.
|
||
- `pio device list` completed with no ports listed. No target/browser connection was available to this task. Historical target reports are not fresh evidence for this revision.
|
||
|
||
| Measurement | This build | Historical mDNS reference | Difference |
|
||
|---|---:|---:|---:|
|
||
| Linked static RAM | 94,532 B | 94,532 B | 0 B |
|
||
| Program flash reported by PlatformIO | 1,599,973 B | 1,599,765 B | +208 B |
|
||
|
||
The flash difference predates this documentation-only change; its cause was not diagnosed. Linked RAM is not runtime heap headroom. No runtime delta can be inferred from these figures.
|
||
|
||
SHA-256 identification (configuration and generated binary, not secrets):
|
||
|
||
| File | SHA-256 |
|
||
|---|---|
|
||
| `platformio.ini` | `65ca5103c7aaf36b685a05371a856294ba6f7f5f53209c68d61a828d3beb78e5` |
|
||
| `sdkconfig.defaults` | `af8fb8a9866888a12219a9d4ff5148d9f45f17c40067dce90fbc55c0b75986e5` |
|
||
| `sdkconfig.esp32-s3-devkitc-1-n16r8` | `c9c8c08b18027e959ba8131686f0e42573e507574079be3cf8fb72a9e1955905` |
|
||
| `partitions.csv` | `9107a2aab52f02633aea72683ffede979d9db2050e95ba4788cd0f8580c7f0ff` |
|
||
| `.pio/build/esp32-s3-devkitc-1-n16r8/firmware.bin` | `c5f10b2137d2cbffad59cda025274dd82aad1a42cdea2144d31424fd918b4e6d` |
|
||
|
||
## Source-verified current behavior and capacities
|
||
|
||
Authoritative paths: `src/web_server.c`, `src/web_serial_transport.{h,c}`, `src/web_ui.c`, `src/user_database.h`, `src/system_console.c`, `src/session_broker.{h,c}`, `src/ssh_transport.{h,c}`, `src/admin_ssh_console.c`. Configuration-dependent defaults below were also checked against installed IDF 5.5 HTTPD/HTTPS headers and implementation and targeted generated sdkconfig entries.
|
||
|
||
- Both roles currently use Basic authentication for the same serial/status UI; there is no login page, cookie session, logout, or browser admin route. All five app assets require authentication. Basic header buffers are wiped after verification. The four-entry keyed-digest cache has a 300-second sliding TTL and rechecks principal currentness; cache expiry is not browser logout.
|
||
- Ticket issuance accepts only an empty POST body. Serial upgrade consumes a one-use, principal-bound ticket; supplied Origin must match `https://<Host>`, but missing Origin is currently accepted. Four tickets, 30-second lifetime, 24 random bytes encoded to 32 characters, digest-only storage. Full ticket storage currently evicts the earliest-expiring entry.
|
||
- Serial admission opportunistically requests writer ownership. Explicit browser Disconnect closes the socket and pauses reconnect; Connect resumes it. Binary data and existing `request-writer`/`release-writer` messages remain unchanged in M1. Status polling runs every five seconds. Current fetch failures do not distinguish expired login from transport failure; cutover must change this.
|
||
- Principals copy account ID, authentication generation, role, method and username. Database limits are eight accounts, username capacity 16 bytes, password capacity 64 bytes. Mutation/recreation invalidates principal currentness; transport notification supplements, not replaces, that authority.
|
||
|
||
### Allocation and execution inventory
|
||
|
||
These are **source array capacities/configured stacks**, not measured allocator costs or complete `sizeof` totals. Do not add stack-local scratch to the stack allocation again.
|
||
|
||
| Resource | Existing bounded allocation or limit |
|
||
|---|---|
|
||
| HTTPS owner | One dynamic internal 10,240 B stack; serialized request handlers; dynamically allocated server/socket/route/TLS state |
|
||
| HTTP request/response scratch | Status JSON 3,072 B on stack; Basic header 115 B, decoded credentials 81 B, digest 32 B on stack; startup certificate/key scratch 1,024 + 256 B |
|
||
| HTTP parser/response headers | Request headers 1,024 B, URI 512 B, eight additional response-header slots; implementation must count cookie + security headers before cutover |
|
||
| TLS records | Configured input/output content limits 16,384/4,096 B per TLS connection; external allocation, dynamic resizing disabled; contexts, certificates, TCP and allocator overhead additional; HTTPS TLS resumption tickets disabled |
|
||
| Web serial | Two static slots, each 1,024 B RX + 512 B TX (3,072 B payload total), principals and metadata; four ticket records; one static 6,144 B stack + TCB; at most one pending HTTPD TX work item per slot |
|
||
| Broker | Eight preallocated clients, 4,096 B usable output/client (32,776 B backing storage including sentinel bytes); 16 events/client, additional `8 × 16 × sizeof(session_broker_event_t)` storage; payloads PSRAM-preferred with internal fallback, controls internal; one dynamic 4,096 B stack |
|
||
| SSH | Two total slots shared by serial/admin/handshakes, 512 B RX + 512 B TX each (2,048 B payload total), plus snapshots/metadata; one dynamic 20,480 B owner stack pinned to core 1; wolfSSH allocations PSRAM-preferred with internal fallback |
|
||
| Shared administration | Two static remote states with 5,899 B explicit byte arrays each (including 4,096 B output and four history entries); additional 1,024 B SSH and 1,024 B UART completion formatters; command queue four, deferred-control queue two; 256-character commands, ten parsed arguments |
|
||
| Admin tasks | Dynamic dispatcher 12,288 B, deferred control 4,096 B, UART frontend 6,144 B; no task per remote session |
|
||
| Serial service | 16 KiB RX + 8 KiB TX usable stream payloads PSRAM-preferred with internal fallback; UART driver/control/task storage additional |
|
||
|
||
The seven listed HTTPD/web/broker/SSH/admin task stacks total **63,488 B**; this is not a whole-firmware task inventory. Other existing tasks, including Wi-Fi, USB, serial, UI, mDNS and system tasks, contribute to the measured baseline. No new task is allowed for M1. Internal fallback is a worst-case reserve cost, not free PSRAM capacity. Exact padded structure sizes, per-TLS/SSH handshake peaks and fragmentation remain unmeasured.
|
||
|
||
### Supported concurrency and socket accounting
|
||
|
||
Configured capacity is not evidence that every combination has passed this revision's target tests.
|
||
|
||
| Resource | Baseline ceiling / intended workload |
|
||
|---|---|
|
||
| Broker clients | Eight, exactly one writer; normal physical-transport maximum is USB + two web serial + two user SSH = five |
|
||
| Web serial | Two simultaneously connected serial WebSockets, independently of four outstanding tickets |
|
||
| SSH | Two connections total; baseline full mixed workload uses one user and one admin |
|
||
| HTTPD routes | Nine registered / nine slots: `/`, `/api/status`, `/api/ws-ticket`, `/ws/serial`, five `/assets/` routes; no spare route slot |
|
||
| HTTPS connections | Six accepted clients total, including WebSockets; two serial sockets leave four HTTP/TLS slots, not four parallel handlers |
|
||
| HTTPD infrastructure | Three more descriptors: listen, UDP control receive, UDP control send; full HTTPS therefore uses nine |
|
||
| SSH descriptors | Listener + two sessions = three; combined HTTPS/SSH subtotal twelve of sixteen lwIP sockets |
|
||
| Remaining socket allowance | Four before other services (including mDNS), network diagnostics and transient accept/reject costs; not four guaranteed spare connections |
|
||
|
||
HTTPS has LRU purge **enabled**, one-second send/receive waits and five-second handshake timeout; SSH has a two-connection listen backlog and 15-second handshake deadline. Before M2, admission must protect retained serial sockets from LRU eviction; do not claim current settings already guarantee that protection. M2 must budget two browser sockets (serial + admin) per dual-mode browser: two such browsers would occupy four of the six HTTPS slots, leaving only two for ordinary HTTPS. This is arithmetic, **not approval of a future admin capacity**. Do not raise lwIP/HTTPD limits or reduce existing serial/SSH capacities to conceal pressure.
|
||
|
||
## Minimal M1 browser contract (planned)
|
||
|
||
Policy constants below are bounded functional choices, **not approved memory reserve floors**. No settings API, admin shell, generic command endpoint, new permanent task, Basic compatibility path, stored browser passwords/localStorage credentials/remember-me tokens, or generated-asset changes are included.
|
||
|
||
### Routes and small wire schema
|
||
|
||
All API JSON is UTF-8, length-bounded and safely encoded. Login accepts only `application/json`; no form/query credentials, duplicate/unknown fields, embedded NULs, oversized bodies, or unbounded parser allocations. Maximum login body: **512 bytes**, decoded username/password limits **16/64 bytes**, using existing database validation. Reject excessive bodies before buffering. New authentication JSON responses have a **512-byte serialized ceiling**; the existing status schema/buffer is unchanged. `X-CSRF-Token` is the only CSRF request header.
|
||
|
||
| Route | Access and contract |
|
||
|---|---|
|
||
| `GET /login` | Public minimal standalone login document; inline bounded CSS/script with matching CSP hash, no dependency on protected app assets. No automatic login or credential verification. |
|
||
| `GET /api/login-challenge` | Public same-origin pre-login bootstrap, empty body; returns `{ "csrf": string, "expires_in": integer }` (remaining whole seconds, at most 120) and pre-login cookie described below. |
|
||
| `POST /api/login` | Strict Origin + pre-login cookie + CSRF header; body `{ "username": string, "password": string }`; success `200 { "authenticated": true }` and new session cookie. Browser navigates to `/`. |
|
||
| `GET /api/session` | Session required; returns `{ "username": string, "role": "user" or "admin", "csrf": string, "expires_in": integer }`, remaining whole seconds. No raw session token or account/verifier internals. |
|
||
| `POST /api/logout` | Session + strict Origin + CSRF; empty body. Invalidate current session before `204`, expire cookie, browser navigates to `/login`. Never a state-changing GET. |
|
||
| `GET /`, five existing `/assets/` routes | Session protected. Unauthenticated document navigation to `/` receives `303 /login`; assets/API receive `401`, never login HTML masquerading as JS/JSON. |
|
||
| `GET /api/status` | Session required; existing status for either role. |
|
||
| `POST /api/ws-ticket` | Session + strict Origin + CSRF; empty body; preserve existing ticket response fields/protocol, bind ticket to originating session as well as principal. |
|
||
| `GET /ws/serial` upgrade | Session cookie + strict Origin + one-use matching-session ticket; no Basic fallback. Currentness and expiry checked before attach. |
|
||
|
||
Five new handlers mean **14 route slots total** at cutover if using these exact routes. This is a separately accounted bounded route-table increase, not a socket increase. Preflight/CORS access is unsupported. Other methods must not bypass policy. API failures use a bounded `{ "error": code }` without echoing submitted input; existing non-auth errors may remain plain text and the browser must tolerate both.
|
||
|
||
### Sessions, CSRF bootstrap and admission
|
||
|
||
- **Four authenticated sessions globally**, independent of two serial sockets and four outstanding serial tickets. New login issues 32 random bytes as 64 lower-case hex characters; store only a SHA-256 token digest with copied principal, origin binding, CSRF state, monotonic deadline and generation-safe session identity. Use `secure_random`; fail closed on RNG/init failure. Keep synchronization explicit for HTTPD lookups versus console invalidation; no cross-subsystem callbacks while holding session storage locks.
|
||
- Cookie: `__Host-sak-session=<token>; Secure; HttpOnly; SameSite=Strict; Path=/; Max-Age=3600`, no Domain. **Absolute lifetime one hour; idle expiry disabled in M1.** Polling, traffic and refresh never extend it. No sliding renewal or refresh token; reauthentication after expiry issues a new identity. This deliberately simple policy will interrupt a long-running serial session at expiry and must be visible to the user. Reboot/web-server stop clears sessions and tickets. Browser cookie lifetime is not server authority.
|
||
- CSRF state: independent 32 random bytes encoded as 64 hex characters, retained only in bounded session state and browser memory. Return via `/api/session` on page load; never localStorage, URL or logs. It does not substitute for the HttpOnly session cookie. Refresh does not rotate it or invalidate another tab.
|
||
- **Four independent pre-login challenges**, 120-second absolute lifetime. Bootstrap issues `__Host-sak-prelogin` with the same cookie attributes and `Max-Age=120`, backed by token digest, origin binding, separate random CSRF value and deadline. `/login` itself allocates nothing. Bootstrap reuses an unexpired matching challenge for that browser without reissuing the cookie or extending its lifetime, returning remaining seconds; otherwise reclaim expired slots and reject at capacity. Require a same-origin custom header `X-Login-Bootstrap: 1` on the bootstrap fetch; reject cross-site Fetch Metadata when present, and validate any supplied Origin. No CORS headers: another origin cannot read a challenge or make that custom-header request. This handles same-origin GETs where browsers omit Origin without weakening POST checks.
|
||
- Login consumes a valid challenge **on every credential attempt**, including wrong passwords; clear its cookie, wipe request/password scratch on all exits, and fetch a new challenge for a user-initiated retry. Concurrent tabs share the cookie: a stale form gets a recoverable challenge-expired error, not a login loop. Successful login always generates a fresh authenticated token (no fixation). Already authenticated browsers receive `409 already_authenticated` instead of silently replacing an account/session; account switching requires logout.
|
||
- Reclaim expired/stale records first; full live session/challenge/ticket tables return `503 capacity` with `Retry-After: 5`, with **no live-session/ticket eviction**. Thus ticket overflow intentionally changes from baseline earliest-expiry eviction to explicit rejection at cutover. A session never reserves a serial socket; full serial capacity can reject Connect without discarding login or disturbing an existing writer.
|
||
- Login throttle: one global bounded monotonic bucket allowing **five credential-verification attempts per 60-second window**, successes included; further attempts get `429 throttled` with seconds until window reset in `Retry-After`. Invalid CSRF/Origin never reaches password verification. No per-username/IP table, sleep in HTTPD, secret retention or persistent lockout. Tradeoff: a hostile client can exhaust shared login capacity; UART0/USB and established sessions remain usable. Do not claim comprehensive DoS resistance; measure PBKDF2 blocking/TLS pressure on target.
|
||
|
||
### Origin, expiry and revocation rules
|
||
|
||
- Every mutation (login, logout, ticket issuance) and WS upgrade requires a single non-null HTTPS Origin equal to the request's validated Host authority after lower-casing host and normalizing default port 443. Reject absent/malformed/oversized Origin, userinfo, paths, query/fragment, comma lists, invalid Host or non-443 port. Use bounded host/origin buffers; do not trust forwarded headers. A Referer is not a substitute. Bind challenge/session/ticket to that canonical origin. No arbitrary return URLs.
|
||
- Direct IP and `sak-<suffix>.local` are separate host-only cookie origins, even on one device. Login/logout on one does not affect the other's unchanged session; no alias cookie sharing. Both require trusted/accepted device TLS in the browser. Hostname changes do not migrate cookies.
|
||
- Lookup/admission, ticket mint/consume and sensitive WS input recheck session liveness plus principal currentness. Extend existing owner-task periodic checks to enforce expiry/revocation for idle sockets, with a planned **at most one-second detection-latency acceptance target**, no new task. This bound is not established by the current loop; implementation and target validation must include lock contention and concurrent load. Mark invalid immediately and request generation-safe close through HTTPD ownership; network delivery/close completion can take longer. Failed notification must not allow more serial input or revive an expired session.
|
||
- Logout invalidates only the originating session and its tickets/serial sockets (and later admin sockets); other sessions for the same unchanged account survive. Logout in one tab affects all tabs sharing that cookie. Account password/role/key changes, deletion/recreation and explicit account revocation invalidate all that account's sessions/tickets/sockets across origins, not unrelated accounts. Preserve distinct account, web-session and transport generations. Wipe retired records; late cleanup cannot close reused slots.
|
||
- All login/session/auth errors, protected documents, API and ticket responses use `Cache-Control: no-store`; preserve nosniff, no-referrer, frame denial and restrictive CSP. Existing immutable vendored asset policy can remain; no auth-dependent data in those assets. Count response headers against HTTPD's eight-slot default, including Set-Cookie (two on login); adjust only if the exact count requires it. Never log cookies, CSRF, ticket query strings, passwords, verifier or private-key material.
|
||
|
||
### Browser recovery behavior
|
||
|
||
- `401`: stop polling/reconnect work, close local socket, clear in-memory CSRF, navigate once to `/login`. On login submission, `401 invalid_credentials` stays on the form with a generic usable error; it must not reveal account existence. No `WWW-Authenticate`, cached Basic header authorization or browser credential-reset instructions after cutover.
|
||
- `403`: report CSRF/origin failure without retrying a mutation automatically; allow challenge/session reload and explicit retry. `400`/`413`/`415`: show input/request error. `409`: offer return to app/logout. `429`/`503`: display bounded retry information and do not retry credentials automatically.
|
||
- Transport/network error: show connection failure without claiming logout succeeded. A lost logout response can follow successful invalidation: check session status when reachable; `401` confirms login is needed. Do not rely solely on client cookie deletion (HttpOnly).
|
||
- Refresh/back navigation validates `/api/session` before ticket/reconnect; expired cookies, reboot and previously Basic-authenticated profiles must all land on usable login. Explicit Disconnect still pauses reconnect while leaving login valid. Capacity and network failure must not look like bad credentials. Cancel pending work on logout/page exit and ignore late responses via generation checks.
|
||
|
||
## Runtime measurements, reserve gates and exact target checklist
|
||
|
||
**The user-provided settled-boot and concurrent-session samples below form the 8D.0 runtime memory baseline. Other workload measurements remain pending, not zero.** `memory` reports free/minimum-free/largest-block for internal 8-bit, internal DMA and PSRAM. Record all nine values at every point. DMA overlaps internal heap: do not sum them. Minimum-free is the conservative sum of per-region lifetime minima, not a synchronized low-water sample. Only SSH currently exposes stack minimum-free via `ssh status`; HTTPD/web/broker/admin/UART task margins lack instrumentation and remain pending (no telemetry changes in 8D.0).
|
||
|
||
| Workload point | Internal free/min/largest | DMA free/min/largest | PSRAM free/min/largest | SSH stack minimum-free |
|
||
|---|---|---|---|---|
|
||
| **Settled clean boot, ~60 seconds, Wi-Fi STA connected, mDNS running (8D.0, user-provided)** | **68,036 / 67,192 / 31,744 B** | **60,280 / 59,436 / 31,744 B** | **8,198,508 / 8,188,800 / 8,126,464 B** | **18,464 B** |
|
||
| **Basic-authenticated browser/status, browser serial explicitly disconnected, UART service running; fresh settled boot (user-provided)** | **65,340 / 60,088 / 31,744 B** | **57,584 / 52,332 / 31,744 B** | **8,212,812 / 8,158,868 / 8,126,464 B** | **18,464 B** |
|
||
| **Basic-authenticated browser/status, browser serial explicitly disconnected, UART service stopped (user-provided)** | **69,988 / 60,280 / 31,744 B** | **62,232 / 52,524 / 31,744 B** | **8,223,088 / 8,158,832 / 8,126,464 B** | **18,464 B** |
|
||
| **Browser/status sample: one active browser serial writer, no SSH sessions (user-provided; disconnected label unconfirmed)** | **60,980 / 55,880 / 31,744 B** | **53,224 / 48,124 / 31,744 B** | **8,163,616 / 8,109,644 / 7,995,392 B** | **18,464 B** |
|
||
| **Updated baseline: one browser serial + user SSH + admin SSH (user-provided)** | **44,552 / 17,812 / 31,744 B** | **36,796 / 10,056 / 31,744 B** | **8,187,416 / 8,176,688 / 8,126,464 B** | **16,288 B** |
|
||
| **Baseline follow-up: two browser serial + USB + user SSH + admin SSH (user-provided)** | **42,056 / 17,812 / 29,696 B** | **34,300 / 10,056 / 29,696 B** | **8,162,828 / 8,126,736 / 7,995,392 B** | **16,288 B** |
|
||
| **After five browser serial disconnect/reconnect cycles, ending connected as user writer (user-provided)** | **60,776 / 59,792 / 31,744 B** | **53,020 / 52,036 / 31,744 B** | **8,163,656 / 8,153,968 / 8,126,464 B** | **18,464 B** |
|
||
| **After 15-minute concurrent soak, clients disconnected, then 60-second settled cleanup; UART running (user-provided)** | **65,040 / 17,812 / 31,744 B** | **57,284 / 10,056 / 31,744 B** | **8,212,596 / 8,126,736 / 8,126,464 B** | **16,288 B** |
|
||
| Cookie login/logout and browser admin connected | Not implemented | Not implemented | Not implemented | Not implemented |
|
||
|
||
### Settled clean-boot sample (8D.0 firmware)
|
||
|
||
The user supplied this transcript on 2026-09-05 after 8D.1 work had begun, explicitly identifying the measured firmware as based on **8D.0**, not 8D.1. Conditions reported: clean boot, settled after approximately 60 seconds, Wi-Fi STA connected and mDNS running. Commands were `memory`, `ssh sessions`, `broker clients`, `ssh counters`, `web counters`, `web status`, then `mdns status`; these are sequential user-provided snapshots, not an atomic sample or an agent-executed test.
|
||
|
||
- SSH sessions **0/2**; no broker clients connected. HTTPS initialized/running, not transitioning, port 443, `last-error=ESP_OK`; HTTP Basic via the user database, four users and two admins. Web serial attached, sessions **0/2**, zero tickets. Endpoints reported: `GET /`, `GET /api/status`, `POST /api/ws-ticket`, `WSS /ws/serial`.
|
||
- mDNS initialized and announced, hostname `sak-1024.local`, suffix `1024`, `last-error=ESP_OK`. This records announcement status, not an independently verified hostname-resolution test.
|
||
- SSH counters: lifecycle starts **1**, start-failures/stops/TCP connections/capacity rejects **0**. All handshake, authentication, request-rejection, broker, admin-console and stream counters **0**, including failures, backpressure and revocations.
|
||
- Web counters: lifecycle starts **1**, start-failures/stops **0**. Requests total **29**, authenticated **29**, status **29**; auth-failures/root/tickets/assets/response-errors **0**. All ticket, WebSocket session, RX, TX, writer-control and failure counters **0**, including service-start/broker failures and closes.
|
||
- The 29 authenticated status requests mean this is a settled boot with HTTPS status activity, not a no-HTTP-traffic idle sample. Browser/version, request origin and polling source were not supplied; do not infer an additional controlled browser-only workload measurement from these counters.
|
||
- Follow-up user-provided `ssh status` for settled boot: initialized/running, not transitioning, port **22**, `last-error=ESP_OK`, sessions **0/2**. Authentication reports role-based password and SSH public key via the user database; admission reports shell/PTY only, with exec, subsystem, forwarding, SCP and SFTP disabled. Owner task on core **1**, configured stack **20,480 B**, stack minimum-free **18,464 B**. This is a settled-boot stack low-water measurement, not a loaded-workload margin or approved reserve floor.
|
||
- Exact flashed revision/configuration or binary hash was not supplied. The user's 8D.0 firmware attribution is retained without independently tying this device to the build hash above. This sample does not validate 8D.1, establish reserve floors or measure post-load cleanup; do not treat comparisons with the earlier concurrent samples as controlled per-client allocation deltas.
|
||
|
||
### Browser/status sample with active serial WebSocket
|
||
|
||
The user supplied this additional baseline transcript on 2026-09-05 labelled "Basic-authenticated browser/status, serial explicitly disconnected." However, the supplied `web status` reports **one active serial writer**, and counters report one connection with zero disconnects/closes. Preserve the measurements separately; the later disconnected sample below also stops the UART service. Commands were `memory`, `ssh sessions`, `ssh counters`, `web counters`, `web status`, `mdns status`, then `ssh status`, so the snapshots are not atomic.
|
||
|
||
- HTTPS initialized/running, not transitioning, port **443**, `last-error=ESP_OK`; HTTP Basic via the user database, four users and two admins, unchanged endpoint list. Web serial attached, sessions **1/2**, zero tickets; slot **0**, fd **56**, generation **1**, role-`user` password authentication, broker **8 writer**, no TX pending or closing state. No `broker clients` snapshot was supplied, so other broker-client presence is not established.
|
||
- Web lifecycle: starts **1**, start-failures/stops **0**. Requests total/authenticated **28**, root **1**, status **26**, tickets **1**, assets **0**, auth-failures/response-errors **0**. Tickets issued/consumed **1**, rejected/expired **0**. WebSocket connects **1**, disconnects **0**, connect/service-start/broker failures **0**.
|
||
- WebSocket RX: **14** accepted frames / **14 B**, no rejected frames/bytes. TX: **18** binary frames / **753 B**, **2** control frames / **148 B**. Writer requests/grants **1**, denials/releases/revocations **0**; send/queue/protocol failures and closes **0**.
|
||
- SSH initialized/running, not transitioning, port **22**, `last-error=ESP_OK`, sessions **0/2**; role-based password/public-key authentication and shell/PTY-only admission, with exec/subsystem/forwarding/SCP/SFTP disabled. Owner task core **1**, stack **20,480 B**, minimum-free **18,464 B**. SSH lifecycle starts **1**; all other supplied SSH counters **0**.
|
||
- mDNS initialized/announced, hostname `sak-1024.local`, suffix `1024`, `last-error=ESP_OK`.
|
||
- This is user-provided baseline evidence, not an 8D.1 target-validation claim. Exact flashed revision/hash, browser/version/origin, settling duration and serial fixture were not supplied. Web request totals are lower than in the settled-boot transcript; same-boot continuity is not established, and no controlled heap delta is inferred. Lifetime minima include prior activity, including the reported serial traffic.
|
||
|
||
### Browser disconnected, UART service stopped
|
||
|
||
The user supplied this follow-up 8D.0 baseline transcript on 2026-09-05, reporting that the browser automatically connected serial after login, then was explicitly disconnected and the serial service stopped. Commands were `memory`, `ssh sessions`, `ssh counters`, `web counters`, `web status`, `mdns status`, `ssh status`, then `serial status`; these are sequential observations, not an atomic sample or an agent-executed test.
|
||
|
||
- HTTPS initialized/running, not transitioning, port **443**, `last-error=ESP_OK`; HTTP Basic via the user database, four users and two admins, unchanged endpoints. Web serial attached, sessions **0/2**, zero tickets. This confirms browser serial disconnection; no `broker clients` snapshot was supplied to establish other broker-client presence.
|
||
- Web lifecycle starts **1**, start-failures/stops **0**. Requests total/authenticated **26**, root **1**, status **24**, tickets **1**, assets **0**, auth-failures/response-errors **0**. Tickets issued/consumed **1**, rejected/expired **0**. WebSocket connects/disconnects **1** each; connect/service-start/broker failures **0**.
|
||
- WebSocket RX accepted/rejected frames and bytes **0**; TX binary frames/bytes **0**, control frames **2** / **148 B**. Writer requests/grants **1**, denials/releases/revocations **0**. Send/queue/protocol failures and closes **0**; the zero closes counter is preserved separately from the reported disconnect count.
|
||
- SSH initialized/running, not transitioning, port **22**, `last-error=ESP_OK`, sessions **0/2**; role-based password/public-key authentication and shell/PTY-only admission, with exec/subsystem/forwarding/SCP/SFTP disabled. Owner task core **1**, stack **20,480 B**, minimum-free **18,464 B**. SSH lifecycle starts **1**, all other supplied SSH counters **0**.
|
||
- mDNS initialized/announced, hostname `sak-1024.local`, suffix `1024`, `last-error=ESP_OK`.
|
||
- UART service **stopped**, RS-232 owner **idle**. Configuration v1: **460800 baud, 8N1, no flow control**, DTR inactive, RTS threshold **96**; RX available/TX pending **0**. Phase 0 hardware commands reported available while stopped; none are claimed executed.
|
||
- Stopping the UART service changes the allocation workload, so this sample is not a substitute for browser-disconnected memory with the service running. That separate workload is recorded in the fresh-boot sample below. Prior automatic connection/startup can contribute to lifetime minima. Exact build hash, browser/version/origin, settling duration and same-boot continuity with earlier samples remain unspecified; do not infer controlled allocation savings, reserve floors or 8D.1 validation.
|
||
|
||
### Browser disconnected, UART service running
|
||
|
||
The user supplied this follow-up 8D.0 baseline transcript on 2026-09-05 after a **fresh, settled boot**, with the UART service running and browser serial explicitly disconnected after login. It fills the intended Basic-authenticated browser/status workload row. Commands were `memory`, `ssh sessions`, `ssh counters`, `web counters`, `web status`, `mdns status`, `ssh status`, then `serial status`; these are sequential user observations, not an atomic sample or an agent-executed test. Exact settling duration was not supplied.
|
||
|
||
- HTTPS initialized/running, not transitioning, port **443**, `last-error=ESP_OK`; HTTP Basic via the user database, four users and two admins, unchanged endpoints. Web serial attached, sessions **0/2**, zero tickets. No `broker clients` snapshot was supplied to establish other broker-client presence.
|
||
- Web lifecycle starts **1**, start-failures/stops **0**. Requests total/authenticated **31**, root **1**, status **28**, tickets **1**, assets **1**, auth-failures/response-errors **0**. Tickets issued/consumed **1**, rejected/expired **0**. WebSocket connects/disconnects **1** each; connect/service-start/broker failures **0**.
|
||
- WebSocket RX accepted/rejected frames and bytes **0**; TX binary frames/bytes **0**, control frames **2** / **148 B**. Writer requests/grants **1**, denials/releases/revocations **0**. Send/queue/protocol failures and closes **0**; closes and disconnects are distinct reported counters.
|
||
- SSH initialized/running, not transitioning, port **22**, `last-error=ESP_OK`, sessions **0/2**; role-based password/public-key authentication and shell/PTY-only admission, with exec/subsystem/forwarding/SCP/SFTP disabled. Owner task core **1**, stack **20,480 B**, minimum-free **18,464 B**. SSH lifecycle starts **1**, all other supplied SSH counters **0**.
|
||
- mDNS initialized/announced, hostname `sak-1024.local`, suffix `1024`, `last-error=ESP_OK`.
|
||
- UART service **running**, RS-232 owner **serial service**. Configuration v1: **460800 baud, 8N1, no flow control**, DTR inactive, RTS threshold **96**; RX available/TX pending **0**. Modem asserted: **DCD=0, DSR=1, CTS=1, RI=0**, valid-voltage **VLD=1**.
|
||
- Lifetime minima include startup and the browser's initial automatic serial connection, not just the disconnected state. This fresh boot is separate from previous samples; no controlled per-connection or service-stop allocation delta is inferred. Exact flashed revision/hash and browser/version/origin remain unspecified. This fills the workload measurement, not reserve-floor approval, serial data-integrity testing or 8D.1 target validation.
|
||
|
||
### After five browser serial disconnect/reconnect cycles
|
||
|
||
The user supplied this 8D.0 baseline transcript on 2026-09-05, reporting **five browser serial disconnect/reconnect cycles**. Commands were `memory`, `ssh sessions`, `ssh counters`, `web counters`, `web status`, `mdns status`, `ssh status`, then `serial status`; these are sequential user observations, not an atomic sample or an agent-executed test. The endpoint is **connected**, not settled disconnected cleanup.
|
||
|
||
- HTTPS initialized/running, not transitioning, port **443**, `last-error=ESP_OK`; HTTP Basic via the user database, four users and two admins, unchanged endpoints. Web serial attached, sessions **1/2**, zero tickets. Slot **0**, fd **54**, generation **8**, role-`user` password authentication, broker **64 writer**, no TX pending or closing state. No `broker clients` snapshot was supplied to establish other clients or independently check old-client cleanup.
|
||
- Web lifecycle starts **1**, start-failures/stops **0**. Requests total/authenticated **152**, root **3**, status **140**, tickets **8**, assets **1**, auth-failures/response-errors **0**. Tickets issued/consumed **8**, rejected/expired **0**. WebSocket connects **8**, disconnects **7**, connect/service-start/broker failures **0**. These cumulative counts include more activity than the reported five test cycles; no per-cycle snapshots or counter-reset boundary were supplied.
|
||
- WebSocket RX **11** accepted frames / **33 B**, rejected frames/bytes **0**. TX **43** binary frames / **8,413 B**, **16** control frames / **1,218 B**. Writer requests/grants **8**, denials/releases/revocations **0**. Send/queue/protocol failures and closes **0**; the zero closes counter is retained separately from seven disconnects.
|
||
- SSH initialized/running, not transitioning, port **22**, `last-error=ESP_OK`, sessions **0/2**; role-based password/public-key authentication and shell/PTY-only admission, with exec/subsystem/forwarding/SCP/SFTP disabled. Owner task core **1**, stack **20,480 B**, minimum-free **18,464 B**. SSH lifecycle starts **1**, all other supplied SSH counters **0**.
|
||
- mDNS initialized/announced, hostname `sak-1024.local`, suffix `1024`, `last-error=ESP_OK`. UART service **running**, RS-232 owner **serial service**; configuration v1 **460800 baud, 8N1, no flow control**, DTR inactive, RTS threshold **96**; RX available/TX pending **0**. Modem asserted **DCD=0, DSR=1, CTS=1, RI=0**, valid-voltage **VLD=1**.
|
||
- This records the reported five-cycle endpoint, successful cumulative admissions/writer grants and traffic without reported web failures. It does not prove byte integrity, paused reconnect behavior, observer isolation, per-cycle cleanup or a leak/fragmentation trend. Only the final normal-user role is shown; five cycles per role, including admin-role browser coverage, remain unverified. Exact flashed hash, browser/version/origin, timing and same-boot continuity with prior samples were not supplied. No reserve-floor approval or 8D.1 target validation is inferred.
|
||
|
||
### Updated one-browser plus user/admin SSH baseline
|
||
|
||
The user supplied this updated 8D.0 baseline transcript on 2026-09-05 for **one browser serial + user SSH + admin SSH**. Commands were `memory`, `ssh sessions`, `ssh counters`, `web counters`, `web status`, `mdns status`, `ssh status`, then `serial status`; these are sequential user observations, not an atomic sample or an agent-executed test. The superseded original measurement was removed at the user's request.
|
||
|
||
- HTTPS initialized/running, not transitioning, port **443**, `last-error=ESP_OK`; HTTP Basic via the user database, four users and two admins, unchanged endpoints. Web serial attached, sessions **1/2**, zero tickets. Slot **0**, fd **53**, generation **1**, role-`user` password authentication, broker **8 writer**, no TX pending or closing state.
|
||
- SSH initialized/running, not transitioning, port **22**, `last-error=ESP_OK`, sessions **2/2**, both public-key authenticated. Admin session **5**, slot **0**, routes to the admin console with no broker client; user session **6**, slot **1**, routes to broker **9 observer**. Both report active/authenticated, admin-command idle, zero admin output, no RX/TX pending or closing state. Authentication/admission remains role-based password/public-key, shell/PTY only, with exec/subsystem/forwarding/SCP/SFTP disabled. Owner task core **1**, stack **20,480 B**, minimum-free **16,288 B**.
|
||
- SSH lifecycle starts **1**, TCP connections **2**, start-failures/stops/capacity rejects **0**. Handshake successes/auth attempts **2**, handshake failures/timeouts/auth failures/request rejects **0**. Broker connects **1**, writer requests **1**, grants **0**, denials **1**, failures/disconnects/revocations **0**. Admin admissions **1**, admission failures/input backpressure **0**. Stream RX/accepted/rejected **0**, TX **657 B**, I/O failures/session revocations **0**.
|
||
- Web lifecycle starts **1**, start-failures/stops **0**. Requests total/authenticated **49**, status **48**, tickets **1**, root/assets/auth-failures/response-errors **0**. Tickets issued/consumed **1**, rejected/expired **0**. WebSocket connects **1**, disconnects/connect failures/service-start failures/broker failures **0**.
|
||
- WebSocket RX **19** accepted frames / **19 B**, rejected frames/bytes **0**. TX **39** binary frames / **7,681 B**, **3** control frames / **237 B**. Writer requests/grants **1**, denials/releases/revocations **0**; send/queue/protocol failures and closes **0**. The SSH writer denial is consistent with the browser retaining writer ownership.
|
||
- mDNS initialized/announced, hostname `sak-1024.local`, suffix `1024`, `last-error=ESP_OK`. UART service **running**, RS-232 owner **serial service**; configuration v1 **460800 baud, 8N1, no flow control**, DTR inactive, RTS threshold **96**; RX available/TX pending **0**. Modem asserted **DCD=0, DSR=1, CTS=1, RI=0**, valid-voltage **VLD=1**.
|
||
- No `broker clients` snapshot was supplied, so the transport snapshots establish the browser writer/user SSH observer but not the absence of additional broker clients such as USB. Exact flashed revision/hash, browser/version/origin, uptime, settling/soak duration and same-boot continuity with earlier samples remain unspecified. This records concurrent admission, traffic counters and an SSH stack low-water measurement, not byte integrity, a leak trend, reserve-floor approval or 8D.1 validation.
|
||
|
||
### Follow-up full-client-mix sample
|
||
|
||
The user supplied this replacement 8D.0 baseline transcript on 2026-09-05 for **two browser serial + USB + user SSH + admin SSH**, including broker/USB snapshots and requesting that it overwrite the previous measurement. Commands were `memory`, `ssh sessions`, `ssh counters`, `web counters`, `web status`, `mdns status`, `ssh status`, `serial status`, `broker clients`, then `usb status`; these are sequential observations, not an atomic sample or an agent-executed test.
|
||
|
||
- SSH initialized/running, not transitioning, port **22**, `last-error=ESP_OK`, sessions **2/2**, both public-key authenticated: admin session **5**, slot **0**, admin-console route with no broker; user session **6**, slot **1**, broker **9 observer**. Both active/authenticated, admin-command idle, zero admin output, no RX/TX pending or closing state. Role-based password/public-key authentication and shell/PTY-only admission remain reported, with exec/subsystem/forwarding/SCP/SFTP disabled. Owner task core **1**, stack **20,480 B**, minimum-free **16,288 B**.
|
||
- SSH lifecycle starts **1**, TCP connections **2**, start-failures/stops/capacity rejects **0**. Handshake successes/auth attempts **2**, handshake failures/timeouts/auth failures/request rejects **0**. Broker connects **1**, writer requests **1**, grants **0**, denials **1**, failures/disconnects/revocations **0**. Admin admissions **1**, admission failures/input backpressure **0**. Stream RX/accepted/rejected **0**, TX **17,312 B**, I/O failures/session revocations **0**.
|
||
- HTTPS initialized/running, not transitioning, port **443**, `last-error=ESP_OK`; HTTP Basic via the user database, four users and two admins, unchanged endpoints. Web serial attached, sessions **2/2**, zero tickets, both password-authenticated normal-user sessions for the same account. Slot **0**, fd **56**, generation **2**, broker **16 writer**; slot **1**, fd **57**, generation **1**, broker **10 observer**. Neither slot TX-pending or closing.
|
||
- Web lifecycle starts **1**, start-failures/stops **0**. Requests total/authenticated **181**, root **2**, status **175**, tickets **3**, assets **1**, auth-failures/response-errors **0**. Tickets issued/consumed **3**, rejected/expired **0**. WebSocket connects **3**, disconnects **1**, connect/service-start/broker failures **0**.
|
||
- WebSocket RX **25** accepted frames / **25 B**, rejected frames/bytes **0**. TX **274** binary frames / **41,456 B**, **10** control frames / **823 B**. Writer requests **3**, grants **2**, denials **1**, releases/revocations **0**; send/queue/protocol failures and closes **0**. Writer denials are consistent with observer admission, not reported transport failures.
|
||
- mDNS initialized/announced, hostname `sak-1024.local`, suffix `1024`, `last-error=ESP_OK`. UART service **running**, RS-232 owner **serial service**; configuration v1 **460800 baud, 8N1, no flow control**, DTR inactive, RTS threshold **96**; RX available/TX pending **0**. Modem asserted **DCD=0, DSR=1, CTS=1, RI=0**, valid-voltage **VLD=1**.
|
||
- Broker lists exactly four clients: web **16 writer** (`web-0-2`), SSH **9 observer** (`ssh-1-1`), web **10 observer** (`web-1-1`), USB **11 observer** (`usb-cdc`), all with **zero pending bytes/events**. Admin SSH is not a broker client.
|
||
- USB CDC initialized/attached, host-open **yes**, host DTR/RTS **yes**; broker client **11 observer**. Last host line coding **38400 baud, 8N1** is reported only and does not change UART1's **460800 baud, 8N1** configuration. This confirms USB attachment/admission, not USB byte-integrity testing. Both browsers are normal-user sessions, so admin-role browser login remains unverified.
|
||
- Exact flashed revision/hash, browser/version/origin, fixture, durations and same-boot continuity remain unspecified. This records concurrent network admission and traffic, not serial byte integrity, a timed soak, controlled per-client allocation costs or 8D.1 validation. Internal/DMA minima **17,812 / 10,056 B** are observed lows, not approved reserve floors; DMA overlaps internal heap.
|
||
|
||
### Settled post-soak cleanup
|
||
|
||
The user supplied this 8D.0 baseline transcript on 2026-09-05 and subsequently confirmed the timing: **15-minute soak**, then client disconnection, then **60 seconds of settled cleanup** before running the commands. This fills the timed soak/cleanup memory row. Commands were `memory`, `ssh sessions`, `ssh counters`, `web counters`, `web status`, `mdns status`, `ssh status`, `serial status`, `broker clients`, then `usb status`; these are sequential user observations, not an atomic sample or an agent-executed test.
|
||
|
||
- HTTPS initialized/running, not transitioning, port **443**, `last-error=ESP_OK`; HTTP Basic via the user database, four users and two admins, unchanged endpoints. Web serial attached, sessions **0/2**, zero tickets. SSH initialized/running, not transitioning, port **22**, `last-error=ESP_OK`, sessions **0/2**. SSH role-based password/public-key authentication and shell/PTY-only admission remain reported, with exec/subsystem/forwarding/SCP/SFTP disabled. Owner task core **1**, stack **20,480 B**, minimum-free **16,288 B**.
|
||
- Broker reports **no clients connected**. USB CDC initialized/attached but host-open **no**, DTR/RTS **no**, broker disconnected. Last host line coding **9600 baud, 8N1** is reported only; UART1 remains independently configured at **460800 baud**. Physical USB attachment therefore remains, without an active broker client.
|
||
- SSH lifecycle starts **1**, TCP connections **2**, start-failures/stops/capacity rejects **0**. Handshake successes/auth attempts **2**, handshake failures/timeouts/auth failures/request rejects **0**. Broker connects/disconnects **1** each, writer requests **1**, grants **0**, denials **1**, failures/revocations **0**. Admin admissions **1**, admission failures/input backpressure **0**. Stream RX/accepted **5 B**, rejected **0**, TX **84,546 B**, **I/O failures 1**, **session revocations 1**. These two nonzero counters are retained without attributing them to normal disconnect or a defect; the transcript does not establish their cause.
|
||
- Web lifecycle starts **1**, start-failures/stops **0**. Requests total/authenticated **527**, root **2**, status **521**, tickets **3**, assets **1**, auth-failures/response-errors **0**. Tickets issued/consumed **3**, rejected/expired **0**. WebSocket connects/disconnects **3** each, connect/service-start/broker failures **0**.
|
||
- WebSocket RX **25** accepted frames / **25 B**, rejected frames/bytes **0**. TX **1,490** binary frames / **177,163 B**, **11** control frames / **918 B**. Writer requests **3**, grants **2**, denials **1**, releases/revocations **0**; send/queue/protocol failures and closes **0**.
|
||
- mDNS initialized/announced, hostname `sak-1024.local`, suffix `1024`, `last-error=ESP_OK`. UART service **running**, RS-232 owner **serial service**; configuration v1 **460800 baud, 8N1, no flow control**, DTR inactive, RTS threshold **96**; RX available/TX pending **0**. Modem asserted **DCD=0, DSR=1, CTS=1, RI=0**, valid-voltage **VLD=1**.
|
||
- Relative to the recorded full-client-mix snapshot, free internal/DMA heap is **22,984 B higher** and free PSRAM **49,768 B higher**; largest blocks return to **31,744 B internal/DMA** and **8,126,464 B PSRAM**. Lifetime minima are unchanged. These endpoint observations show headroom recovery, not a controlled per-client allocation measurement or proof of no leak. Relative to the fresh-boot browser-disconnected/UART-running sample, free internal/DMA heap is **300 B lower** and PSRAM **216 B lower**, with equal largest blocks; differing HTTP activity and uncontrolled allocation state prevent treating those differences as a diagnosed leak.
|
||
- All serial transport clients are disconnected, but cumulative authenticated status requests are present; this is not evidence of no HTTPS polling or retained TLS connections. Exact flashed hash, browser/version/origin, fixture and traffic pattern/verified byte counts remain unspecified. The 15-minute soak and 60-second cleanup durations are user-confirmed; the reported UART configuration is **460800 baud**, not the checklist's separate **115200-baud** soak workload. Full soak/stress acceptance, SSH counter diagnosis, reserve floors and 8D.1 validation remain pending.
|
||
|
||
Resource gates:
|
||
|
||
| Gate | Budget / status |
|
||
|---|---|
|
||
| 8D.0 firmware allocation/route/task/socket change | **0**; documentation only |
|
||
| Baseline internal free/minimum/largest-block reserve floors | **Pending reserve analysis and remaining workload measurements**; the recorded sample is not a safety floor |
|
||
| PSRAM reserve and largest-block floor | **Pending**, including TLS/SSH connection and handshake peaks |
|
||
| Stack reserve floors | **Pending** reserve analysis; SSH measurements recorded, other owner-task telemetry missing |
|
||
| 8D.1 incremental budget | **Actual static delta +672 B**, including 664 B store/lock symbols; no module heap allocation, task, route or socket increase. Basic cache retained. Runtime reserve acceptance still pending; see implementation record below. |
|
||
| 8D.2 incremental budget | **Pending** exact generation/session bindings added to four tickets/two slots and cleanup integration |
|
||
| 8D.3 incremental and cumulative M1 budget | **Pending** four challenge records, throttle, five handlers, parser/CSRF scratch, minus removed Basic cache/key; no new task/socket allowance |
|
||
| M2 admin allocation/socket budget | **Pending** retained-serial admission design and target M1 measurements; not approved here |
|
||
|
||
To close these gates, record each chunk's actual linked/static and allocated structure sizes, internal-versus-PSRAM placement and worst-case fallback, then measure peak incremental handshakes/traffic and post-cleanup fragmentation under the same workloads. Set numeric reserve floors sufficient for measured recovery/USB/SSH needs and the largest required allocation, with an explicitly justified margin; subtract these floors before approving incremental/cumulative budgets. A percentage of total RAM or a source payload subtotal is not a reserve. Stop on monotonic leak/declining largest block, watchdog/stack fault, lost lease or inaccessible login.
|
||
|
||
Target operator checklist (user-provided samples cover status/memory collection and concurrent admission of two web serial clients, USB and user/admin SSH, with traffic counters and broker/USB snapshots confirming four broker clients and USB host DTR. Remaining checks are unverified, and none were executed by the agent):
|
||
|
||
1. Identify flashed revision/configuration against this record; record browser/version, IP/mDNS origin, Wi-Fi mode, serial framing/flow/baud and attached peer/fixture. Use existing provisioned accounts; do not expose credentials in evidence. Firmware upload is a separate user-controlled action; no erase is required for this documentation task.
|
||
2. On UART0 run `memory`, `serial status`, `broker status`, `broker clients`, `usb status`, `web status`, `ssh status` at settled boot and each table point. Record output without secrets. Use `pio device monitor -b 115200` once a port is available; this is interactive, not a completed automated test.
|
||
3. In separate browser profiles authenticate an existing admin and user through **current Basic auth**; verify protected assets/status and bidirectional serial bytes, single writer/request/release, and observer isolation. Test direct-IP and mDNS separately where STA mDNS is available. If baseline login fails, stop and diagnose in a separate task, not during auth replacement.
|
||
4. Verify native USB UART1 with host DTR, user SSH serial and admin SSH console, while UART0 remains usable. Open two browser serial sockets plus USB, one user SSH and one admin SSH; collect the full-workload row. This is four broker clients and two SSH sessions; admin SSH is not a broker client. Also check the alternative two-user-SSH mix (five broker clients) without increasing any limit.
|
||
5. Run **five** explicit browser serial Disconnect/Connect cycles per role, observing paused reconnect, old broker cleanup, writer/observer correctness and memory after each cycle. Baseline has no logout: do not report Basic-cache reset as a logout test. M1 will repeat five login/serial/logout cycles per role instead.
|
||
6. Run a **15-minute** concurrent soak at **115200 baud, 8N1, no flow control**, using a safe known peer/fixture and a recorded binary pattern/byte count. Transfer writer ownership explicitly among transports, collect memory/status every minute, and record bytes/drops, latency and any watchdog/reset. Repeat a separately recorded **five-minute 460800-baud** stress run with the same mix; historical drops at that load are not permission for new exhaustion or lease loss. These workloads do not reduce supported baud rates. Record any fixture limitation rather than inventing results.
|
||
7. Disconnect network clients and USB DTR, wait 60 seconds and capture cleanup memory/status. Compare free/largest-block recovery and lifetime minima with boot/full-load rows. Record SSH stack margin; explicitly retain other stack floors as pending until instrumentation is separately scoped.
|
||
8. Fill measured reserve/incremental budget cells with justified numbers and review remaining failures. The user explicitly requested 8D.1 with this gate pending; that is not approval to skip later regression checks or M1/M2 milestone pauses.
|
||
|
||
## 8D.1 implementation and validation record
|
||
|
||
- **Checkpoint:** Work started from clean `devel` at `02fdeee3453654680c11096c9a6224c26233eced` (documentation-only successor to the build baseline). The user requested 8D.1 after the initial runtime sample and provided the full-client-mix sample while work began. Remaining reserve floors are explicitly pending; this is not an automatic budget approval for subsequent chunks.
|
||
- **Implemented:** `src/web_session_store.{h,c}` provides four fixed internal records; 32-byte token digests, 32-byte origin digests, separate 32-byte CSRF state, copied principals, one-hour absolute deadlines and non-reused 64-bit IDs. Issue/lookup/currentness/prune/session and user invalidation APIs exist, plus secret-free counts/storage sizes. No raw bearer token is retained. Failed output and retired record cleanup is explicit. Stop wipes records, cancels in-flight issuance/init and never resets IDs/epochs. No task, module heap allocation or internal-fallback allocation is introduced.
|
||
- **Concurrency:** A short portMUX protects only bounded state operations; RNG/SHA/database work is outside it. Resolution rechecks ID/liveness/deadline after principal validation. Issuance rechecks an invalidation epoch before publication; any explicit invalidation conservatively cancels concurrent issuance, including issuance for another account, without removing unrelated live sessions. Epoch/ID exhaustion is fail-closed. Snapshot counts are cumulative for the boot; views containing CSRF are sensitive request-local results, never status snapshots. HTTP Origin validation remains the future caller's responsibility; the primitive only binds a bounded canonical origin digest.
|
||
- **Lifecycle integration:** Only an admitted HTTPS start initializes the store; failed starts and accepted stops disable it before cleanup, including failed teardown. Store-init failure is warning-only for the unchanged Basic-auth service. No production caller issues/looks up sessions yet. No login page, cookie route, ticket binding, account-mutation notification hook, new URI, protocol change or generated asset change. Those remain 8D.2/8D.3 scope.
|
||
- **Build:** Final `pio run` passed in 8.90 seconds after review fixes, with the same PlatformIO/IDF configuration: **95,204 B linked RAM (+672 B)**, **1,600,505 B flash (+532 B)** relative to 8D.0. Target object symbol inspection reports **656 B state + 8 B portMUX = 664 B**; DWARF confirms **152 B per record × 4 = 608 B**, principal 40 B. The aggregate link delta includes eight additional bytes beyond those store symbols. All storage coexists with the existing Basic cache. No post-change runtime heap claim is made.
|
||
- **Dormant-code accounting:** All functions compile and are exercised by the host harness, but the firmware link currently retains only init/stop and their dependencies; unused issue/lookup/etc. code is garbage-collected. The static state is retained in full. Later production callers must account for the newly linked code and stack/crypto execution costs rather than treating this flash delta as the full M1 cost.
|
||
- **Focused executable checks:** `python3 tests/web_session_store/run.py` passed using OpenSSL SHA-256 and deterministic RNG/database/time/FreeRTOS doubles. Coverage includes failed init/retry/idempotence, capacity/no eviction, token/origin mismatch, exact expiry/no renewal, per-session/account isolation, stale principals and DB failure, stop/reinit/stale IDs, invalidation during pruning and candidate issuance, replacement during lookup, expiry during revalidation, stop during init, and post-token SHA failure output wiping. RNG/SHA/database doubles assert that no store lock is held. This narrow harness is not a general firmware test suite.
|
||
- **Review-only limits:** ID/epoch exhaustion, collision rejection and private record wiping were inspected, not forced by public-API tests. Callback races are deterministic interleavings, not real multicore scheduling. Host tests do not test device DRBG, mbedTLS integration, HTTPD lifecycle races, heap/stack margins or future dormant browser paths. Independent source review found and verified the fix for reinitialization during a rejected concurrent start.
|
||
- **Post-change target gate:** Boot this build through a user-controlled upload; check UART0 recovery, unchanged admin/user Basic login and assets/status, explicit serial Disconnect/Connect, USB, user/admin SSH and full-client-mix memory/status/counters against both pre-change samples. Run five serial lifecycle cycles per role; include HTTPS stop/start and observe no new failures. Record `ssh status` stack margin and the remaining soak/cleanup measurements above. No target run of this changed firmware has been supplied or performed. Stop at 8D.1; request 8D.2 only after the regression checkpoint or an explicit decision acknowledging it remains pending.
|