From 2f383cd283571d828d0c143259eb768636941d41 Mon Sep 17 00:00:00 2001 From: Commander1024 Date: Sun, 30 Aug 2026 22:45:33 +0200 Subject: [PATCH] Refresh agent documentation for current behavior --- AGENTS.md | 2 +- docs/agent/architecture.md | 52 ++++++++++++++++++---------------- docs/agent/code-map.md | 43 ++++++++++++++-------------- docs/agent/current-state.md | 26 ++--------------- docs/agent/design-decisions.md | 26 ++++++++--------- 5 files changed, 67 insertions(+), 82 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index eb94db4..1a0f150 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,6 +39,6 @@ pio run --target upload pio device monitor -b 115200 ``` -A first migration to the custom partition table may require `pio run --target erase`, but erasing destroys persisted configuration and credentials. Never run it without explicit user approval. +The first migration from the former default/factory layout to the custom partition table requires `pio run --target erase`. Erasing destroys persisted configuration and credentials; never run it without explicit user approval. No automated host test command is defined in the repository. Hardware validation procedures live in `docs/electrical_tests.md` and `docs/user_administration_tests.md`; do not claim they passed unless actually performed. diff --git a/docs/agent/architecture.md b/docs/agent/architecture.md index 0cb3ba6..3cbf890 100644 --- a/docs/agent/architecture.md +++ b/docs/agent/architecture.md @@ -27,19 +27,19 @@ SSH role=admin ------> shared administration dispatcher <------ UART0 `app_main()` in `src/main.c` is the composition root. The implemented order matters: -1. Report PSRAM and initialize the sole secure DRBG before Wi-Fi or other radio use. -2. Initialize boot-critical RGB LED state, RS-232 ownership/static-safe hardware, diagnostics, and the shared administration dispatcher resources. +1. Report PSRAM and initialize the sole project-owned application DRBG before Wi-Fi or other radio use. +2. Initialize boot-critical RGB LED state, RS-232 ownership/static-safe hardware, diagnostics, and the shared administration dispatcher resources. RGB LED initialization is currently guarded by `ESP_ERROR_CHECK` and is therefore boot-fatal. 3. Attempt optional OLED initialization and a bounded boot animation. Display failure is nonfatal; a working display can delay later recovery services by about five seconds. 4. Initialize button diagnostics and load local-UI and serial configurations, falling back to RAM defaults on load failure. 5. Initialize the serial service, session broker, and permanent USB transport task. UART1 is not started automatically here. 6. Load/generate HTTPS material, then initialize the user database using the legacy web credential for first migration when available. 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, persist generated first-boot defaults when appropriate, initialize its manager, and start it when configured for boot. -9. Start HTTPS and SSH only when their startup gates pass. Current code requires Wi-Fi and HTTPS security readiness for both; 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. 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. 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. -Several core initializers use `ESP_ERROR_CHECK`; optional display and network/security paths generally log failure while retaining UART0 administrative recovery and network-independent UART1 access through USB. +Several core initializers use `ESP_ERROR_CHECK`; optional display and network/security paths generally log failure while retaining UART0 administrative recovery and network-independent UART1 access through USB. SSH starts before command registration, so role-`user` sessions can be admitted in that interval while role-`admin` sessions are rejected until the administration frontend is ready. ## Serial service and physical ownership @@ -56,9 +56,9 @@ Several core initializers use `ESP_ERROR_CHECK`; optional display and network/se - `SERVICE`: serial service owns it; - `FAULT`: cleanup could not establish a safe state; reboot is required. -Diagnostics and the production service must claim this owner before manipulating UART/GPIO state. Unsafe cleanup keeps the transceiver disabled and marks a fault rather than attempting continued operation. +The owner is cooperative rather than an interceptor for UART/GPIO APIs. Active diagnostic commands claim `PHASE0`, and the running service claims `SERVICE`; boot-time static-safe GPIO initialization and service-owned restoration of that static mode are explicit exceptions. Unsafe cleanup keeps the transceiver disabled and marks a fault rather than attempting continued operation. -Serial configuration is a working RAM value. Applying it while running performs a stop/restart and attempts rollback on failure; queued data may be discarded. Persistence is explicit through save/reset commands. +Serial configuration is a working RAM value. Applying it while running performs a stop/restart and attempts rollback on failure. Stop/reconfiguration discards and accounts serial-service RX/TX streams and task-local pending TX, but does not disconnect broker clients or clear their writer lease, events, or already-fanned output. An open USB session retries service start after a stop; existing WebSocket and role-`user` SSH sessions do not independently restart it. Persistence is explicit through save/reset commands. ## Session broker and data flow @@ -84,29 +84,33 @@ Only the generation-safe client ID holding the current writer lease may enqueue Broker events are advisory bounded notifications. Transports reconcile against authoritative snapshots because an event queue can overflow. `DTR_ON_CONNECT` follows whether any broker client is connected, not writer ownership. +The broker currently enters nonblocking serial read/write APIs while holding its mutex and takes the serial-service state mutex during first-connect/last-disconnect DTR changes. Keep this ordering acyclic: serial-service code must not call broker APIs while holding its state mutex. + ## Transport architecture ### USB CDC `usb_cdc_transport` has a permanent transport task and TinyUSB callbacks. Attached plus host DTR asserted is treated as open. Opening starts the serial service if necessary, creates the `usb-cdc` broker client, and opportunistically requests writer ownership; otherwise USB observes. -TinyUSB callbacks enqueue/copy data and state; the transport task owns broker lifecycle and forwarding. Supported host line coding can update the RAM serial configuration only while USB is writer, the serial service is running, and TX is empty. It is not automatically persisted. +TinyUSB callbacks enqueue/copy data and state; the transport task owns broker lifecycle and forwarding. The callback caches the latest supported host line coding regardless of current writer state; the task applies it only while USB is writer, the serial service is running, and TX is empty. The pending value is discarded when the CDC session closes and is not automatically persisted. ### HTTPS, WebSocket, and web serial `web_server` runs HTTPS only on port 443 using the device-specific self-signed P-256 certificate from `web_security`. Current routes provide the UI, static assets, status, ticket issuance, and serial WebSocket upgrade. -HTTP Basic authentication uses `user_database`, not the legacy recovery credential. Both `user` and `admin` roles currently receive the same web status/terminal experience; web administration is not implemented. +HTTP Basic authentication uses `user_database`. Before administrator bootstrap, the migrated role-`user` account is synchronized from the legacy credential, so that username/password can authenticate through the database; after bootstrap, the legacy blob is independent recovery material and is no longer consulted for authentication or synchronized into role-based accounts. Both `user` and `admin` roles currently receive the same web status/terminal experience; web administration is not implemented. -A WebSocket connection requires a one-time, 30-second, principal-bound ticket. Ticket issuance and upgrade also validate a supplied `Origin` against `https://`; absence of `Origin` is accepted for non-browser clients. Tickets are stored as digests, consumed before currentness validation, and are never persisted. An admitted session starts the serial service if necessary, creates a broker client, and opportunistically requests writer ownership. The web transport has two fixed session slots. Binary frames carry serial data; small text messages request or release writer ownership. HTTPD owns socket send/close operations, while the web transport task mediates broker work through bounded scheduling. +The boot-local Basic-authentication cache has four RAM entries and a five-minute sliding lifetime. It stores a keyed digest of the complete `Authorization` header rather than the raw header, and every hit revalidates principal currentness. Its current lack of locking relies on the single-HTTPD-owner execution model. + +A WebSocket connection requires a one-time, principal-bound ticket with a maximum 30-second lifetime. Only four tickets can be outstanding; minting another evicts the live entry with the earliest expiry. Ticket issuance and upgrade also validate a supplied `Origin` against `https://`; absence of `Origin` is accepted for non-browser clients. Tickets are stored as digests, consumed before currentness validation, and are never persisted. An admitted session starts the serial service if necessary, creates a broker client, and opportunistically requests writer ownership. The web transport has two fixed session slots. Binary frames carry serial data; small text messages request or release writer ownership. HTTPD owns socket send/close operations, while the web transport task mediates broker work through bounded scheduling. 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. `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. +`web_ui.c` contains authored index/application strings and response policy. 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. ### SSH -`ssh_transport` uses wolfSSH on port 22 with two fixed session/handshake slots. One owner task pinned to core 1 is the only project task that calls wolfSSH APIs. It enforces bounded handshakes, authentication attempts, receive work, and session buffers. +`ssh_transport` uses wolfSSH on port 22 with two fixed session/handshake slots. Initialization calls `wolfSSH_Init()` in the caller before task creation; after that, one owner task pinned to core 1 exclusively owns runtime contexts/sessions and wolfSSH calls. It enforces bounded handshakes, authentication attempts, receive work, and session buffers. Authentication uses user-database passwords or stored Ed25519/ECDSA-P256 public keys. Public-key lookup authorizes a username/key pair, while wolfSSH verifies signed proof of possession. SSH host identity is a separate persisted P-256 key managed by `ssh_security`. @@ -115,7 +119,7 @@ Routing follows the authenticated role: - `user`: start the serial service if necessary, then create a broker-backed binary-transparent serial stream and opportunistically request writer ownership; - `admin`: bounded administration console, with no broker client or writer lease. -There is no `exec`, SFTP, SCP, subsystem, agent forwarding, or TCP forwarding support. +A shell request is required, but project code does not explicitly require a PTY. Exec and subsystem requests are rejected, and there is no project SFTP, SCP, agent-forwarding, or TCP-forwarding route. ## Authentication, authorization, and revocation @@ -125,10 +129,10 @@ Network code holds copied, secret-free principals rather than pointers into data Revocation has two layers: -1. command-layer account mutations explicitly request targeted WebSocket/SSH revocation; -2. transports periodically and at sensitive boundaries recheck principal currentness, providing fail-safe closure if notification fails. +1. after a database mutation commits, the command layer makes best-effort targeted WebSocket/SSH revocation calls; notification failure does not roll back the mutation; +2. transports periodically and at sensitive boundaries recheck principal currentness, providing authoritative fail-safe closure if notification fails. -The final administrator cannot be deleted or demoted. UART0 is trusted for initial administrator bootstrap and explicit unavailable-database recovery. Authenticated admin SSH can run the operational registry but is denied those two recovery operations. The legacy `web_sec` username/password remains migration/recovery material after bootstrap and no longer authenticates HTTPS or SSH. +The final administrator cannot be deleted or demoted. UART0 is trusted for initial administrator bootstrap and explicit unavailable-database recovery. Authenticated admin SSH can run the operational registry but is denied those two recovery operations; other secret-bearing commands are remotely available unless their handlers deny them. NVS is not encrypted. Password verifiers improve password storage, but Wi-Fi credentials, legacy recovery credentials, and TLS/SSH private keys remain recoverable under physical flash extraction. @@ -143,19 +147,19 @@ admin SSH line editor ----/ | +-> registered *_console handlers ``` -`admin_ssh_console` creates the dispatcher before network services but marks command dispatch ready only after ESP-IDF console registration. An admin SSH connection during that boot window is rejected rather than racing an incomplete registry. +`admin_ssh_console` creates the dispatcher before network services but marks command dispatch ready only after ESP-IDF console registration and successful UART frontend task creation. An admin SSH connection during that boot window is rejected rather than racing an incomplete registry. The dispatcher is the sole caller of `esp_console_run()`, serializing UART0 and all admin SSH commands. This is required because the console registry is treated as non-reentrant, but it also means a long command or interactive prompt blocks all administration entry routes. For SSH, standard output/error is redirected to the invoking session's bounded output ring. `console_input` routes visible or hidden prompts to UART0 or the active SSH session. Session tokens include slot and generation so late queued work cannot attach to a reused SSH slot. Only the SSH owner task moves ring output through wolfSSH. -Self-affecting remote actions such as reboot, stopping SSH, disconnecting sessions, or replacing the host key are deferred until acknowledgement output drains. UART0 invokes these synchronously. Completion candidates are manually maintained and can drift from command registration. +Remote reboot, SSH stop/disconnect, and host-key rotate/reset use deferred control. The control task waits up to ten seconds for command state plus administration and transport application buffers to clear, then adds a short delay; this is a bounded best-effort heuristic, not peer-delivery confirmation. UART0 invokes these actions synchronously. User mutations and their revocations are not part of this mechanism. UART0 linenoise and the SSH editor consume the same manually maintained completion-hint provider, so the two administration routes cannot drift from each other; the hints can still drift from command registration and are not an authorization list. ## Wi-Fi and persistence `wifi_config` owns a fixed-width versioned NVS schema with four prioritized station profiles and AP policy `off`, `fallback`, or `always`. Missing configuration generates per-device defaults including a random AP password. Invalid stored data is generally left untouched while RAM defaults are used. -`wifi_manager` is a permanent task with one bounded command/event queue. ESP-IDF callbacks only copy compact events into the queue. The task owns association, DHCP deadlines, profile failover, AP policy, retries/backoff, and next-profile requests. It also reconciles against authoritative driver/netif state so dropped events do not permanently wedge policy. ESP-IDF Wi-Fi storage is RAM-only; the application blob is authoritative, and edits require explicit save. +`wifi_manager` is a permanent task with one bounded command/event queue. ESP-IDF callbacks only copy compact events into the queue. The task owns association, DHCP deadlines, profile failover, AP policy, retries/backoff, and next-profile requests. It also reconciles against authoritative driver/netif state so dropped events do not permanently wedge policy. ESP-IDF Wi-Fi storage is RAM-only; the application blob is authoritative, and edits require explicit save. Start/stop—including local controls—intentionally update the RAM `enabled_at_boot` field. Working-configuration copies contain PSKs and must be securely wiped; routine status and the local UI use secret-free snapshots. Persistent namespaces/blobs include: @@ -170,21 +174,21 @@ Configuration modules generally choose RAM defaults without erasing incompatible ## Local UI and hardware boundaries -`board_pins.h` centralizes GPIO/UART/I2C assignments. `local_display` solely owns I2C0, the SSD1315-compatible OLED, its static framebuffer, and display mutex. Display frames belong to the initiating task. Dirty-page commits and I2C transactions are bounded. +`board_pins.h` centralizes project-assigned RS-232, diagnostic, RGB LED, and local-UI hardware resources; UART0 GPIOs remain local to `main.c`, and native USB uses platform wiring. `local_display` solely owns I2C0, the SSD1315-compatible OLED, its static framebuffer, and display mutex. Display frames belong to the initiating task. Dirty-page commits and I2C transactions are bounded. -`local_status_ui` is a permanent low-priority task that polls/debounces buttons, renders copied public snapshots, implements aging/wake behavior, and invokes a constrained set of public service APIs for local controls. It collects snapshots before opening a display frame, so service/broker locks are not held across I2C. It never parses CLI output, becomes a broker client, edits credentials, or assigns a writer; emergency action can only release the expected current writer. +When button GPIO initialization succeeds, `local_status_ui` starts a firmware-lifetime low-priority task that polls/debounces buttons, renders copied public snapshots, implements aging/wake behavior, and invokes a constrained set of public service APIs for local controls. It collects snapshots before opening a display frame, so service/broker locks are not held across I2C. It never parses CLI output, becomes a broker client, edits credentials, or assigns a writer; emergency action can only release the expected current writer. -A missing or failed OLED is nonfatal. A fresh button press can request one bounded reprobe. Long confirmation holds protect disruptive local actions, and stuck buttons are quarantined. +The task can run with an absent OLED, and a fresh button press can request one bounded panel reprobe after successful I2C bus setup. Failed I2C bus creation is not recoverable through that path. The `display` configuration commands depend on the UI task. Long confirmation holds protect disruptive local actions, and stuck buttons are quarantined. Hardware diagnostics are synchronous console commands. RS-232 tests own the physical port exclusively and restore safe GPIO state; OLED tests reuse the display service rather than taking independent I2C ownership. ## Concurrency and lifecycle constraints -- Broker, USB, web-transport, Wi-Fi, local-UI, and SSH owner tasks are firmware-lifetime tasks; stopping a service generally stops its runtime/listener, not the owner task. +- Broker, USB, web-transport, Wi-Fi, and SSH owner tasks are firmware-lifetime tasks; the local-UI task is also firmware-lifetime when button initialization allowed it to start. Stopping a service generally stops its runtime/listener, not the owner task. - Bounded queues, stream buffers, work bursts, and drop counters are part of slow-client and watchdog isolation. - Transport slot generations and account authentication generations solve different stale-reference problems; preserve both. -- Library/hardware ownership is centralized: serial task owns UART1 while running, display service owns I2C/framebuffer, SSH owner task owns wolfSSH calls, and the console dispatcher owns `esp_console_run()`. +- Library/hardware ownership is centralized: serial task owns UART1 while running, display service owns I2C/framebuffer, the SSH owner task owns post-initialization wolfSSH runtime calls, and the console dispatcher owns `esp_console_run()`. - Password authentication performs PBKDF2 outside the user-database mutex and revalidates afterward. Some password mutation paths currently derive verifiers while holding the mutation lock; do not generalize the authentication locking pattern without checking the exact path. -- Avoid holding service/database/broker locks across I2C, network sends, or other potentially long operations unless the existing contract explicitly requires it. +- Avoid holding service/database/broker locks across I2C, network sends, or other potentially long operations unless the existing contract explicitly requires it. Preserve the existing broker-before-serial lock order. - Broker and selected cryptographic allocations prefer PSRAM but can fall back to internal RAM. FreeRTOS control structures and task stacks intentionally remain internal where flash/cache-disable safety matters. - The build disables wolfSSL ESP32 AES/SHA acceleration, and the HTTPS path uses software AES for PSRAM-backed records. This preserves the validated workaround for uncoordinated mbedTLS/wolfSSL hardware-crypto locks and a prior mbedTLS external-RAM DMA watchdog stall. diff --git a/docs/agent/code-map.md b/docs/agent/code-map.md index 8b08624..0c15179 100644 --- a/docs/agent/code-map.md +++ b/docs/agent/code-map.md @@ -14,12 +14,12 @@ This is a semantic map, not a complete file inventory. Start here, then read the ## Secure randomness -**Responsibility:** provide the sole mutex-serialized device DRBG, seeded before Wi-Fi/radio use. +**Responsibility:** provide the sole project-owned, mutex-serialized application DRBG, seeded before Wi-Fi/radio use. - Files: `src/secure_random.{h,c}` - Interfaces: `secure_random_init()`, random-byte helpers, `secure_wipe()` -- Called by: HTTPS material, SSH keys, users, Wi-Fi defaults, tickets and authentication cache -- Constraint: initialization order is security-significant; do not add independent weak RNGs or radio-dependent early entropy paths. +- Called by: HTTPS material, SSH keys, users, Wi-Fi defaults, tickets, the HTTPS authentication cache, and the wolfCrypt seed callback +- Constraint: initialization order is security-significant; the DRBG deliberately avoids post-radio reseeding and fails closed at its generation limit. Do not add independent weak RNGs or radio-dependent early entropy paths. ## Physical RS-232 and serial service @@ -29,8 +29,8 @@ This is a semantic map, not a complete file inventory. Start here, then read the - Interfaces: owner claim/release/fault; serial init/start/stop/read/write/configuration/snapshots; versioned NVS load/save - Normal data caller: `session_broker`; USB, WebSocket, role-`user` SSH, console, and local UI also call serial lifecycle/configuration APIs as appropriate - Dependencies: ESP-IDF UART driver, `board_pins.h`, NVS -- Ownership: diagnostics use `PHASE0`; service uses `SERVICE`; unsafe cleanup marks `FAULT` until reboot. -- Lifecycle: runtime reconfiguration stops/restarts UART and may discard bounded queued data. +- Ownership: the cooperative owner arbitrates active diagnostics (`PHASE0`) against the service (`SERVICE`); boot-time static-safe GPIO initialization and service-owned static-mode restoration are explicit exceptions. Unsafe cleanup marks `FAULT` until reboot. +- Lifecycle: stop/reconfiguration discards serial-service RX/TX and task-local pending bytes, but leaves broker clients, writer ownership, events, and already-fanned output intact. ## Session broker @@ -54,7 +54,7 @@ This is a semantic map, not a complete file inventory. Start here, then read the - Dependencies: TinyUSB, broker, serial service - Flow: `USB host <-> USB task <-> broker` - Lifecycle: permanent owner task; broker client exists only while attached with host DTR asserted. -- Constraint: host line coding is accepted only while USB is writer and is RAM-only. +- Constraint: callbacks cache the latest host line coding; the task applies it only while USB is writer, UART is running, and TX is empty. It is RAM-only and is discarded when the CDC session closes. ## Web and WebSocket serial @@ -65,9 +65,10 @@ This is a semantic map, not a complete file inventory. Start here, then read the - Asset files: authored/generated boundary in `src/web_assets_data.{h,c}`, `web_assets/SOURCES.md`, `web_assets/generate_embedded_assets.py` - Interfaces: web init/start/stop/snapshots; HTTP handlers; ticket mint/consume; attach/detach; targeted session revocation - Called by: startup, ESP-IDF HTTPS server, user administration revocation, console/local UI -- Dependencies: user database, secure random, broker, Wi-Fi reachability, mbedTLS/HTTPS server +- Dependencies: user database, secure random, broker, successful Wi-Fi manager initialization at boot, mbedTLS/HTTPS server; actual network reachability is an operational prerequisite, not an initializer invariant - Flow: `browser -> HTTPS Basic auth -> ticket -> WebSocket -> web transport -> broker` -- Ownership: HTTPD owns socket send/close work; transport task owns broker mediation; two fixed WebSocket slots. +- Ownership: HTTPD owns socket send/close work; transport task owns broker mediation; two fixed WebSocket slots and four outstanding tickets. +- Security constraints: Basic-auth cache hits still revalidate principal currentness; changes to the authored inline loader must update its hard-coded CSP hash in the same change. - Asset constraint: `web_assets_data.c` is checked-in generated input to the build; do not hand-edit or regenerate casually. ## SSH @@ -79,19 +80,19 @@ This is a semantic map, not a complete file inventory. Start here, then read the - 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 - Flow: role `user` -> broker; role `admin` -> `admin_ssh_console` -- Ownership: one task pinned to core 1 is the sole wolfSSH caller; two fixed generation-tagged slots. -- Security constraint: shell/PTY only; no exec, file transfer, forwarding, or subsystems. +- 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. ## Users, authentication, and authorization **Responsibility:** persist bounded accounts, verify passwords/SSH keys, issue secret-free principals, and enforce account invariants. -- Files: `src/user_database.{h,c}`, `src/user_console.{h,c}`, `src/admin_command_gate.{h,c}` +- Files: `src/user_database.{h,c}`, `src/user_console.{h,c}`; `src/admin_command_gate.{h,c}` is currently a narrow recursive wrapper used only by the `user` command handler, not the global command serializer - Interfaces: init/migration/recovery, authenticate, principal-currentness, account/password/role/key mutations, snapshots -- Called by: web and SSH authentication; console administration; transport revocation checks -- Dependencies: NVS, secure random, mbedTLS cryptography, web/SSH revocation hooks at command layer +- Called by: web and SSH authentication/currentness checks and console administration +- Dependencies: NVS, secure random, mbedTLS cryptography; after a committed command-layer mutation, best-effort web/SSH revocation calls supplement authoritative transport currentness checks - Ownership: database mutex protects live records; password authentication runs PBKDF2 outside it and revalidates afterward, while mutation locking must be checked per operation. -- Authorization: UART0 owns bootstrap/recovery; current admins may use admin SSH for operational commands; HTTPS currently treats both roles alike. +- Authorization: UART0 exclusively owns initial administrator bootstrap and unavailable-database recovery; current admins may use admin SSH for other commands unless handler policy denies them. HTTPS currently treats both roles alike. - Constraint: final administrator cannot be deleted or demoted; transport principals must be rechecked after mutations. ## Administration console infrastructure @@ -103,9 +104,9 @@ This is a semantic map, not a complete file inventory. Start here, then read the - Called by: startup, UART0 frontend, role-`admin` SSH transport - Dependencies: ESP-IDF console/linenoise, all command handlers, user-principal currentness - Flow: `UART0/admin SSH -> bounded request queue -> one dispatcher -> esp_console_run()` -- Ownership: dispatcher is sole `esp_console_run()` caller; SSH owner remains sole wolfSSH caller. -- Lifecycle: remote session tokens include slot generation; output/history/prompt buffers are fixed and wiped on close. -- Constraint: one slow command or prompt serializes all administration. Remote self-affecting actions use deferred control after output drain. +- Ownership: dispatcher is sole `esp_console_run()` caller; the SSH owner exclusively performs post-initialization wolfSSH runtime calls. +- Lifecycle: remote session tokens include slot generation; fixed output/history/prompt state is wiped immediately on idle close or after an executing handler returns. +- Constraint: one slow command or prompt serializes all administration. Admin SSH is unavailable until command registration and UART frontend creation complete; supported deferred actions wait only for a bounded application-buffer drain heuristic. ## Wi-Fi @@ -116,18 +117,18 @@ This is a semantic map, not a complete file inventory. Start here, then read the - Called by: startup, console, local UI, ESP event callbacks - Dependencies: secure random for default AP password, NVS, ESP-NETIF/Wi-Fi/events, 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. +- 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. ## Local display and controls -**Responsibility:** own OLED I2C/framebuffer operations and present read-only status plus constrained button actions. +**Responsibility:** own OLED I2C/framebuffer operations and present status plus constrained button actions. - Files: `src/local_display.{h,c}`, `src/local_status_ui.{h,c}`, `src/local_boot_animation.{h,c}`, `src/local_ui_config.{h,c}`, `src/local_ui_console.{h,c}` - Interfaces: display init/frame/draw/commit/snapshot; UI start/activity/config; versioned NVS settings - Called by: startup, local UI task, diagnostics, display console - Dependencies: copied snapshots/public APIs from serial, broker, USB, Wi-Fi, web, SSH - Ownership: `local_display` solely owns I2C0 and framebuffer mutex; a frame belongs to its initiating task. -- Lifecycle: low-priority permanent UI task; optional OLED failures are nonfatal and recover through a bounded reprobe. +- Lifecycle: the low-priority task is firmware-lifetime only if button GPIO initialization succeeds; it still runs with an absent panel so a press can reprobe after successful I2C bus setup. Failed bus creation is not recoverable by that reprobe, and `display` configuration commands depend on the UI task. - Constraint: collect service snapshots before I2C; local UI never joins broker or handles secrets. ## Hardware and diagnostics @@ -139,7 +140,7 @@ This is a semantic map, not a complete file inventory. Start here, then read the - Called by: startup and `debug`/`status` commands - Dependencies: physical RS-232 owner, serial/display services, ESP-IDF GPIO/UART/I2C/LED drivers - Ownership: RS-232 diagnostics refuse to run while the service owns the port; display diagnostics reuse `local_display`. -- Constraint: wiring and voltage assumptions are safety-relevant; verify target hardware before running diagnostics. +- Constraint: wiring and voltage assumptions are safety-relevant; verify target hardware before running diagnostics. RGB LED initialization is currently boot-fatal, and its colors report diagnostic state rather than aggregate firmware health. ## Where should I look? diff --git a/docs/agent/current-state.md b/docs/agent/current-state.md index 9a2fd93..f0ef3f9 100644 --- a/docs/agent/current-state.md +++ b/docs/agent/current-state.md @@ -4,7 +4,7 @@ This file is working memory. Update it during active work and before handoff; do ## Development state -Based on current source plus `README.md` and `docs/roadmap.md`: +Based on checked-in source plus `README.md` and `docs/roadmap.md`: - Hardware characterization, serial service, session broker, USB CDC, Wi-Fi, HTTPS/WebSocket, SSH serial transport, and local display/control are implemented and documented as target-hardware validated. - Phase 8A role-based user storage/UART0 administration and Phase 8B role-aware HTTPS/SSH authentication and targeted revocation are documented as target-hardware validated. @@ -13,20 +13,6 @@ Based on current source plus `README.md` and `docs/roadmap.md`: - Security/production hardening, OTA, BLE evaluation, advanced networking, and optional filesystem features remain future roadmap work. - Reserved OTA, coredump, NVS-key, and storage partitions do not imply those runtime features are implemented. -The normal build is `pio run`. No automated host/unit-test command is defined in the repository; important validation is hardware-oriented. - -## Implemented capability summary - -- One UART1/MAX3243 RS-232 service with RAM working configuration, explicit persistence commands, and explicit start/stop. -- Generation-safe broker with up to eight clients, one writer, multiple observers, bounded per-client output/events, and drop accounting. -- Native USB CDC-ACM, two browser WebSocket sessions over HTTPS, and two SSH slots. -- SSH role routing: users receive serial; administrators receive the shared bounded administration shell. -- Four-profile station Wi-Fi plus off/fallback/always AP policies and network diagnostics. -- Eight-user role database, three Ed25519/P-256 keys per user, PBKDF2 password verifiers, copied principals, and targeted revocation. -- Self-signed HTTPS identity, separate SSH host key, one-time WebSocket tickets, and fail-closed authentication when user storage is unavailable. -- UART0/admin SSH serialized command registry with transport-aware prompts, bounded remote output/history, and deferred self-affecting SSH actions. -- Optional SSD1315-compatible OLED status/control interface with persisted inactivity settings and bounded failure recovery. -- Hardware diagnostics for MAX3243/UART flow control and OLED/buttons. ## Clearly incomplete or transitional areas @@ -35,27 +21,21 @@ The normal build is `pio run`. No automated host/unit-test command is defined in - Browser authentication still uses HTTP Basic; integrated login/logout sessions are planned. - NVS encryption, secure boot/flash encryption review, authentication rate limiting, production certificate/provisioning policy, and OTA are not implemented. -## TODO/FIXME survey - -No authored `src/*.{c,h}` `TODO`, `FIXME`, `XXX`, or `HACK` markers were found during the initial architecture analysis. A TODO inside vendored `web_assets/xterm.css` is upstream asset content and not project work. ## Known inconsistencies These observations should be checked when touching the relevant area; they are not automatically bugs requiring unrelated cleanup. - `src/main.c` logs a Phase 7E startup banner although the implementation/roadmap is at Phase 8C. -- Manual completion candidates omit implemented `wifi next-profile`. - Some source comments still call shared commands UART0-only or call the current local status/control task read-only. -- `USER_DATABASE_LOAD_EMPTY` and the corresponding `main.c` log branch appear reserved or vestigial; the current missing-storage success path migrates valid legacy credentials. **Needs verification** before removing or repurposing. +- `USER_DATABASE_LOAD_EMPTY` is only an initialization/failure sentinel at the checked-in revision: every successful `user_database_init()` path returns `STORED` or `MIGRATED_LEGACY`, so `main.c`'s successful "new empty" log branch is unreachable. - SSH startup is currently gated on successful `web_security` initialization even though SSH uses separate host-key material. **Needs verification:** whether this coupling is intentional recovery policy or an accidental startup dependency. ## Items to verify in future work - Complete the documented Phase 8C target-hardware validation before marking it complete. - Confirm task-local Newlib standard-stream behavior if ESP-IDF/Newlib configuration changes; admin SSH command output relies on dispatcher-task stream redirection. -- Revalidate software-crypto/watchdog behavior before changing crypto acceleration or PSRAM placement. -- If HTTPD concurrency configuration changes, verify whether the boot-local Basic-authentication cache needs explicit locking. -- Treat serial-service exclusivity as an architectural contract: its public read/write APIs do not themselves prove that only the broker calls them. +- If HTTPD concurrency configuration changes, add locking around the boot-local Basic-authentication cache. ## Active Task diff --git a/docs/agent/design-decisions.md b/docs/agent/design-decisions.md index ec2ba03..f2706a5 100644 --- a/docs/agent/design-decisions.md +++ b/docs/agent/design-decisions.md @@ -8,7 +8,7 @@ Only constraints supported by implementation or current project documentation be **Rationale/evidence:** The broker is initialized after the serial service and all transport implementations connect broker clients. It is the normal serial RX consumer and TX gate. Project documentation requires one writer and multiple observers. -**Consequence for future changes:** New serial transports must become broker clients. Do not bypass writer checks or consume `serial_service` RX directly. Preserve binary transparency and avoid in-band ownership control. +**Consequence for future changes:** New serial transports must become broker clients. Do not bypass writer checks or consume `serial_service` RX directly. `serial_service_start()` is not idempotent, so admission code must reconcile check/start races as the existing transports do. Broker paths enter serial-service APIs while holding the broker mutex; preserve that lock order and do not call back into the broker while holding the serial state mutex. Preserve binary transparency and avoid in-band ownership control. **Relevant files:** `src/session_broker.{h,c}`, `src/serial_service.{h,c}`, `src/usb_cdc_transport.c`, `src/web_serial_transport.c`, `src/ssh_transport.c` @@ -28,7 +28,7 @@ Only constraints supported by implementation or current project documentation be **Rationale/evidence:** The code has explicit `NONE`, `PHASE0`, `SERVICE`, and `FAULT` hardware states plus broker client/writer IDs. -**Consequence for future changes:** A writer lease never authorizes direct UART/GPIO access. Hardware tests must claim `PHASE0`; production service must claim `SERVICE`. Ambiguous cleanup must keep the transceiver safe and require reboot rather than clearing fault casually. +**Consequence for future changes:** A writer lease never authorizes direct UART/GPIO access. Active hardware tests must claim `PHASE0`; the production service must claim `SERVICE`. Boot-time static-safe GPIO setup and service-owned static-mode restoration are explicit exceptions to this cooperative gate. Ambiguous cleanup must keep the transceiver safe and require reboot rather than clearing fault casually. **Relevant files:** `src/rs232_port_owner.{h,c}`, `src/rs232_hw_test.c`, `src/serial_service.c`, `src/session_broker.c` @@ -72,23 +72,23 @@ Only constraints supported by implementation or current project documentation be **Relevant files:** `src/admin_ssh_console.c`, `src/main.c`, `src/console_input.c`, all `src/*_console.c` -## Self-affecting admin SSH actions drain output before execution +## Selected self-affecting admin SSH actions use bounded deferred control -**Decision:** Remote reboot, SSH stop/disconnect, and host-key replacement are deferred until acknowledgement output leaves the administration and transport buffers. +**Decision:** Remote reboot, SSH stop/disconnect, and host-key rotate/reset are deferred until command state and administration/transport application buffers appear drained, with a ten-second limit and short final delay. -**Rationale/evidence:** `admin_ssh_console` has a separate bounded control task and pending-action state. Immediate execution would sever the session before confirmation is delivered. +**Rationale/evidence:** `admin_ssh_console` has a separate bounded control task and pending-action state. The check is a best-effort application-buffer heuristic, not peer-delivery confirmation. User account mutations and their immediate revocation calls do not use this path. -**Consequence for future changes:** Commands that invalidate their own transport/session must integrate with deferred control rather than acting synchronously from the dispatcher. Prevent new input while the action is pending. +**Consequence for future changes:** Actions that would invalidate their own SSH transport should integrate with deferred control when acknowledgement preservation matters. Prevent new input while an action is pending, keep the wait bounded, and do not describe it as guaranteed delivery. **Relevant files:** `src/admin_ssh_console.c`, `src/system_console.c`, `src/ssh_console.c`, `src/ssh_transport.c` ## Authentication uses copied principals and fail-safe currentness checks -**Decision:** Network sessions retain secret-free copied principals. Account mutations invalidate generations/IDs, explicitly request targeted transport revocation at the command layer, and rely on ongoing currentness checks as the fail-safe. +**Decision:** Network sessions retain secret-free copied principals. Account mutations invalidate generations/IDs; after commit, the command layer requests best-effort targeted transport revocation, while ongoing currentness checks are authoritative. **Rationale/evidence:** `user_database` issues principals without secrets; web/SSH check currentness during admission and active sessions. Mutating console paths call transport revocation hooks. -**Consequence for future changes:** Do not retain pointers to database records or treat login as permanently authoritative. New authenticated sessions/transports must revalidate at admission, before sensitive input, and periodically or on relevant events. Database mutation APIs alone do not perform transport notification. +**Consequence for future changes:** Do not retain pointers to database records or treat login as permanently authoritative. New authenticated sessions/transports must revalidate at admission, before sensitive input, and periodically or on relevant events. Database mutation APIs alone do not perform transport notification, and notification failure must not roll back an already committed mutation. **Relevant files:** `src/user_database.{h,c}`, `src/user_console.c`, `src/web_server.c`, `src/web_serial_transport.c`, `src/ssh_transport.c` @@ -98,7 +98,7 @@ Only constraints supported by implementation or current project documentation be **Rationale/evidence:** Serial, Wi-Fi, local UI, web security, users, and SSH security each validate schema/size and own their namespace. User/security mutations build and validate candidate state before committing it; security modules avoid silently replacing an established identity. -**Consequence for future changes:** Add schema versions and transactional candidate validation. Do not overwrite unknown records automatically; provide explicit migration/reset behavior. Preserve the distinct persistence contracts: explicit save/load/default/reset for working configuration, atomic commit-or-fail for user and identity mutation. +**Consequence for future changes:** Add schema versions and transactional candidate validation. Do not overwrite unknown records automatically; provide explicit migration/reset behavior. Preserve the distinct persistence contracts: explicit save/load/default/reset for working configuration and per-blob commit-before-live-install for user and identity mutation. Pre-bootstrap legacy credential rotation spans `web_sec/material` and `user_db/database`, is not cross-namespace atomic, and relies on boot reconciliation after interruption. **Relevant files:** `src/serial_config.c`, `src/wifi_config.c`, `src/local_ui_config.c`, `src/web_security.c`, `src/user_database.c`, `src/ssh_security.c` @@ -134,11 +134,11 @@ Only constraints supported by implementation or current project documentation be ## Hardware and library access has designated owners -**Decision:** The serial task owns UART1 while active, `local_display` owns I2C/framebuffer access, the SSH owner task alone calls wolfSSH, and the console dispatcher alone runs registered commands. +**Decision:** The serial task owns UART1 while active, `local_display` owns I2C/framebuffer access, the SSH owner task owns runtime wolfSSH contexts/calls after caller-side library initialization, and the console dispatcher alone runs registered commands. **Rationale/evidence:** These constraints are enforced by module structure, mutex/task assertions, and transport indirection. Original rationale varies; the observable effect is serialized library/hardware access. -**Consequence for future changes:** Cross-task requests should use existing queues/public APIs. Do not call wolfSSH, mutate display frames, or run console handlers from arbitrary tasks. +**Consequence for future changes:** Cross-task requests should use existing queues/public APIs. Do not make post-initialization wolfSSH calls, mutate display frames, or run console handlers from arbitrary tasks. **Relevant files:** `src/serial_service.c`, `src/local_display.c`, `src/ssh_transport.c`, `src/admin_ssh_console.c` @@ -146,7 +146,7 @@ Only constraints supported by implementation or current project documentation be **Decision:** wolfSSL ESP32 AES/SHA acceleration is disabled, and HTTPS uses software AES for PSRAM-backed TLS records. Internal task stacks are retained where cache-disable safety matters. -**Rationale/evidence:** Root `CMakeLists.txt` disables wolfSSL hardware crypto. The roadmap records a reproduced watchdog stall in mbedTLS external-RAM hardware-AES DMA and uncoordinated mbedTLS/wolfSSL hardware locks; the software-crypto build passed the documented concurrency retest. +**Rationale/evidence:** Root `CMakeLists.txt` disables wolfSSL hardware crypto. The roadmap reports a reproduced watchdog stall involving mbedTLS external-RAM hardware-AES DMA, uncoordinated mbedTLS/wolfSSL hardware locks, and a successful software-crypto concurrency retest; no standalone execution record is checked in. **Consequence for future changes:** Do not remove these definitions as a performance cleanup. Any re-enablement needs target-hardware concurrency testing with simultaneous USB, WebSocket, SSH, and serial traffic plus watchdog/stack telemetry. @@ -158,6 +158,6 @@ Only constraints supported by implementation or current project documentation be **Rationale/evidence:** `src/CMakeLists.txt` lists generated data as a source, and `web_assets/SOURCES.md` documents pinned versions, hashes, and deterministic gzip inputs. -**Consequence for future changes:** Edit authored web UI separately. When dependency assets change, follow the documented provenance/generation process and review generated diffs; do not hand-edit arrays or regenerate assets during unrelated work. +**Consequence for future changes:** Edit authored web UI separately. Changes to its inline bootstrap loader must update the hard-coded CSP hash atomically and preserve the response security policy. When dependency assets change, follow the documented provenance/generation process and review generated diffs; do not hand-edit arrays or regenerate assets during unrelated work. **Relevant files:** `web_assets/SOURCES.md`, `web_assets/generate_embedded_assets.py`, `src/web_assets_data.{h,c}`, `src/web_ui.c`