Implement transport-qualified session identity and immutable owner adapters for SSH console lifecycle and output-drain operations. Add focused host tests covering admission, stale identities, deferred actions, completion races, prompts, backpressure, and slot reuse. Update Phase 8D documentation and current-state tracking.
6.0 KiB
Phase 8D.4 - Small Console Boundary
Status (2026-09-06): Implemented / host-tested / build-verified; target validation pending. M1 and previous phase sign-offs stand. Numeric reserve gates remain open. No 8D.5 implementation or browser admin route is included.
Scope And Contract
Starting revision: f9ee6eec9cbe06fe5120ee718507eaffd69787a2, initially clean worktree. Changed production files are src/admin_ssh_console.{c,h} and src/ssh_transport.c; focused tests are in tests/admin_console_boundary/. No console-input changes were necessary because its prompt routing already uses the shared dispatcher.
admin_ssh_console_open_owned()accepts a copied transport-qualified token/principal and an immutable firmware-lifetime owner adapter. Existing SSH admission remains available throughadmin_ssh_console_open(), now implemented beside its owner inssh_transport.c.- The adapter supplies nonblocking application-output drain checks and deferred lifecycle handling, called by the existing control task outside console locks. SSH validates slot/session/generation and uses existing transport control APIs, never runtime wolfSSH calls from the control task.
- Two console slots remain shared, not two per transport. Occupied and still-executing slots reject replacement. Future admission must coordinate this pool; this phase does not promise simultaneous browser and full SSH admin capacity.
- The token includes transport, slot, session ID and generation. Owners serialize input per session, exclusively consume output, maintain transport authentication/expiry, and close on disconnect/revocation. The core retains admission/dispatch principal checks and queued-work identity checks.
- Completion scratch has a nonblocking claim so different owners cannot race the shared buffer outside the lock. A competing TAB remains unconsumed for retry; stale completion cannot publish into a reused slot.
- Shell exit uses an owner-relative
SELF_CLOSEaction; existing STOP/DISCONNECT/key actions still mean SSH. Unsupported actions fail before side effects. Deferred input is rejected at both admission and feed, and identity is checked again after the final delay. - The same dispatcher, canonical registry, queue, editor/history/prompts, UART0 policy and output ring remain. Five-second output backpressure and ten-second deferred drain plus 200 ms remain bounded best-effort heuristics, not delivery confirmation.
No new tasks, browser endpoints, UI changes, serial/broker changes, generated assets, persistence changes, uploads, erases or commits.
Verification And Resources
python3 tests/admin_console_boundary/run.py: PASS for production console and extracted production SSH adapter with deterministic host fakes. Covers readiness/admission, cross-transport and stale identities, completion contention and close/reopen during completion, history, queued disconnect/revocation, UART dispatch, executing cleanup, hidden/visible/cancelled/disconnected prompts, exit-to-SELF_CLOSE routing, unsupported actions, queue rejection, drain timeout/delay, SSH action routing and slow output.python3 tests/admin_ssh_policy/run.py: PASS, including quiet empty input and physical-only command restrictions.- Independent code review found no defects.
git diff --check: PASS. pio run: PASS in 43.61 seconds, PlatformIO espressif32 6.12.0 / ESP-IDF 5.5.0, N16R8 release. Linked RAM 95,084 B, flash 1,627,173 B. Against the recorded latest 8D.3 build (95,508 / 1,625,725 B): -424 B RAM / +1,448 B flash. Against recorded 8D.0 (94,532 / 1,599,973 B): +552 B RAM / +27,200 B flash. These comparisons use recorded builds, not a fresh baseline rebuild.
Static savings come from removing the full SSH snapshot scratch in the console and reading only the matching published slot under the SSH lock. Added costs are transport tags, owner pointers in sessions/control requests, the immutable adapter, and one completion-claim flag. No payload buffer or capacity increase. Two 4 KiB output rings, four-entry command queue, two-entry control queue, four-line per-session history and task stack sizes (dispatcher 12 KiB, UART frontend 6 KiB, control 4 KiB) are unchanged. No new module heap allocation, socket, TLS connection, HTTP handler, web session or ticket capacity. Runtime heap, PSRAM and stack margins were not measured; no reserve approval is inferred from linked RAM.
Host fakes do not establish actual FreeRTOS scheduling, task-local stdio redirection, real queue capacities, socket behavior or hardware regression. The command runner and lifecycle operations are doubled; direct production helpers test routing and output separately. Sanitizer execution was unavailable because the host lacks the required runtime library, not a sanitizer pass.
Target Handoff
Do not stack 8D.5 on this unverified runtime change without target sign-off or an explicit user decision. After flashing through the user's normal workflow:
- Boot and capture UART0
memory, status andssh status. Verify UART0 recovery and empty Enter/normal commands through admin SSH (also covers the separately pending 8D.3 empty-line fix). - Exercise UART0/admin-SSH serialization, completion/history, visible/hidden/cancelled prompts, and disconnect/revocation while work is queued or a prompt is active. Check reconnect/slot reuse does not receive old output.
- Exercise slow-reader backpressure and recovery,
exit, empty-line Ctrl+D, and the existing deferred SSH stop/disconnect/key/reboot paths as appropriate. Confirm acknowledgement remains best effort and stale sessions cannot act on reused identities. - Run the always-on browser login/serial explicit disconnect/reconnect, USB UART1 and user/admin SSH smoke. Repeat five serial lifecycle cycles per role and collect comparable settled/full-client-mix/cleanup
memoryand SSH stack telemetry at the supported 115200-baud workload.
No target checks above were executed by the agent. Exact next chunk is 8D.5: bounded admin WebSocket backend, only when separately requested after the applicable validation decision.