Compare commits
4
Commits
56aa97d60d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c43a088ce8 | ||
|
|
d629a0ff51 | ||
|
|
e0780f58e0 | ||
|
|
121491bab4 |
@@ -0,0 +1,130 @@
|
|||||||
|
# HW_VERIFICATION.md — the device half of this roadmap's verification
|
||||||
|
|
||||||
|
The host gate (`.agents/validate.sh`) proves what can be proven without a board:
|
||||||
|
the pure-module suites under sanitisers, the page and tool suites, both firmware
|
||||||
|
builds, repo hygiene. It deliberately contains **no** `-t upload`, no `curl` to a
|
||||||
|
device, no network fetch — an upload in a gate is a device side effect on
|
||||||
|
somebody else's schedule (AGENTS.md).
|
||||||
|
|
||||||
|
That leaves claims that only a device can answer. AGENTS.md's standard:
|
||||||
|
*"A feature that lives on the device is not done when the source builds — it is
|
||||||
|
done when the device is running it and something observed it."* This file pairs
|
||||||
|
every such claim in phases 02–05 with the exact command that observes it and the
|
||||||
|
observation that counts, and states the rule that keeps a unit from ever blocking
|
||||||
|
on a board nobody is holding.
|
||||||
|
|
||||||
|
`bash tools/hw_probe.sh` (printed at the end of every gate run, exit status
|
||||||
|
deliberately ignored) says what is reachable *right now* — serial nodes,
|
||||||
|
`keyrelay.local`, http/https, `uv`/`node`, target-side evdev nodes — without
|
||||||
|
side effects and without blocking. It informs; it never gates and never records
|
||||||
|
an outcome by itself.
|
||||||
|
|
||||||
|
## Baseline (measured, phase 01 task 01 gate run)
|
||||||
|
|
||||||
|
Later phases quote a **delta against these numbers**, never a fresh guess.
|
||||||
|
|
||||||
|
| metric | value |
|
||||||
|
|---|---|
|
||||||
|
| `bridge` RAM | 26.7 % (87 592 / 327 680 B) |
|
||||||
|
| `bridge` Flash | 36.1 % (1 135 293 / 3 145 728 B) |
|
||||||
|
| `console` RAM | 16.8 % (55 200 / 327 680 B) |
|
||||||
|
| `console` Flash | 33.6 % (1 056 081 / 3 145 728 B) |
|
||||||
|
| host checks | **3406** (3162 C++ + 160 `relayctl` + 84 page), green in the normal **and** the `SANITIZE=1` run |
|
||||||
|
| served page | **48 598 bytes** — the raw-string length the gate prints every run; a reflashed device must serve exactly this many |
|
||||||
|
|
||||||
|
Phase 02 additionally quotes the heap cost of one and two live TLS sessions as a
|
||||||
|
delta against the `bridge` RAM/heap line above.
|
||||||
|
|
||||||
|
## Claims that need hardware
|
||||||
|
|
||||||
|
Outcome vocabulary, written into the row **in the phase's commit** by whoever ran
|
||||||
|
the command: `ok` · `skipped (no device)` · `failed: <what>`. Blank = not run
|
||||||
|
yet. A row nobody ran is recorded `skipped (no device)` at phase close — an
|
||||||
|
unobserved claim is never back-filled with a guess (ASSUMPTION: unattended runs
|
||||||
|
may have a board attached that nobody is watching, and a second machine for
|
||||||
|
`watch_target.py` is rarer than either).
|
||||||
|
|
||||||
|
### Every phase that touches the keystroke path (02–05 regression baseline)
|
||||||
|
|
||||||
|
| claim | command | expected observation | outcome |
|
||||||
|
|---|---|---|---|
|
||||||
|
| keystrokes reach the target | `python3 tools/watch_target.py --seconds 60` **on the target**, driven from the page or `tools/relayctl.py` | one press **and** release per key with timestamps; `still held` absent from the summary | skipped (no passphrase) — task 04, 2026-11-09: the board is reachable on `:443` and the TLS/`wss://` transport rows below are measured, but this board's control passphrase is not the default (`status` says `dflt:0`) and a verification run will not change a credential on a device it cannot put back. To close it: `console.py token <known>` on a board you are willing to re-provision, then drive `wss://` |
|
||||||
|
| a chord is the keys its label claims | `python3 tools/relayctl.py --host keyrelay.local chord 04 2b` while `watch_target.py` runs | `LAlt press` then `Tab press` — and **not** `LCtrl`. The watcher is the only thing that says what the target received (README: Alt+Tab was `Ctrl+Alt+A` on the wire once) | skipped (no device run) — phase-03 close, 2026-11-09. This row is still open for the same standing reason as every row in the phase-03 table below: the board is a live device this pass neither reflashed nor armed (arming means typing into whatever this operator's console has focus, and the passphrase is the operator's). Host-side the claim is pinned instead: `test_proto` asserts every labelled chord button's `data-chord` bytes against the combination its label promises, so `04:2b` is Alt+Tab or the suite fails. To close: reflash `bridge`, arm, run the command above with the watcher open |
|
||||||
|
| served page == built page | `curl -s http://keyrelay.local/ \| wc -c` (after phase 02 lands: `curl -sk https://keyrelay.local/ \| wc -c`) | byte-equal to the `page bytes:` line the gate prints. A mismatch means the page in flash is stale — reflash **and** reload any open tab | ok — re-measured 2026-11-09 **over TLS after the phase-02 flash**: `curl -sk https://10.2.0.22/` = 48598 and `curl -s http://keyrelay.local/` = 48598, both = the gate's count. **Task 04** re-ran it over TLS three times in a row: 48 598 bytes each time with `sha256` equal to the raw string in `src/web_page.h` (`f7aae9e5…c6f7`) — the page now leaves through `kr::Conn` at 1460 B per write and 5840 B per tick (34 chunks over ~9 `loop()` iterations) instead of one `WiFiClient::print`, so byte-equality across a multi-tick send is the transport claim. Original — measured 2026-11-09: `curl -s http://keyrelay.local/ \| wc -c` = 48598 = gate's count (board on the LAN, pre-TLS, serving over http as expected). Re-run by the phase 01 final pass, one capped attempt each: 200 / 48 598 bytes, 404 on `/nope`, 401 on `/status` without the token, zero `src=`/`href=` in the served bytes. The first cold probe returned nothing (`http_code=000`, glibc does not cache mDNS) and the retry worked — that retry is the documented behaviour, not a second attempt at the check |
|
||||||
|
| silence disarms | arm, kill the controller process without a clean close (or close the tab), wait `kNoClientTimeoutMs` (8 s), `python3 tools/relayctl.py --host keyrelay.local status` | `armed: 0`, and `watch_target.py` shows releases for anything held. Test the *timer*, not the socket close (README bug 10) | skipped (no passphrase), same reason as the row above. What task 04 *did* pin is the property this row depends on and that the transport rewrite could silently have moved: `_lastInboundMs` is stamped in exactly one place, gated on `_phase == WebSocket && _authed`, and neither the TLS handshake, a `/status` poll nor a viewer refresh reaches it — asserted structurally in `test_relayctl` (`the controller clock is stamped in exactly one place`, `no HTTP path can refresh the controller clock`) |
|
||||||
|
| nothing outlives the link | hold a key and a mouse button, then drop the link | every key up, every button up; mouse summary shows no `still held` | skipped (no device run) — phase-03 close, 2026-11-09, same standing as the row above. Phase 03 is the phase that makes this row sharper (a *handover* is a link teardown with a new occupant waiting, and the claim path must not keep anything alive), so the host-side pins are the ones named in the phase-03 table: `test_output`'s release-funnel suite and `test_relayctl`'s funnel-runs-before-adoption pin. To close: the run described in the phase-03 table |
|
||||||
|
|
||||||
|
### Phase 02 — HTTPS + `wss://`
|
||||||
|
|
||||||
|
Every row below was produced by one capped command on the board at `10.2.0.22` /
|
||||||
|
`keyrelay.local`, joined to the LAN, flashed with `bridge`. Two rows are honestly `skipped`:
|
||||||
|
the passphrase-dependent ones (arming/typing over a pinned session need a credential the
|
||||||
|
operator owns) and the rollback's device leg; one row is `failed` and fixed in the tree with
|
||||||
|
its device leg still open (the certificate's SAN, found at the final pass) — see the rows
|
||||||
|
themselves for what would close them and why it was not run.
|
||||||
|
|
||||||
|
| claim | command | expected observation | outcome |
|
||||||
|
|---|---|---|---|
|
||||||
|
| TLS handshake completes | `curl -sk https://keyrelay.local/status` or `openssl s_client -connect keyrelay.local:443 </dev/null` | 200 + status JSON / a session whose only verify failure is *self-signed*. `HTTP 000` / connection refused = listener not up | ok — measured 2026-11-09 on the flashed bridge: `openssl s_client` negotiates **TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384**; `curl -sk https://10.2.0.22/` → 48 598 bytes (= the gate's count); `/status` without the token over TLS → **401**, path still gated. `hw_probe`'s `https` line now reports `HTTP 200 (TLS listener is up)` |
|
||||||
|
| fingerprint printed **once**, on serial | serial console boot log after `nvs erase`; then `python3 tools/console.py cert` (opens the port — one attempt, capped) | SHA-256 fingerprint appears exactly once at first boot, on demand afterwards; **never** the key; survives reflashing (NVS) | ok (minus the `nvs erase` leg) — measured 2026-11-09: captured boot log shows `[tls] identity loaded from NVS (reprint with \`cert\`)` with **no** fingerprint on it, and `console.py cert` prints it on demand; the identity survived two reflashes and three reboots with the same fingerprint. `nvs erase` → first-boot printing was not re-run (the regeneration path had just been observed end-to-end). **Finding:** the *first* boot under the new firmware rotated the fingerprint once for a reason the boot log could not show (no host had the CDC open yet); `loadTlsIdentity()`/`storeTlsIdentity()` now print the failing gate and the record **lengths** (`stored records cert=… key=… fp=… -- shape/selfCheck`, `NVS put refused: …`, `read-back mismatch: …`) so a rotation can never again be a mystery — every later boot loads cleanly. **(That first
|
||||||
|
"rotation" is corrected two rows down: it was the `openssl -ext` artifact, not a regenerated
|
||||||
|
identity. The instrumentation stays worth having — it is what makes the next claim provable.)** |
|
||||||
|
| pinned fingerprint == handshake cert | `openssl s_client -connect keyrelay.local:443 </dev/null 2>/dev/null \| openssl x509 -noout -fingerprint -sha256` | same hex as the serial console printed; mismatch = two identities, investigate before pinning | ok — measured 2026-11-09: console `cert` `61:54:19:30:ed:df:e5:52:d1:ee:18:6f:cc:f9:57:c5:41:47:d4:15:61:26:1e:11:aa:49:4f:07:79:b1:af:f9` == the `x509 -fingerprint -sha256` of the live handshake, and the pair still matched after two further `reboot` cycles. **Corrected at the phase-02 final pass, same day:** the earlier mismatch recorded as `C8:76:F1:69:…` was **not a rotation** — it is what `openssl x509 -noout -fingerprint -sha256` prints when `-ext <oid>` is passed in the same invocation, on OpenSSL 4.0.3, for *this same* certificate. Reproduced byte for byte: the peer certificate captured with `s_client`, written with `-outform der`, hashes to `61541930…b1aff9`; the same file run through `-fingerprint -sha256 -ext subjectAltName` prints `C8:76F1…E6D2` *and* “No extensions in certificate”. So: take the fingerprint with `-fingerprint -sha256` alone (or `sha256sum` of `-outform der`) and never with `-ext` in the same command, or a stable identity looks like a rotation. Re-measured at the final pass (`console.py cert`, one capped attempt, and `s_client` + `openssl x509 -outform der | sha256sum`): console `61:54:19:30:…:b1:af:f9` == `61541930eddfe552…79b1aff9`, one identity, one board, no rotation since the instrumented boot. |
|
||||||
|
| `:80` is redirect-only | `curl -s -o /dev/null -w '%{http_code} %{redirect_url}' http://keyrelay.local/` | 3xx to `https://`, and **no page bytes** on plaintext | ok — measured 2026-11-09 on the task-04 flash, one capped `curl` each: `/` → **301** `Location: https://10.2.0.22/`; `/index.html` → **301** to the same path over `https://`; `/nope` → **301** to `/` (an unknown target is a 404 on 443, and an arbitrary request target is never echoed into a `Location`); `/status?token=…` → **426** with `Upgrade: TLS/1.2, HTTP/1.1`, **no `Location`**, and the body verified free of the token (`grep -c` of the value → 0). No plaintext path returns page bytes any more |
|
||||||
|
| plaintext `/ws` refused, not downgraded | a `ws://` upgrade attempt on `:80` (e.g. `curl -i -N` with upgrade headers, or `relayctl.py` pointed at plaintext) | refused loudly (per README error style), never a silent plaintext session | ok — measured 2026-11-09: `curl -i -N` with `Upgrade: websocket` on `:80` → **426 Upgrade Required**, `Connection: Upgrade`, `Upgrade: websocket, TLS/1.2, HTTP/1.1`, body `plaintext websocket refused; reconnect over wss:// (https://<host>)` — no `101`, no session; the board's own counter moved (`link controller=no rejected=1`). `relayctl`'s plaintext path now raises the same sentence via `handshake_refusal()` rather than the old "board refused the websocket upgrade". **Re-measured at the phase-02 final pass** (the bodies were reworded after that flash): `/ws` upgrade → **426**, `Upgrade: TLS/1.2, HTTP/1.1`, `Connection: close`, `Content-Length: 96`, body `keyrelay: this controller channel is TLS-only -- reconnect over wss://, the page is on https://`; `/status?token=<dummy>` → **426**, `Content-Length: 140`, **no `Location:`** (`grep -c location: → 0`) and the value not echoed (`grep -c <dummy> → 0`); `/` → **301** to `https://keyrelay.local/` with a 77-byte redirect body and **no page bytes**; over `:443`: `/status` without the token → **401**, `/nope` → **404**, `/` → 200 / 51 626 B / `sha256 dd16e195…f04e9` == the locally assembled page |
|
||||||
|
| TLS session heap cost | build RAM delta from the gate output + live free-heap readout the phase's `status`/serial path exposes, at 0 / 1 / 2 live sessions | quoted as a delta against the baseline table above; two sessions must fit without allocation failure | ok — measured 2026-11-09 from the serial `[tls] sessions=N heap=…` lines: boot `[tls] https :443 listening heap=187936`; **1 session ≈ 145.5 k, 2 sessions ≈ 107.9 k** (second session ≈ −37.6 k: the mbed TLS record buffers), close returns heap to 168–185 k. **Task 04** (transport view on both ports: 2 KB `:80` redirect ring + 1460 B page chunk + 64 B `tRing` per slot; gate RAM 97 240 → 99 616) measured in one run `sessions=1` `140 092` → `sessions=2` `109 228` → close one `144 836` → close all `182 256`: second session ≈ −31 k, and the memory comes back inside the band above. 20 further connect/disconnect cycles on `:443` ran in the same pass. Original 20 connect/disconnect cycles: the `sessions=1` readings held at 145.5–149.8 k with **no downward drift** — no leak. Slot cap enforced live: with two sessions held, a third got a **fatal TLS alert (handshake_failure, 40) in 0.0 s** while both listeners kept serving. Stall check: a connection that completed the handshake and sent nothing did **not** block `loop()` — the full page still served over a second connection during the stall, and the stalled socket was closed by the ~10 s deadline |
|
||||||
|
| `-D KR_TLS=0` rollback behaves like today | build the rollback env, flash once, `curl -s http://keyrelay.local/ \| wc -c` + page connects over `ws://` | page and `ws://` work exactly as pre-phase-02; README documents the rollback | **build leg ok, device leg skipped (not flashed)** — task 06, 2026-11-09. The rollback is now a first-class environment, `[env:rollback]`, built by `.agents/validate.sh` on every run alongside `bridge` and `console`: RAM 28.5 % (93 456 B) / Flash 36.4 % (1 146 137 B) against the TLS `bridge` 30.4 % (99 616 B) / 39.6 % (1 245 353 B), so TLS costs +6 160 B RAM and +99 216 B flash. *(Final pass, same day: after the SAN fix `bridge` is 99 616 B / 1 246 145 B — RAM unmoved, +792 B of flash — and the rollback image is **byte-for-byte unchanged** at 93 456 B / 1 146 137 B, because the fix lives entirely inside `#if KR_TLS`; TLS now costs +100 008 B of flash.)* That the flag *took* is measured, not assumed: `xtensa-esp-elf-nm .pio/build/rollback/firmware.elf \| grep -c mbedtls_ssl_` → **0** (115 in `bridge`), `… \| grep -c mbedtls_x509write` → **0** (15), `strings .pio/build/rollback/firmware.elf \| grep -c "pin this certificate"` → **0** (1). The device half was deliberately not run: flashing the rollback and back is two write cycles on a board that is a live HID keyboard into the machine running this pipeline, `to_bootloader.py` strands it in ROM download mode if anything goes wrong after that point, and no criterion for task 06 needed it. To close it, in order: `uv run --python 3.14 --with platformio python -m platformio run -e rollback -t upload` → `timeout 10 curl -s http://keyrelay.local/ \| wc -c` (expect the same 51 626 the gate prints, on plaintext, with `var KR_TLS_BUILD=0;` from `curl -s http://keyrelay.local/ \| grep -o 'var KR_TLS_BUILD=[01];'`) → `timeout 10 curl -s -o /dev/null -w '%{http_code}' https://keyrelay.local/` (expect 000/refused: no `:443` exists any more) → boot log line `[tls] TLS compiled out (KR_TLS=0) -- plaintext control channel` → `python3 tools/relayctl.py --plain --host keyrelay.local --token … status` → then reflash the TLS build (`run -e bridge -t upload`) and confirm `curl -sk` serves 51 626 again |
|
||||||
|
| TLS handshake latency and page fetch over TLS | `curl -sk -o /dev/null -w '%{time_appconnect} %{time_total}\n' https://keyrelay.local/` (and `…/status`), one capped run each | a handshake that completes in a time a browser operator will not mistake for a dead board, and a page that arrives whole over the chunked TLS send | ok — measured 2026-11-09 (task 06, read-only, board on the LAN): seven warm connections, **handshake 0.349–0.423 s**; page 51 626 B in **0.528–0.618 s** total; `/status` 0.382–0.434 s total. The first connection after ~10 min of quiet took **3.94 s** of handshake (one observation, recorded because it looks like a timeout from a browser and is a cold start). Both numbers are in README “Measured behaviour” with the command |
|
||||||
|
| served page carries the build marker and the pinning UI | `curl -sk https://keyrelay.local/ \| wc -c` + `grep -o 'var KR_TLS_BUILD=[01];'` + `sha256sum` against the locally **assembled** page (chunks + KR_TLS=1 branch) | 51 626 bytes, marker `=1`, `certFp`/`pTls`/`clearWarn` present, digest equal to the assembled TLS page | ok — measured 2026-11-09 after the task-05 flash: 51 626 bytes, `var KR_TLS_BUILD=1;`, all three new ids (6 matches), `sha256 dd16e195…f04e9` **byte-equal** to the locally assembled page — the multi-tick chunked send still delivers the changed page intact, and `curl -sI http://keyrelay.local/` is still a 301 |
|
||||||
|
| `relayctl.py` pinning, end to end | `--fingerprint 00…00` / the real fingerprint / `--ca <exported self-signed>` / `--insecure` / no argument / `--plain`, each `timeout 15`, dummy token | wrong pin aborts **before sending**; correct pin passes the pin and stops only at authentication; `--ca` verifies the self-signed cert with hostname checking on; `--insecure` warns on stderr every run; no argument → refusal at exit 2 without a socket; `--plain` reads the 426 as a TLS refusal, never as a passphrase problem | ok — measured 2026-11-09: wrong pin → `certificate fingerprint mismatch … Nothing was sent` (and the presented digest `61:54:19:30:ed:df:…:b1:af:f9` equals the serial `cert` value recorded above — the pin and the console agree on one identity across reflashes); correct pin, `--insecure` and `--ca /tmp/board.pem` all reached authentication and were refused there by `E1` (the board's passphrase is the operator's — I did not guess it, so **arming/typing over a pinned session is not device-confirmed in this row**, three `authfails` total, well under the 5-try lockout); no argument → the four-option refusal, exit 2, no connection attempted; `--plain` → `board refuses plaintext … wss:// on :443`, not one word about the passphrase |
|
||||||
|
|
||||||
|
| the certificate carries the name list `cert` prints | `openssl s_client -connect keyrelay.local:443 </dev/null \| openssl x509 -noout -text`, compared with `console.py cert` | the `X509v3 Subject Alternative Name` extension reads `DNS:keyrelay.local, DNS:keyrelay, IP:192.168.4.1`, so a client can check the name and not only the fingerprint | **failed → fixed in the tree, device leg needs `nvs erase`.** Found at the phase-02 final pass 2026-11-09 by reading the certificate instead of the code: `-text` printed the extension as `1.10.46.53.46.50.57.46.49.55` (not `X509v3 Subject Alternative Name`) and `-ext subjectAltName` said *no extensions*. A hex dump named the cause — the extension was framed `30 2f 06 09 32 2e 35 2e 32 39 2e 31 37 04 22 <the correct 34 bytes>`: the OID was written from the **ASCII text** `"2.5.29.17"` where the DER content bytes `55 1d 11` belong, because `mbedtls_x509write_crt_set_extension()` takes the OID as DER content bytes. So the served certificate has the right SAN *value* under a label no client reads, i.e. **no SAN**: encryption and pinning work, hostname validation cannot (browsers match SAN, not CN). Fixed in `src/tls_pem.h` (`kSanOidTlv`/`sanOidDer`, `sanExtensionFullDer` pinned to the 43 bytes OpenSSL writes for these three names, `derCarriesSan` requiring OID *and* value), used by `src/tls.cpp`, 33 new host checks in `test_tls_pem.cpp`; the regression fixture is the bytes above, and `derCarriesSan` answers no to it while `derContains` says yes. README bug 11. **To close the device leg** (deliberately not run here: `nvs erase` also forgets the passphrase and the Wi-Fi credentials, which the verification run cannot put back): reflash `bridge`, `nvs erase`, reboot, then `openssl s_client … \| openssl x509 -noout -text` must show `X509v3 Subject Alternative Name: DNS:keyrelay.local, DNS:keyrelay, IP:192.168.4.1`, the new fingerprint must appear exactly once in the boot log, and `console.py cert` must print that same value with **no** `!! the stored certificate does NOT carry that name list` warning. Until then `cert` prints that warning for this board — which it now does, because the check looks at the OID. |
|
||||||
|
|
||||||
|
### Phase 03 — session takeover
|
||||||
|
|
||||||
|
| claim | command | expected observation | outcome |
|
||||||
|
|---|---|---|---|
|
||||||
|
| a second `/ws` upgrade arrives as a pending claim, not a 503; a third socket is still refused (task 01) | after reflashing `bridge` from this tree: controller A connects and authenticates; a second socket does a raw `/ws` upgrade (`openssl s_client -connect keyrelay.local:443` + upgrade headers, or a python one-liner) and sends **one** deliberately-wrong `T`; a third connection opens in parallel; `watch_target.py` runs on the target throughout | second upgrade answers **101** (not 503), then exactly `E1:wrong passphrase` and close (one attempt only — the claim shares the board-wide 5-fail lockout); the third connection is refused (fatal TLS alert / close) and `slotrefused`/`rejected` move while both listeners keep serving; A keeps typing throughout and the silence clock never moves | **skipped (not run on device), 2026-11-09** — the live board runs the phase-02 firmware; the claim path is not flashed, and the authenticating half needs the operator's passphrase, which verification runs must not guess (the same call phase 02 recorded, and more pointed here: a wrong claim token feeds the shared lockout this task wires). Host-side the gate is green: 95 new `test_claim` checks (whitelist matrix over every `proto::Cmd` × auth × role, both deadline clocks at the boundary and across the `millis()` rollover, oversized-frame refusal through the real codec), 4921 checks in both suite runs, all three firmware builds; bridge RAM 99 728 B vs the phase-02 99 616 B — **+112 B**, the claim's whole footprint (64 B buffer + reader state + role/auth bytes + the 4 B clock, matching the comment in `control_server.h`) |
|
||||||
|
| takeover releases the incumbent's held keys | after reflashing `bridge` from this tree, two controllers on one board: incumbent holds a key, newcomer takes over (`relayctl.py --take-over` — landed in task 03; or a raw `/ws` upgrade sending `T<passphrase>` then `X`), all while `watch_target.py` runs **on the target** | `watch_target.py` shows the key **up** *before* the new session's first key-down — the ordering is the whole claim | **skipped (no device run), 2026-11-09** — same standing as the task-01 row directly above: the live board still runs the phase-02 firmware (no claim path), and both controllers need the operator's passphrase, which a verification run must not guess into the shared 5-fail lockout. The ordering is what the run will observe; on the host it is *pinned*: `test_relayctl.py` fails if `closeLink("taken over")` ever moves after `_authed = true` in `handOverClaim` (demonstrated failing on an inverted tree, then reverted), and the funnel it runs is `test_output`'s release-funnel suite. To close: reflash `bridge`, run the two-controller procedure above, capture the transcript |
|
||||||
|
| the board comes back disarmed | `python3 tools/relayctl.py --host keyrelay.local status` immediately after the handover | `armed: 0` across the handover; the newcomer must arm again on purpose; nothing in the handover path arms | **skipped (no device run), 2026-11-09** — same reason as the row above. Host-side the sentence is the (b) pin of `test_relayctl.py`'s **bridge handover** section: no `setArmed` / `armed = true` / `arm(` exists in `handOverClaim`'s code lines nor in the controller-side `X` case (demonstrated failing when a `…_rearm();` line was inserted, then reverted); `forgetPendingWrite()` is the one TLS state the adoption touches, and it writes no arming state. To close: the same reflash and run, `armed: 0` in the status read between handover and the newcomer's first `A1` |
|
||||||
|
| a third controller is still refused | a third `/ws` upgrade while two sessions exist | `E3` — takeover consumes the one spare slot, it does not open the door | **skipped (no device run), 2026-11-09 (phase close)** — the live board still runs the pre-phase-03 firmware, so the second session would be the only claim-shaped thing on it, and opening the two sessions first needs the operator's passphrase. Host-side: `test_claim` pins `claim::acceptsUpgrade()` (a slot that already carries a Claim takes no second upgrade) and `test_tls_session` walks the admission table (`controller`, `spare`, `refused`) with the cap checked before the free slot. Note the mechanism, as the README's Security model states it: a third *socket* never reaches HTTP, so what it gets is the slot refusal — a fatal TLS alert record on `:443`, a close on `:80`, counted in `slotrefused` — and `503`/`E3` is the sentence for an upgrade that reaches the spare slot and cannot be a claim (a malformed upgrade, or that slot already holding one). To close: reflash `bridge`, hold controller + claim, open the third connection, read `slotrefused` moving while both listeners keep serving |
|
||||||
|
| the locked-out operator sees `held by another controller` and takes the bridge with one click; the incumbent's page says it was taken over, disarmed, in its log (task 03) | reflash `bridge` from this tree (check the build's own exit status first); laptop tab holds the bridge; on the phone open `https://keyrelay.local/`, type the passphrase, connect — the pill must read `held by another controller`, the **take over** button appears, and nothing further leaves the phone until it is clicked; press it while `watch_target.py` runs **on the target**; then read the laptop tab | pill `held by another controller`; one click sends `T` then `X` and nothing else; the target shows every key and button up before the phone's next key; the phone is DISARMED after the handover; the laptop tab shows pill `taken over` and a log line naming "another controller took over", and does not reconnect (no second `/ws` from it); `--take-over` from the terminal produces the same frames and states | **skipped (no device run), 2026-11-09** — same standing as the rows above: the live board still runs pre-phase-03 firmware, the gesture needs the operator's passphrase on two devices (guessing feeds the shared 5-fail lockout), and a phone-and-laptop dance is not a headless attempt. Host-side the gate is green: `test_page.js` executes exactly this flow (claim-close → button appears → nothing sent until the click → click sends `T` then `X` and nothing else → first status logged as the DISARMED start; `E12` disarms, releases the mouse, ends a typing run, marks `taken over`, and stays down through the close; `E3` keeps its own "no room" sentence), and `test_relayctl.py` pins the CLI's `T,X,H` ordering, the default refusal naming `--take-over`, and the free-board no-op. To close: the reflash and two-device run above, transcript captured |
|
||||||
|
|
||||||
|
### Phase 04 — responsive page
|
||||||
|
|
||||||
|
| claim | command | expected observation | outcome |
|
||||||
|
|---|---|---|---|
|
||||||
|
| page still byte-exact after the restructure | the generic `curl … \| wc -c` row above | equals the gate's new `page bytes:` count (the count moves when the page does — that is fine; equality with the gate is the check) | **skipped (board not reflashed), 2026-11-09 (task 01)** — one capped attempt was run *before* the page edit: `timeout 15 curl -sk https://keyrelay.local/ \| wc -c` → **51 626** and `sha256 dd16e195…f04e9`, byte-identical to the phase-02 close record, so the live board still runs pre-phase-03 firmware and the measurement path works. The 60 780-byte page of this tree is only served after `run -e bridge -t upload`, deliberately not run here: the board is a live HID keyboard into the machine running this pipeline and `to_bootloader.py` strands it if anything goes wrong unattended (same call phase 02 recorded). To close: reflash, then `timeout 15 curl -sk https://keyrelay.local/ \| wc -c` must equal the gate's `page bytes:` line for this tree. **Task 02** (the prose fold) re-ran that one capped read-only probe after the edit: **51 626** bytes, `sha256 dd16e1954a68…` — identical to the task-01 and phase-02 records, so the live board still runs the pre-phase-03 image and this tree's **59 223**-byte page (the gate's `page bytes:` line, equal in both builds) is not on it yet. Measurement path proven, byte comparison still open until a flash. **Task 03** (docs and suite) re-ran the same single capped probe: `timeout 15 curl -sk https://keyrelay.local/ | wc -c` → **51 626**, `sha256 dd16e1954a68…f04e9` — byte-identical to the task-02 record, and this tree's locally assembled page hashes `dc26b441…b634` at **59 223** B (the count the gate prints and the count README's Controller-page section now quotes with this exact `curl` command named as the device-side measurement). README == gate == assembled bytes; device equality still opens by the standing no-unattended-reflash call (live HID keyboard into the pipeline host) | **Final pass, 2026-11-09:** one more capped read-only probe — `timeout 15 curl -sk https://keyrelay.local/ \| wc -c` → **51 626**, `sha256 dd16e195…f04e9`, byte-identical to the task-01/02/03 records (the live board has not been reflashed all phase), while this tree assembles to **59 262 B** / `b9414d2f9847b774…` after the `summary` thumb-floor fix; README (×2), `config.h` (×2) and `test_conn.cpp`'s `kServedPageBytes` are re-pinned to 59 262 and the gate's `page bytes:` line agrees. |
|
||||||
|
| one column on a phone, ARM thumb-reachable | open `https://keyrelay.local/` on a phone at ~360 px | single column, no horizontal scroll, sticky ARM bar on screen, log collapsed and not pushing ARM off screen | **skipped (no device), 2026-11-09 (task 01); still skipped for task 02, 2026-11-09** — no phone is reachable headlessly, and the page must be reflashed first (row above). Task 02 adds one thing for whoever runs it: the folded notes (`<details>` under Special keys, Connection, Mouse, Send text and the footer) must be readable and tappable at 360 px, and the State card's counters must wrap into a couple of lines rather than stacking eight rows — the counters stay on screen because they are the "is it typing" evidence. Host-side the layout is pinned instead: 17 new `test_page.js` checks assert the served bytes carry `viewport-fit=cover` with pinch zoom intact, the one 720 px breakpoint (one column; `main` as flex column, which is what makes the sticky ARM bar actually stick), the 16 px input floor (focus-zoom), the log height cap + `overscroll-behavior:contain`, the 44 px `pointer:coarse` floors, safe-area bottom padding, and ARM still first in `main`. To close: reflash `bridge`, open `https://keyrelay.local/` on a ~360 px phone — one column, no horizontal scroll at 360/768/1440 px, ARM bar visible while scrolling, focusing the passphrase field does not zoom the page. **Task 03** (2026-11-09): still `skipped (no device)` — no phone is reachable headlessly and the page must be reflashed first. The phone checklist for whoever runs it, from the phase's completion criteria: 360 px width, one column, no horizontal scroll, ARM bar reachable with a thumb at the top of the scroll, focusing the passphrase input does not zoom, and each folded note (Special keys, Connection, Mouse, Send text, footer shortcuts) expands with a tappable summary. Host-side task 03 adds the structural teeth for the last one: `test_page.js` pins exactly five `<details>`, each with a summary naming what is behind the tap and real sentences inside, and proves the fold checks fail on deletion (mutation-run, then restored byte-for-byte) |
|
||||||
|
| the responsive layout, observed in a real browser at phone widths (final pass) | assemble the served bytes from `src/web_page.h` (raw-string chunks + each marker branch in turn), serve them on `127.0.0.1`, and measure them in headless Chromium at 360×640, 390×844, 768×1024 and 1440×900 with touch/`pointer:coarse` emulated | no horizontal scroll at any width; ARM reachable without scrolling and sticky at the bottom of a 400-line log; one column and a height-capped, self-scrolling log below the breakpoint; every press target ≥ 44 px on a coarse pointer; the five folds open on a tap and stay inside the viewport; no script error; the safety sentences present in *rendered text* — and the same measurement against the pre-phase-04 page, where the sticky/flex-column/log-cap/thumb-floor rows must fail | **ok (browser, not a phone), 2026-11-09 (phase-04 final pass)** — all five viewports pass on this tree's page (59 262 B, both marker branches equal); the same script on the phase-start page (59 286 B) fails exactly the four rows the phase changed: ARM not sticky (`z-index:auto`, scrolled to −1989 px), `main` still `display:grid`, no log cap, and 19 targets at 34 px. **It also found a defect the byte-level suite could not see, now fixed:** the five `<summary>` elements task 02 introduced rendered **17 px** tall on a coarse pointer — the only route to five pinned safety sentences, on a control below the page's own 44 px thumb floor, with every served-byte assertion green. `summary` is now in the `pointer:coarse` rule (page 59 223 → 59 262 B, re-pinned in `config.h`/`test_conn.cpp`/README); the floor is asserted in `test_page.js` (219 → 220) **and** measured here, and deleting the rule fails both (demonstrated, then restored byte-for-byte). This is not the phone row below: no hand-held device was involved, `env(safe-area-inset-*)` is 0 on a desktop screen (so the inset is checked in the declared CSS, where it means something), and iOS Safari's focus-zoom stays unobserved. |
|
||||||
|
|
||||||
|
### Phase 05 — soft keyboard and touch pad
|
||||||
|
|
||||||
|
| claim | command | expected observation | outcome |
|
||||||
|
|---|---|---|---|
|
||||||
|
| arm on a phone raises the soft keyboard, characters land | phone on the board's AP/network, tap ARM, type a word; `watch_target.py` **on the target** | phone keyboard appears; the watcher's `typed:` line reproduces exactly what was typed, one `P=0:` character per frame | |
|
||||||
|
| a latched modifier chords correctly | latch Ctrl on the row, tap `c`; watch on the target | `LCtrl press` + `C press` — Ctrl+C on the wire, latch visibly cleared after | |
|
||||||
|
| pad moves the pointer; lift releases everything | press-and-hold the pad, move, lift; hold a button meanwhile, then drop the link | `movement dx … dy …` lines that **stop at lift**; `still held` absent from the mouse summary; no button stuck | |
|
||||||
|
|
||||||
|
## The blocking rule
|
||||||
|
|
||||||
|
A unit running any of the rows above:
|
||||||
|
|
||||||
|
1. **Look first, side-effect-free.** `bash tools/hw_probe.sh` (always exits 0,
|
||||||
|
every probe capped, no serial port opened — opening `/dev/ttyACM*` toggles
|
||||||
|
DTR/RTS and resets the board). It tells you whether an attempt is plausible.
|
||||||
|
2. **One attempt, capped.** Each device step runs **at most once**, wrapped in
|
||||||
|
`timeout` (or the tool's own `-m`). No polling a port, no retry loops, no
|
||||||
|
re-powering, no re-flashing "to see if it works". A timeout is an outcome
|
||||||
|
(`failed: timeout …` or `skipped (no device)`), not a cue to try again.
|
||||||
|
3. **Record what was observed.** The outcome — `ok`, `skipped (no device)`,
|
||||||
|
`failed: …` — plus the exact command written goes into the row above, in the
|
||||||
|
phase's commit. Never write `ok` for a command that did not run; the
|
||||||
|
unattended-run contract is that the human picks these rows up later.
|
||||||
|
4. **Never strand the board.** `tools/to_bootloader.py` drops the board into ROM
|
||||||
|
download mode *deliberately*, and a power cycle is the only way back. If a
|
||||||
|
run leaves the board in download mode, its report says so explicitly —
|
||||||
|
nobody may be there to notice.
|
||||||
|
5. **Never block the gate.** None of this is inside `.agents/validate.sh`. The
|
||||||
|
gate stays device-free and network-free; the probe line at its end is context
|
||||||
|
whose exit status is deliberately discarded.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
All green. Report:
|
||||||
|
|
||||||
|
**Implemented / verified (phase 01 final pass)**
|
||||||
|
- `.agents/validate.sh` re-verified as the gate: ①`tests/run_tests.sh` ②`SANITIZE=1` run ③`uv … platformio run -e bridge -e console` ④page byte count ⑤hygiene, then the informational `tools/hw_probe.sh`. Added an explicit `== repo hygiene ==` header + `repo hygiene: OK (57 tracked files…)` verdict line so every check prints one (previously hygiene passed in silence).
|
||||||
|
- `.agents/HW_VERIFICATION.md`: baseline (bridge 26.7 %/87 592 B, 36.1 %/1 135 293 B; console 16.8 %/33.6 %; 3406 checks; page 48 598 B) re-confirmed; served-page row extended with this pass's one-attempt measurements.
|
||||||
|
|
||||||
|
**Tests / gate (100 % green)**
|
||||||
|
- `bash .agents/validate.sh` → **exit 0** (also from `/tmp`): `RESULT: all suites passed` twice (3406 = 3162 C++ + 160 relayctl + 84 page), `2 succeeded`, `page bytes: 48598`, `repo hygiene: OK`, `validation OK`.
|
||||||
|
- Faults re-demonstrated then reverted: (a) inverted `test_keystate` assert → cold-copy gate `RESULT: FAILED`, **GATE_EXIT=1**; (b) markup id `pArmed`→`pArmedZ` → page suite rc 1; (c) type error in `src/main.cpp` → `bridge FAILED / console FAILED`, rc 1; (d) hygiene regex matches `certs/test.pem` (git index commands are forbidden in this pass — task 01's report holds the original live `git add` demo).
|
||||||
|
- Lint: no linter is configured for this repo; compiler is the linter — `-Wall -Wextra` host suites + both firmware builds: **0 warnings**; `bash -n` clean; `py_compile` clean. `ruff check` (this machine's broad ruleset) reports 21 pre-existing *preview-rule* style nits in untouched `tools/*.py` + `tests/test_relayctl.py` (SIM/UP/ISC/EXE/RUF/FURB), zero correctness findings — not churned: `watch_target.py`/`relayctl.py` are hardware-verified artifacts outside this phase. `shellcheck` is absent here and not in the local container image (fetching it would violate the gate's no-network rule). Coverage: n/a (shell + docs; no new Python).
|
||||||
|
|
||||||
|
**Completion criteria** — all met: exit 0 with per-check output ✓; SANITIZE in-gate ✓; no `-t upload`/device curl/network fetch (grep: only in comments; `run_tests.sh` checks `podman image exists` first, so never pulls) ✓; faults bite ✓; HW_VERIFICATION.md names command+expected observation for every 02–05 device claim + one-attempt rule ✓; full suite green, `git status` = phase artifacts + mandated doc edits, nothing stray ✓.
|
||||||
|
|
||||||
|
**Documentation** — README inconsistencies found & fixed: `test_proto` 193→**2327** (table now sums to the headline 3406); duplicate stale `test_page 47` row deleted (element-id clause merged into the 84 row); "last six checks"/"fails 3 checks" claims replaced by measurement (5 fail-safe source pins; `linked()` gate → 2, accept-stamp → 1, both → 3); console line formats corrected to what the code prints (`mouse interface=present target=report protocol buttons=0x00 …`, `queue 0 keys pending…`, `target=BOOT protocol (refused)`); status-JSON sample gained `typed/typing/mouse/mbtn/mframes`; footprint 87 400/35.5 %→87 592/36.1 %; page delivery `Content-Length: 35164`→**48 598** (re-measured live, one capped curl: 200/48 598, 404 on `/nope`, 401 on `/status`); stale Backlog item "Mouse." removed and renumbered; "~35 KB"→"~48 KB"; page-button names matched to the real markup; `validate.sh`/`HW_VERIFICATION.md`/`hw_probe.sh` added to Architecture, Tests and End-to-end. AGENTS.md: only stale statements fixed — "one commit, `Initial import`… predates the mouse and `output.h`" and the pure-module list missing `mouse.h`/`output.h`; not expanded.
|
||||||
|
|
||||||
|
**Deviations** — Did not run the keystroke/mouse device rows: the board's target is this very machine, so injecting input during an unattended pass is the side effect the README's "hazard nobody warns you about" forbids; those rows stay blank per the contract (read-only GETs only). No live infra changed.
|
||||||
|
|
||||||
|
**Next pending phase:** `02_tls_control_channel` (tasks `01_tls_identity_module` … `06_tls_docs_rollback`).
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2327 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 160 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
element ids
|
||||||
|
page 84 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2327 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 160 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
element ids
|
||||||
|
page 84 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 26.7% (used 87592 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.1% (used 1135293 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.84 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 16.8% (used 55200 bytes from 327680 bytes)
|
||||||
|
Flash: [=== ] 33.6% (used 1056081 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.83 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.844
|
||||||
|
console SUCCESS 00:00:00.833
|
||||||
|
========================= 2 succeeded in 00:00:01.676 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw string ==
|
||||||
|
page bytes: 48598 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of this same string)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (57 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ unreachable (curl rc 7, code 000) — expected until phase 02 (TLS) lands
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 200, no redirect (plaintext page still served: the pre-phase-02 shape)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console builds + page size + hygiene)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
Everything green. Final report:
|
||||||
|
|
||||||
|
**Implemented**
|
||||||
|
- Rewrote `.agents/validate.sh` (adapted the generic template) as keyrelay's real gate, `rc`-accumulating, any-cwd, no `set -e` swallowing: ① `bash tests/run_tests.sh` ② `SANITIZE=1` run ③ `uv run --python 3.14 --with platformio python -m platformio run -e bridge -e console` (missing `uv` = FAIL with the AGENTS.md fallback-probe message; never `-t upload`) ④ page byte count from the `R"HTML(` raw string ⑤ the template's three hygiene gates verbatim. Added a python3/node presence check so the suite can't silently skip.
|
||||||
|
- `.gitignore` already had `.agents/phase-sessions/` + `.agents/pipeline.log` — no edit needed.
|
||||||
|
|
||||||
|
**Tests** (`bash .agents/validate.sh`, also from `/tmp`): **exit 0**, ~9 s — `RESULT: all suites passed` (3406 checks: 3162 C++ + 160 relayctl + 84 page) in normal **and** sanitised runs; `bridge SUCCESS RAM 26.7% (87 592 B) / Flash 36.1% (1 135 293 B)`, `console SUCCESS RAM 16.8% (55 200 B) / Flash 33.6% (1 056 081 B)`; `page bytes: 48598`; `validation OK`.
|
||||||
|
**Fault injection, each → gate exit 1, then reverted + green re-confirmed**: (a) inverted `CHECK(!s.dirty())` → exit 1; (b) markup id `pArmed`→`pArmedZ` → page suite FAIL, exit 1; (c) `int kr_injected_fault_c[2] = "…"` in `main.cpp` → bridge+console FAILED, exit 1; (d) tracked `certs/test.pem` → hygiene FAIL exit 1, `git rm --cached` done, index verified clean, `git diff` empty. Gate contains no upload/curl/pull/network command (grep-verified; only a quoted warning in `echo`).
|
||||||
|
|
||||||
|
**Docs**: README "Controller page" said "~35 KB" — stale (measured 48 598 B); now states the exact gate-counted figure. AGENTS.md: no stale facts created; not expanded (its policy).
|
||||||
|
|
||||||
|
**Decisions**: missing `uv`/deps fail rather than skip (per task ASSUMPTION); the page-size check fails if the raw-string markers aren't exactly one each; output is full (RAM/Flash captured for task 02's baseline table).
|
||||||
|
|
||||||
|
**Next task**: `01_verification_gate/02_hardware_contract.md` (`.agents/HW_VERIFICATION.md` + `tools/hw_probe.sh`).
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2327 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 160 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
element ids
|
||||||
|
page 84 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2327 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 160 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
element ids
|
||||||
|
page 84 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 26.7% (used 87592 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.1% (used 1135293 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.84 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 16.8% (used 55200 bytes from 327680 bytes)
|
||||||
|
Flash: [=== ] 33.6% (used 1056081 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.844
|
||||||
|
console SUCCESS 00:00:00.856
|
||||||
|
========================= 2 succeeded in 00:00:01.700 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw string ==
|
||||||
|
page bytes: 48598 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of this same string)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console builds + page size + hygiene)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
**Report — task `01_verification_gate/02_hardware_contract.md`**
|
||||||
|
|
||||||
|
- **Implemented**
|
||||||
|
- `.agents/HW_VERIFICATION.md`: Baseline table (bridge RAM 26.7 %/87 592 B, Flash 36.1 %/1 135 293 B; console 16.8 %/55 200 B, 33.6 %/1 056 081 B; 3406 checks; page 48 598 B); per-phase claim tables for the keystroke-path regression baseline and phases 02–05 (chord identity via `watch_target.py`, `curl … | wc -c` vs gate count http→https, TLS handshake + one-time serial fingerprint + `:80` redirect-only + ws refusal + heap delta, takeover key-up-before-first-key + `armed:0` + `E3`, phone layout, soft keyboard/latch/touch-pad), each row with command + expected observation + outcome slot; the blocking rule (one `timeout`-capped attempt, record `ok`/`skipped (no device)`/`failed:`, no port polling, no opportunistic reflash, explicit note if left in ROM download mode, gate stays device-free).
|
||||||
|
- `tools/hw_probe.sh` (executable, `set -uo pipefail`): 7 fact lines — serial nodes (stat-only, never opened), mDNS, https, http+redirect, `uv`, `node`, board evdev nodes (same USB-id + `capabilities/ev` classification as `watch_target.py`); every probe capped; always exits 0.
|
||||||
|
- `.agents/validate.sh`: appended `bash tools/hw_probe.sh || true` as the last, explicitly non-`rc` line with the why.
|
||||||
|
- **Tests** (all green): `bash .agents/validate.sh` → exit 0 — `RESULT: all suites passed` (3406) normal + `SANITIZE=1`; bridge+console SUCCESS with baseline RAM/Flash; page bytes 48598; hygiene OK; probe context printed without affecting rc. Probe: exit 0 from repo root and `/tmp`, with uv stripped, and in a minimal PATH (no `timeout`/`getent`/`dirname` → states reasons, never hangs; 1.8 s full run); wrapper survives a probe that dies non-zero. `shellcheck` (cached podman image): rc 0, zero findings. Live one-attempt check recorded honestly: `curl -s http://keyrelay.local/ | wc -c` = 48598 = gate count (`ok` row).
|
||||||
|
- **Docs**: no README change — “End-to-end verification” matches every probed fact (http 200 no-redirect, mDNS resolves, no TLS yet); the historical 1.0.0 `Content-Length: 35164` row belongs to phase 04 task 03’s byte-check re-derivation. No AGENTS.md change (no statement went stale; not expanded per its policy).
|
||||||
|
- **Decisions**: probe never opens serial or blocks (degrades to stated “unknown” when its caps are missing); one real device row filled to demonstrate the outcome format; no firmware source touched.
|
||||||
|
- **Next task**: `02_tls_control_channel/01_tls_identity_module.md`.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+148
@@ -0,0 +1,148 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2327 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 160 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
element ids
|
||||||
|
page 84 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2327 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 160 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
element ids
|
||||||
|
page 84 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 26.7% (used 87592 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.1% (used 1135293 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.84 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 16.8% (used 55200 bytes from 327680 bytes)
|
||||||
|
Flash: [=== ] 33.6% (used 1056081 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.83 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.837
|
||||||
|
console SUCCESS 00:00:00.834
|
||||||
|
========================= 2 succeeded in 00:00:01.671 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw string ==
|
||||||
|
page bytes: 48598 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of this same string)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ unreachable (curl rc 7, code 000) — expected until phase 02 (TLS) lands
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 200, no redirect (plaintext page still served: the pre-phase-02 shape)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console builds + page size + hygiene)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
**Verified / implemented (phase 02 final pass)**
|
||||||
|
- All 6 tasks already in `complete/` → verification pass. Device measurement **found a real defect (README bug 11)**: the cert carried its SAN *value* under the OID written from the ASCII text `"2.5.29.17"` instead of DER content bytes `55 1D 11` (`openssl x509 -text` showed `1.10.46.53…`, hex `30 2f 06 09 32 2e 35…`), i.e. **no SAN** → hostname validation impossible while every host check stayed green (they walked the value, never the label).
|
||||||
|
- Fixed in the pure module: `kSanOidTlv`/`sanOidDer()`, `sanExtensionFullDer()` (pinned to the 43 bytes OpenSSL writes for these names), `derCarriesSan()` (OID *and* value); `tls.cpp` uses them + 3 `static_assert`s; `cert` now warns and names the remedy. 33 new checks in `test_tls_pem.cpp`, incl. the pre-fix extension byte-for-byte (`derContains` yes / `derCarriesSan` no).
|
||||||
|
- Corrected a false finding: the earlier "fingerprint rotated once" was an **OpenSSL `-ext` artifact** (`C8:76…` vs the real `61:54:19:30…`, same DER). Re-measured: serial `cert` == live handshake DER hash; page 51 626 B, `sha256 dd16e195…f04e9` == locally assembled page.
|
||||||
|
|
||||||
|
**Tests / lint (100 % green)**
|
||||||
|
- `bash .agents/validate.sh` → **GATE_EXIT=0**: `RESULT: all suites passed` in the plain *and* `SANITIZE=1` run, **4826 checks / 0 failures** each; `bridge`/`console`/`rollback` **SUCCESS**; `page bytes: 51626`; `README/AGENTS claims: OK`; `repo hygiene: OK (71 tracked files)`.
|
||||||
|
- Footprint: bridge **RAM 30.4 % (99 616 B) / Flash 39.6 % (1 246 145 B)** vs baseline 26.7 %/87 592 & 36.1 %/1 135 293 → **+12 024 B RAM / +110 852 B flash**; TLS alone +6 160 B / +100 008 B (rollback image byte-identical); session heap 187 936 → ~145.5 k (1 session) → ~107.9 k (2).
|
||||||
|
- `ruff check --select F,E9 tools/ tests/` → All checks passed; `bash -n` clean (gate, runner, probe); `-Wall -Wextra` warning-free.
|
||||||
|
|
||||||
|
**Completion criteria**: 1 ✓ (gate green + RAM/Flash + session heap quoted); 2 ✓ as recorded (boot prints no fingerprint on the NVS-load path, `cert` prints on demand, identity survived reflashes, nothing about the key is printed anywhere — `nvs erase`→first-boot leg **not re-run**); 3 ✓ re-measured (51 626 == gate; `:80` → 301, no page bytes); 4 partial — 426/401/404 + pinning handshake measured, but arming/typing needs the operator's passphrase (`dflt:0`) and the target is this machine, so no input was sent; 5 ✓ (no `.pem/.key/.crt` tracked or on disk; hygiene block ran); 6 ✓ (`[env:rollback]` built every run + README section); 7 ✓.
|
||||||
|
|
||||||
|
**Documentation**: README — check count 4793→4826 (intro + Quick start), `test_tls_pem` 829→862 with new pins, footprint/TLS-cost rows re-measured, "Ten bugs"→"Eleven bugs" + new entry + both anchors, `cert` row, Troubleshooting names-warning row, `tls_pem.h` architecture entry, nine/ten pure-module wording. AGENTS.md (staleness only): "Ten bugs"→"Eleven bugs" ×2, 4826, and the certificate-names row extended with the DER-OID rule. `.agents/HW_VERIFICATION.md` — 426 body/headers re-measured, fingerprint row correction + `-ext` rule, new SAN row (`failed → fixed in tree, device leg needs nvs erase`) with closing commands; phase report written to `.agents/reports/02_tls_control_channel/02_tls_control_channel__00_phase.a1.md`.
|
||||||
|
|
||||||
|
**Deviations / live-infra**: made **no** reflash and **no** `nvs erase` (erase also destroys the token and Wi-Fi credentials; flashing puts a live HID keyboard into ROM download mode on this machine). Live steps were read-only `curl`/`openssl` plus one capped `console.py cert` (opening the CDC resets the board — permitted, recorded). No phase/PLAN files touched; everything left in the working tree for the harness to commit.
|
||||||
|
|
||||||
|
**Next pending phase**: `03_session_takeover` — `01_pending_claim_socket.md`.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2359 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 268 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 271 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
element ids
|
||||||
|
page 113 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2359 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 268 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 271 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
element ids
|
||||||
|
page 113 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console -e rollback ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 30.4% (used 99616 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 39.6% (used 1246145 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.87 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 20.5% (used 67224 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 37.1% (used 1166397 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Processing rollback (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/rollback/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/rollback/firmware.elf
|
||||||
|
Checking size .pio/build/rollback/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 28.5% (used 93456 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.4% (used 1146137 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.871
|
||||||
|
console SUCCESS 00:00:00.860
|
||||||
|
rollback SUCCESS 00:00:00.857
|
||||||
|
========================= 3 succeeded in 00:00:02.588 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw strings ==
|
||||||
|
page bytes: 51626 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of these same bytes, both builds)
|
||||||
|
|
||||||
|
== README/AGENTS claims vs what this run measured ==
|
||||||
|
README/AGENTS claims: OK (4 826 checks quoted and measured in both runs; 11 Tests-table rows match their suites; 51 626 page bytes quoted; Backlog contiguous; every ws:// sentence names the rollback)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (71 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ -> HTTP 200 (TLS listener is up)
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 301, redirects to https://keyrelay.local/ (redirect-only: the post-phase-02 shape)
|
||||||
|
hw-probe tls 10.2.0.22:443 negotiated TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 (a self-signed verify failure is the expected shape; the fingerprint to compare it against is on the serial console)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console/rollback builds + page size
|
||||||
|
+ the numbers README/AGENTS quote + hygiene)
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
**Implemented**
|
||||||
|
- `src/tls_pem.h` (new, pure — no Arduino/IDF/mbed includes): `wrapPem`/`pemFind` PEM armour both ways, `fingerprintHex`, the single `sanEntries()` name table + `subjectLine()`/`sanList()`, hand-built SAN GeneralNames DER, strict `ipv4Bytes`, and `__DATE__`/`addDays`/`addYears`/`utcTime` validity arithmetic.
|
||||||
|
- `src/tls.h` + `src/tls.cpp` (mbed TLS half): `generateIdentity()` (secp256r1 → self-signed SHA-256 cert, DRBG personalisation `"keyrelay"`, SAN extension, ±validity window, SHA-256 fingerprint), `selfCheck()` (reparse + `pk_check_pair`), `certificateFingerprint()`, `lastError()`; RAII context wrappers free everything on every error path; `#if KR_TLS == 0` stubs keep the rollback linkable. Header records the ld_libs/sdkconfig/nm evidence.
|
||||||
|
- `src/config.h`: `KR_TLS` (default 1), `kCertCommonName`, `kCertValidityYears`, `kPemMax=1024` with measured numbers; `platformio.ini`: `-DKR_TLS=1` in both envs with the rollback named.
|
||||||
|
- SAN is encoded by tested code (OID 2.5.29.17 via `set_extension`) rather than the library's version-varying SAN struct; the 34 DER bytes were verified byte-identical to OpenSSL's for the same names.
|
||||||
|
|
||||||
|
**Verification** (all green)
|
||||||
|
- `bash .agents/validate.sh` → `validation OK`; suites + `SANITIZE=1` both `RESULT: all suites passed`, **4170 checks** (3162 C++ + **764** `test_tls_pem` + 160 relayctl + 84 page), `test_proto` untouched at 2327, page bytes 48 598, hygiene OK, no key-material shapes.
|
||||||
|
- gcov: **src/tls_pem.h 100.00 % of 305 lines** (requirement >90 %); `src/tls.*` stated, not faked.
|
||||||
|
- Firmware: `bridge` 26.7 % RAM / 36.1 % Flash and `console` 16.8 % / 33.6 % — **unchanged from baseline** because nothing calls the identity yet (`--gc-sections` drops it). Proved that is not a false pass: an injected undefined symbol in `tls.cpp` linked anyway, so I re-linked a scratch copy with `-Wl,-u,` forcing the three functions in → both envs link all mbed TLS symbols; cost then `bridge` **88 304 B RAM (+712) / 1 170 853 B Flash (+35 560)**, `console` 55 904 / 1 091 181. `-D KR_TLS=0` builds both envs at exactly the pre-phase footprint.
|
||||||
|
- Found and fixed a real defect via the cross-check: `wrapPem` omitted the newline after a padded final group (1/2 bytes mod 48) — round-trips fine, but not what any other tool writes.
|
||||||
|
|
||||||
|
**Docs**: README (check count ×2, architecture map, pure-module count, Tests table row, "gaps" paragraph — explicit that the identity is not on the wire yet). AGENTS.md stale facts only (check count, pure-module list now including `tls_pem.h` + the no-crypto-library rule, a name-table row, the "fingerprint yes, key never" clause).
|
||||||
|
|
||||||
|
**Decisions**: no device changes — nothing is reachable over TLS yet, so no `HW_VERIFICATION.md` row was run or recorded; `.agents/HW_VERIFICATION.md`'s 3406 baseline left as the phase-01 delta anchor.
|
||||||
|
|
||||||
|
**Next pending task**: `.agents/phases/todo/02_tls_control_channel/02_identity_persistence.md` (NVS storage, generate-once, serial `cert`, `StatusSnapshot` visibility).
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2327 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 764 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 160 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
element ids
|
||||||
|
page 84 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2327 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 764 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 160 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
element ids
|
||||||
|
page 84 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 26.7% (used 87592 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.1% (used 1135293 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.84 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 16.8% (used 55200 bytes from 327680 bytes)
|
||||||
|
Flash: [=== ] 33.6% (used 1056081 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.85 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.845
|
||||||
|
console SUCCESS 00:00:00.853
|
||||||
|
========================= 2 succeeded in 00:00:01.698 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw string ==
|
||||||
|
page bytes: 48598 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of this same string)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (71 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ unreachable (curl rc 7, code 000) — expected until phase 02 (TLS) lands
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 200, no redirect (plaintext page still served: the pre-phase-02 shape)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console builds + page size + hygiene)
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
**Implementation** (`02_tls_control_channel/02_identity_persistence`)
|
||||||
|
|
||||||
|
- `src/main.cpp`: identity now lives in NVS (`tlscrt`/`tlskey`/`tlscrtfp`); `loadTlsIdentity()` runs at boot before `control.begin()` — loads and `selfCheck()`s the stored pair, else generates, stores and prints the fingerprint **once**; a corrupt record is regenerated with two loud `!!` lines before the new value ("any fingerprint you pinned is now wrong"). Store does a read-back (lengths compared, contents never) and prints `IDENTITY NOT STORED … do not pin this fingerprint` if NVS refuses/truncates. All PEM helpers are inside `#if KR_TLS`.
|
||||||
|
- Serial `cert` (no argument form, in `help`): SHA-256 fingerprint (two 47-char lines, not one 95-char line — 128-byte CDC ring, README bug 1), validity window from the stored cert, and the names from `sanEntries()`, with a `!!` warning when `derContains()` shows the stored cert does not carry that table. No command writes or dumps a key/cert; `git grep -i "private key" src/` returns only prose comments and the `kPemKeyLabel`/mbedTLS API names.
|
||||||
|
- New pure `src/status.h`: `StatusSnapshot` + `statusJson()` moved out of `control_server.{h,cpp}` so the frame is host-testable, plus `tls` and `fp` (first 8 bytes of the fingerprint in hex, `fp:""` whenever `tls:0`; a non-hex `fp` is cut so a stored value cannot forge a JSON field). `src/tls_pem.h` gained `fingerprintPairs`, `fingerprintPrefix`, `derContains`, `dateTimeText`, `sanEntries()`; `src/tls.{h,cpp}` gained `certificateDetails()` (+ `KR_TLS==0` stub) and the `tls.cpp` docstring.
|
||||||
|
- Tests: `test_tls_pem.cpp` +97 checks (pinned name table incl. `CN == kHostname + ".local"`, both printed fingerprint shapes rejoining to the digest and refusing at `cap`, `derContains` breaking on one changed byte either side, `dateTimeText`); `test_proto.cpp` + status-frame suite (fields, sanitising, longest legal frame **322 B ≤ `kStatusJsonCap` 384**, renders as nothing when 1 B short).
|
||||||
|
|
||||||
|
**Results:** `bash .agents/validate.sh` → `validation OK` (exit 0). `tests/run_tests.sh`: **4267 checks, 0 failures** (proto 2359, tls_pem 829, text 459, relayctl 160, output 153, ws_codec 135, page 84, mouse 48, keystate 40); same under `SANITIZE=1`. Coverage: `src/tls_pem.h` 100.00 % of 367, `src/status.h` 100.00 % of 28. `pio run -e bridge -e console` SUCCESS — bridge RAM 27.6 % (90 432 B) / Flash 37.5 %, console 17.7 %/34.9 %. Rollback `-DKR_TLS=0` (out-of-tree copy) builds both envs: RAM 26.8 %, Flash 36.1 %, no new warnings.
|
||||||
|
|
||||||
|
**Docs:** README — check count 3406→4267, Quick-start first-boot fingerprint block + survive-reflashing/re-pin rule, `cert` in the serial-console table, `tls`/`fp` in the Wire-protocol status row, architecture map (`status.h`, `tls_pem.h`, `tls.{h,cpp}`, `test_tls_pem.cpp`), Tests-table rows, NVS design decision, 3 troubleshooting rows, note that the measured transcript predates these fields. AGENTS.md — check count and the pure-module list (`status.h`).
|
||||||
|
|
||||||
|
**Deviations:** (1) fingerprint printed as wrapped colon-pair lines, not one 64-hex line; (2) status `fp` is a 16-char label, not the full digest — the cap and the page can't carry 64 hex usefully; (3) `StatusSnapshot`/`statusJson` moved modules (needed for the host-measured cap); (4) added `certificateDetails()` and the four `tls_pem.h` helpers as the tested seams `cert`/status need; (5) boot-time store read-back added to honour "if it does not fit, say so" — the API otherwise fails silently.
|
||||||
|
|
||||||
|
**Device pass (next, not run here):** `console.py cert` → `reboot` → same fingerprint + `identity loaded from NVS`; `nvs erase` + reboot → `identity generated`, pin it, reboot again → unchanged; `console.py status` shows `"tls":1,"fp":"<16 hex>"` matching `cert`'s first 8 bytes.
|
||||||
|
|
||||||
|
Next task: `.agents/phases/todo/02_tls_control_channel/03_tls_listener.md`.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+153
@@ -0,0 +1,153 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2359 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 829 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 160 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
element ids
|
||||||
|
page 84 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2359 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 829 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 160 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
element ids
|
||||||
|
page 84 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 27.6% (used 90432 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 37.5% (used 1178749 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 17.7% (used 58040 bytes from 327680 bytes)
|
||||||
|
Flash: [=== ] 34.9% (used 1099237 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.863
|
||||||
|
console SUCCESS 00:00:00.865
|
||||||
|
========================= 2 succeeded in 00:00:01.727 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw string ==
|
||||||
|
page bytes: 48598 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of this same string)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (71 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local did not resolve within 4 s (no responder? glibc does not cache mDNS — a retry may still work; the IP is the fallback)
|
||||||
|
hw-probe https https://keyrelay.local/ unreachable (curl rc 28, code 000) — expected until phase 02 (TLS) lands
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 200, no redirect (plaintext page still served: the pre-phase-02 shape)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console builds + page size + hygiene)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
**Task complete: `02_tls_control_channel/03_tls_listener.md`**
|
||||||
|
|
||||||
|
**Implemented** — TLS control-channel listener on `:443`, raw mbedTLS driven from `tick()`:
|
||||||
|
- `src/tls_session.{h,cpp}` — one non-blocking server session per accepted fd; handshake = one `mbedtls_ssl_handshake()` per tick with a `kTlsHandshakeMs` deadline; strict same-buffer write-resume contract; slot-cap refusal renders the 7-byte RFC 5246 fatal alert without building a context. Pure policy half (deadline, admission, return-code table, alert, write queue, frame ring) host-tested; library half behind `KR_TLS && ESP_PLATFORM`.
|
||||||
|
- `src/control_server.{h,cpp}` — `:443` listener + plaintext `:80` feed **one** controller slot via one `admitConnection()` rule (cap 2, no transport parameter); per-slot response pumps (page leaves 4 KB/tick); WS frames whole-or-dropped via `TxRing`; `KR_TLS=0` rollback intact.
|
||||||
|
- Device logs only sessions + heap; `hw_probe.sh` gained an informational TLS probe.
|
||||||
|
|
||||||
|
**Tests** — `tests/test_tls_session.cpp` (135 checks; 100 % coverage of the pure half, ASan/UBSan clean); suite now **4402 checks**, green normal + sanitised; `validate.sh` **validation OK** (both firmware builds, page bytes, hygiene).
|
||||||
|
|
||||||
|
**Device pass** (board flashed and verified live): TLSv1.2/**ECDHE-ECDSA-AES256-GCM-SHA384**; page over `https` = 48598 B = gate count; `/status` 401 without token over TLS; console `cert` == handshake fingerprint, stable across 2 reflashes + 3 reboots; heap 187.9 k boot → 145.5 k (1 session) → 107.9 k (2), 20 cycles no drift; 3rd connection refused with alert 40 in 0.0 s; stalled handshake never blocked `loop()` and died on the ~10 s deadline; `:80` untouched (task 04's territory). Results recorded in `.agents/HW_VERIFICATION.md`.
|
||||||
|
|
||||||
|
**Found & fixed during verification**: one silent identity rotation happened on the first boot under the new firmware with nothing observable about it — `loadTlsIdentity()`/`storeTlsIdentity()` now name the failing gate with lengths only. README/AGENTS.md updated (check counts, security model, new pinned-table row). No phase files touched; nothing staged; board left running the new firmware, not in download mode.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+156
@@ -0,0 +1,156 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2359 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 829 checks, 0 failures
|
||||||
|
test_tls_session 135 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 160 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
element ids
|
||||||
|
page 84 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2359 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 829 checks, 0 failures
|
||||||
|
test_tls_session 135 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 160 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
element ids
|
||||||
|
page 84 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 29.6% (used 97136 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 39.4% (used 1238321 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 19.8% (used 64744 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.8% (used 1158621 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.863
|
||||||
|
console SUCCESS 00:00:00.858
|
||||||
|
========================= 2 succeeded in 00:00:01.721 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw string ==
|
||||||
|
page bytes: 48598 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of this same string)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (71 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ -> HTTP 200 (TLS listener is up)
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 200, no redirect (plaintext page still served: the pre-phase-02 shape)
|
||||||
|
hw-probe tls 10.2.0.22:443 negotiated TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 (a self-signed verify failure is the expected shape; the fingerprint to compare it against is on the serial console)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console builds + page size + hygiene)
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
**Task 04 (`02_tls_control_channel/04_transport_abstraction.md`) — complete.**
|
||||||
|
|
||||||
|
- `src/conn.h`: one `kr::Conn` transport view (tagged union, no virtual — rebinding per slot, inlinable pump) over `WiFiClient`/`tls::Session`; whole-frame-or-nothing `push`, resumable `pump` capped per write (4 KB) and per tick, flash body streamed through one 1460 B RAM chunk.
|
||||||
|
- `:80` is now redirect-only via pure `plaintextRoute()`: `301 https://…`, `426 Upgrade Required` for a plaintext `/ws` upgrade **and** for any `?token=` request (a `Location` would leak the passphrase twice); full `301`/`426` body pair built by the one `renderHttpResponseHead()`. `serveReadonly()` is the single HTTP answer for both slots.
|
||||||
|
- Safety invariant kept and pinned: `_lastInboundMs` is stamped only for `WebSocket && _authed` — handshake, `/status` and viewer frames cannot keep a dead controller armed.
|
||||||
|
- Tests: new `tests/test_conn.cpp` (264 checks, 100 % of the pure half), `test_tls_session` 135→118 (`WriteQueue` deleted; `TxRing` reseat/unseated pinned), `test_relayctl` 160→189 (structural pins: one formatter, one route decision, one clock stamp, page bytes == `kIndexHtmlLen`), `test_page` 84→93 (`https:`→`wss://`, no credential in the connect URL).
|
||||||
|
- Two real bugs found by the **sanitised** run and fixed, each pinned by the case that triggered it: `memcmp` past a short query, and `octets[4]` overflow on `"192.168.1.1.1"`.
|
||||||
|
- Gate: `.agents/validate.sh` exits 0 — 4687 checks in both the normal and `SANITIZE=1` runs, `bridge` RAM 30.4 % (99 616)/Flash 39.5 % (1 242 329), `console` 20.5 %/37.0 %, page 48 598 B, hygiene OK.
|
||||||
|
- Device (flashed, observed): `https://…/` = 48 598 B ×3 with `sha256` == `web_page.h`; `/`→301, `/index.html`→301, `/nope`→301 to `/`; `/status?token=`→426 with no token in the body; `/ws`→426 (`rejected=1` on the board); `wss://` upgrade→101, wrong passphrase→`E1`+close; viewer 200/401 concurrently; heap 140 k/109 k/182 k over 0–2 sessions.
|
||||||
|
- Docs moved: README (intro count, quick start `https://`, security-model rewrite, measured-behaviour rows, architecture map +`conn.h`, tests table, gaps), AGENTS.md (`curl -sk`, 4687, new defaults), `.agents/HW_VERIFICATION.md` rows.
|
||||||
|
- Not run, recorded honestly: the keystroke/silence rows need an *authenticated* controller and this board's passphrase is not the default (`dflt:0`) — I did not change a credential I could not restore.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+162
@@ -0,0 +1,162 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2359 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 829 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 264 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
one routing path, two transports
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 189 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
element ids
|
||||||
|
page 93 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2359 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 829 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 264 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
one routing path, two transports
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 189 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
element ids
|
||||||
|
page 93 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 30.4% (used 99616 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 39.5% (used 1242329 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.88 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 20.5% (used 67224 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 37.0% (used 1162585 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.87 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.882
|
||||||
|
console SUCCESS 00:00:00.866
|
||||||
|
========================= 2 succeeded in 00:00:01.748 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw string ==
|
||||||
|
page bytes: 48598 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of this same string)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (71 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ -> HTTP 200 (TLS listener is up)
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 301, redirects to https://keyrelay.local/ (redirect-only: the post-phase-02 shape)
|
||||||
|
hw-probe tls 10.2.0.22:443 negotiated TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 (a self-signed verify failure is the expected shape; the fingerprint to compare it against is on the serial console)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console builds + page size + hygiene)
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
**Task 05 (`02_tls_control_channel/05_page_client_fingerprint.md`) — complete.**
|
||||||
|
|
||||||
|
- **Page** (`src/web_page.h`): one 19-byte `#if KR_TLS` build marker joins the raw-string chunks (branches equal length → page size stays one number); TLS page on an `http:` origin opens **no socket, sends no frame**, disables the passphrase box + connect button behind a banner ("this page arrived in the clear; reload over https") — never a silent `ws://` fallback; rollback build keeps `ws://`; new `tls`/`plaintext` pill and a certificate line rendered from the status frame's `fp` only (nothing when `tls:0` or no `fp`).
|
||||||
|
- **`relayctl.py`**: TLS by default on :443 via stdlib `ssl`; `--fingerprint`/`$KEYRELAY_TLS_FP` hashes the peer DER and **aborts before any frame** on mismatch; `--ca` = `create_default_context` + hostname checking; `--insecure` warns on every run; none of them → refuses (exit 2) without a socket; `--plain` is the only, explicit path to :80; 426/301 messages updated (tool now has a TLS client). Token pre-flight, `paste --yes` gate, no token ever printed — kept.
|
||||||
|
- **Tests**: `test_relayctl` 189→271 (trust-decision matrix, port table, fingerprint normalisation/refusals, fake-board byte recording proving mismatch ⇒ zero bytes sent, `--ca`/`--insecure`/`--plain` shapes, main-level refusals, page assembled from *both* marker branches); `test_page` 93→113 (http: refusal incl. `boot()` disabling inputs, rollback marker keeps `ws://`, pill + exact-`fp` rendering); `test_conn` count 264→268 + `kServedPageBytes` 51 310→**51 626**; `test_proto.cpp` untouched.
|
||||||
|
- **Gate**: `bash .agents/validate.sh` → **validation OK** — 4793 checks in both normal and `SANITIZE=1` runs; bridge RAM 30.4 % (99 616) / Flash 39.6 % (1 245 353), console 20.5 %/37.1 %; page bytes 51 626; hygiene OK. Coverage of changed/added `relayctl.py` functions: **156/158 = 98.7 %** (stdlib `trace`). `ruff --select F,E9` clean on `tools/`+`tests/`; both marker branches compile (`KR_TLS=0/1` syntax-checked).
|
||||||
|
- **Device** (flashed `bridge`, board healthy, not in download mode): served page **51 626 B**, sha256 `dd16e195…f04e9` byte-equal to the locally assembled TLS page, marker `=1`, new ids present; `:80` still 301. `--fingerprint 00…00` → `certificate fingerprint mismatch … Nothing was sent`; real pin / `--insecure` / `--ca <exported self-signed>` all passed their trust step and stopped at authentication (`E1` — the passphrase is the operator's, 3 authfails total, not guessed); no argument → exit 2, no socket; `--plain` → the 426 sentence, no passphrase blame. Recorded in `.agents/HW_VERIFICATION.md`; typing over a pinned session stays the operator's confirmation.
|
||||||
|
- **Docs**: README (4793 counts, Quick start pinning block with `cert`/`curl -sk …/status?token=`/`$KEYRELAY_TLS_FP`, Controller-page byte count + 3 new element rows, architecture map, end-to-end examples, 2 new Troubleshooting rows incl. the wrong-network/identity-rotation wording in full, tests table), AGENTS.md (check count, page-size and raw-string statements now describe the marker, new cross-check table row), `.agents/validate.sh` page-size check now assembles chunks+marker (would otherwise fail on the new layout).
|
||||||
|
- **Notable**: the page must differ per build (a runtime probe would *guess* whether the board does TLS — invariant 5), hence the equal-length build marker; the pin hash deliberately runs post-handshake on the presented DER so self-signed pinning needs no CA ceremony, while `--ca` keeps OpenSSL's full verification.
|
||||||
|
- **Next pending task**: `02_tls_control_channel/06_tls_docs_rollback.md`.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2359 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 829 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 268 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 271 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
element ids
|
||||||
|
page 113 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2359 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 829 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 268 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 271 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
element ids
|
||||||
|
page 113 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 30.4% (used 99616 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 39.6% (used 1245353 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 20.5% (used 67224 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 37.1% (used 1165609 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.85 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.862
|
||||||
|
console SUCCESS 00:00:00.854
|
||||||
|
========================= 2 succeeded in 00:00:01.716 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw strings ==
|
||||||
|
page bytes: 51626 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of these same bytes, both builds)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (71 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ -> HTTP 200 (TLS listener is up)
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 301, redirects to https://keyrelay.local/ (redirect-only: the post-phase-02 shape)
|
||||||
|
hw-probe tls 10.2.0.22:443 negotiated TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 (a self-signed verify failure is the expected shape; the fingerprint to compare it against is on the serial console)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console builds + page size + hygiene)
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
**Implemented**
|
||||||
|
- `platformio.ini`: new `[env:rollback]` (`extends env:bridge`, unflags `-DKR_TLS=1`, sets `-DKR_TLS=0`) — `pio run -e rollback` is now the documented, reproducible rollback; `pio run` has no `--build-flags`, so an env is the only unambiguous spelling.
|
||||||
|
- `.agents/validate.sh`: builds all three envs, plus new check 5 that pins the docs to measurements — check count in README+AGENTS, **every Tests-table row against that suite's own line**, the page byte count, Backlog numbered 1..N, and every `ws://` line must name the rollback on the same line; fixed its stale `curl -s http://` comment.
|
||||||
|
- README: TLS intro bullet; Safety model "TLS is transport, not an arming input"; trust table gains "who can see the traffic" (with the unpinned window stated) and "anyone with a copy of this firmware"; WiFi-owner row corrected; new "What TLS does not buy" + "the certificate you did not check is the one thing to check"; Wire-protocol transport paragraph + the non-`E<code>` refusals (401/503/301/**426**); new **"The rollback — building it, and what it costs"**; Measured-behaviour rows (footprint vs baseline, TLS cost, handshake time, session heap); Troubleshooting rollback row; 5 Design decisions (challenge–response, on-board identity, raw mbedTLS vs `esp_tls`, 426 vs 301, rollback-as-build); Backlog TLS item removed, renumbered 1–9, narrower "a certificate a browser already trusts" added.
|
||||||
|
- Docstrings: "what it does NOT protect / must not reverse" added to `tls.h`, `tls_pem.h`, `tls_session.h`, `conn.h`, `relayctl.py`. AGENTS.md: corrected facts only (3-env gate, `conn.h` pure half, gate checks doc numbers, TLS identity defaults, "Three tables"→"Tables", "last row"→"evdev row").
|
||||||
|
- Fixed earlier-task defects: stale `test_page | 93`→113; two Tests rows missing a closing pipe + a blank line splitting that table; HW_VERIFICATION duplicate blank `/ws` row and a heap row wrapped over 6 lines (broke its table).
|
||||||
|
|
||||||
|
**Tests/lint**: `bash .agents/validate.sh` → **exit 0, validation OK**; 4793 checks / 0 failures in both the plain and `SANITIZE=1` runs; page bytes 51 626; RAM/Flash — bridge 30.4 %/99 616 · 39.6 %/1 245 353, console 20.5 %/67 224 · 37.1 %/1 165 609, **rollback 28.5 %/93 456 · 36.4 %/1 146 137**; `ruff --select F,E9 tools/ tests/` clean; `bash -n` clean. No new executable code, so coverage is unchanged (pure modules already 100 %).
|
||||||
|
|
||||||
|
**Docs**: README.md and AGENTS.md updated as above.
|
||||||
|
|
||||||
|
**Deviations**: rollback proven by build + binary evidence, not by reflashing — `nm` gives 0 `mbedtls_ssl_*`, 0 `mbedtls_x509write`, 0 identity strings in the rollback image vs 115/15/1 in `bridge`; flashing the rollback and back would put a live HID keyboard into download mode on the host running this pipeline, so the device leg is recorded `skipped (not flashed)` in `.agents/HW_VERIFICATION.md` with the exact commands. Handshake time measured this task (0.349–0.423 s warm ×7, 3.94 s cold, page whole in 0.53–0.62 s); heap quoted from the recorded task 03/04 serial readouts, not re-measured (opening the CDC port resets the board).
|
||||||
|
|
||||||
|
**Next pending task**: this was phase 02's last task; after the harness moves it, next is `03_session_takeover/01_pending_claim_socket.md`.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+205
@@ -0,0 +1,205 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2359 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 829 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 268 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 271 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
element ids
|
||||||
|
page 113 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2359 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 829 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 268 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 271 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
element ids
|
||||||
|
page 113 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console -e rollback ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 30.4% (used 99616 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 39.6% (used 1245353 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 20.5% (used 67224 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 37.1% (used 1165609 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Processing rollback (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/rollback/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/rollback/firmware.elf
|
||||||
|
Checking size .pio/build/rollback/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 28.5% (used 93456 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.4% (used 1146137 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.87 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.862
|
||||||
|
console SUCCESS 00:00:00.860
|
||||||
|
rollback SUCCESS 00:00:00.867
|
||||||
|
========================= 3 succeeded in 00:00:02.589 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw strings ==
|
||||||
|
page bytes: 51626 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of these same bytes, both builds)
|
||||||
|
|
||||||
|
== README/AGENTS claims vs what this run measured ==
|
||||||
|
README/AGENTS claims: OK (4 793 checks quoted and measured in both runs; 11 Tests-table rows match their suites; 51 626 page bytes quoted; Backlog contiguous; every ws:// sentence names the rollback)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (71 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ -> HTTP 200 (TLS listener is up)
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 301, redirects to https://keyrelay.local/ (redirect-only: the post-phase-02 shape)
|
||||||
|
hw-probe tls 10.2.0.22:443 negotiated TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 (a self-signed verify failure is the expected shape; the fingerprint to compare it against is on the serial console)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console/rollback builds + page size
|
||||||
|
+ the numbers README/AGENTS quote + hygiene)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
**Phase 03 `session_takeover` — final verification pass. All green.**
|
||||||
|
|
||||||
|
All four task files were already in `.agents/phases/complete/03_session_takeover/` with per-task reports, so this was the phase-level close, not new implementation.
|
||||||
|
|
||||||
|
**Completion criteria**
|
||||||
|
1. *2nd controller authenticates + takes the bridge; a 3rd is refused* — met host-side: `test_claim` 102 (whitelist matrix, `acceptsUpgrade` one-claim-at-a-time, both deadline clocks across `millis()` rollover, 64-byte cap through the real codec), `test_relayctl` 308 (`--take-over` = `T`,`X`,`H`; default refusal names the flag and sends no `X`), `test_page` 142 (claim→button→one click→`T` then `X` only). Refusal mechanism stated precisely: a second claim gets `503`/`E3`, a third *socket* gets the slot refusal (`slotrefused`).
|
||||||
|
2. *Handover releases then adopts, lands disarmed* — pinned: `closeLink("taken over")` before adoption (inverting it fails exactly 1 check, reverted); no `arm`/`setArmed`/`armed = true` in the `X` path (inserting one fails exactly 1 check, reverted); un-authenticated `X` admitted in `claim.h` fails `test_claim`+`test_relayctl` (reverted). Also verified by reading: `serveClaim` never writes `_lastInboundMs`/`_controllerSeen`, so a parked claim cannot hold the board armed past 8 s.
|
||||||
|
3. *Incumbent page says taken over, disarmed, no auto-reclaim* — `onE12()` + `test_page` (disarms, releases mouse, ends typing run, pill `taken over`, reconnect loop off).
|
||||||
|
4. *Three source pins fail on inversion* — re-demonstrated in this pass, all reverted (`src/claim.h`, `src/control_server.cpp` byte-identical to pre-inversion).
|
||||||
|
5. *Gate green + README/AGENTS say takeover cannot arm* — yes.
|
||||||
|
|
||||||
|
**Validation** — `bash .agents/validate.sh` → **exit 0**: 12 suites **5026 checks, 0 failures** (normal and `SANITIZE=1`), `bridge`/`console`/`rollback` all SUCCESS, page bytes 59 286, README/AGENTS claims OK, hygiene OK. Extra lint: `ruff --select F,E9` clean, `bash -n` clean, `py_compile` clean. Coverage on new/changed logic: `claim.h` 100 % (11 lines), `status.h` 100 % (29), `proto.h` 94.8 % (229), relayctl take-over lines 14/15 (the miss is a `def` continuation trace never records).
|
||||||
|
|
||||||
|
**Defects fixed (docs only — no code defect found)**: bug 6 claimed a 2nd `/ws` upgrade "is still refused"; Measured-behaviour samples lacked `taken=`/`busy`/`tkov`; footprint + TLS-cost rows quoted phase-02 numbers while claiming to be what the gate prints (now 99 744/1 257 933, 67 352/1 178 173, 93 568/1 157 609 → TLS = +6 176 B RAM/+100 324 B flash); page-delivery/handshake rows dated; `AGENTS.md` pure-module list was missing `claim.h`.
|
||||||
|
|
||||||
|
**Hardware**: no device run. The board still runs pre-phase-03 firmware, reflash/arming mutates a live device (the target is this machine), and both controllers need the operator's passphrase — guessing feeds the shared 5-fail lockout. The 3 blank rows (third-controller refusal, chord labels, nothing-outlives-the-link) are now recorded `skipped (no device run)` with the exact closing commands in `.agents/HW_VERIFICATION.md`.
|
||||||
|
|
||||||
|
**Tree left for the harness**: index untouched (0 staged), no history changes; `todo/03_session_takeover/00_phase.md` moved to `complete/` (matches the phase-02 rename pattern); untracked files the commit must include: `.agents/validate.sh`, `src/claim.h`, `tests/test_claim.cpp`, `.agents/phases/complete/03_session_takeover/`, `.agents/reports/03_session_takeover/`. Next phase: `todo/04_controller_page_responsive/`.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2379 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 280 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the busy board and --take-over
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 308 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
the busy board and the take-over
|
||||||
|
element ids
|
||||||
|
page 142 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2379 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 280 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the busy board and --take-over
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 308 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
the busy board and the take-over
|
||||||
|
element ids
|
||||||
|
page 142 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console -e rollback ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 30.4% (used 99744 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 40.0% (used 1257933 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.84 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 20.6% (used 67352 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 37.5% (used 1178173 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.85 seconds =========================
|
||||||
|
|
||||||
|
Processing rollback (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/rollback/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/rollback/firmware.elf
|
||||||
|
Checking size .pio/build/rollback/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 28.6% (used 93568 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.8% (used 1157609 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.84 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.838
|
||||||
|
console SUCCESS 00:00:00.848
|
||||||
|
rollback SUCCESS 00:00:00.837
|
||||||
|
========================= 3 succeeded in 00:00:02.523 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw strings ==
|
||||||
|
page bytes: 59286 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of these same bytes, both builds)
|
||||||
|
|
||||||
|
== README/AGENTS claims vs what this run measured ==
|
||||||
|
README/AGENTS claims: OK (5 026 checks quoted and measured in both runs; 12 Tests-table rows match their suites; 59 286 page bytes quoted; Backlog contiguous; every ws:// sentence names the rollback)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (109 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ -> HTTP 200 (TLS listener is up)
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 301, redirects to https://keyrelay.local/ (redirect-only: the post-phase-02 shape)
|
||||||
|
hw-probe tls 10.2.0.22:443 negotiated TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 (a self-signed verify failure is the expected shape; the fingerprint to compare it against is on the serial console)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console/rollback builds + page size
|
||||||
|
+ the numbers README/AGENTS quote + hygiene)
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
All green. Report:
|
||||||
|
|
||||||
|
**Implemented (task `03_session_takeover/01_pending_claim_socket.md`)**
|
||||||
|
- `src/claim.h` (new pure module): `ViewerRole`, `acceptsUpgrade()` (one claim at a time), the **one visible whitelist** `honours()` — allowed: `T<token>` (unauth+auth) and `K` (auth); refused by name: `A1`/`A0`, `S=`, `C`, `Y=`, `P=`, `M=`, `R`, `H`, `Bad`/`None`; `X` reserved for task 02 on one marked line; two-clock `expired()` deadline arithmetic (wrap-safe).
|
||||||
|
- `control_server.{h,cpp}`: spare slot gained `_viewerRole`/`_viewerPhase`/`_claimAuthed`/`_claimAtMs`/`_claimMsg[64]`+`_claimReader` (~110 B, commented); `/ws` upgrade on the spare is accepted into Claim (same `ws::acceptFor` + one shared 101 writer — the "exactly one status line" pin holds); claim auth = same constant-time compare, same shared `_authFails`/`_lockUntilMs`, E1/E2 + drop on failure; whitelist violation = E6 + drop; claim never touches `_lastInboundMs`/`_controllerSeen`/`_onMessage`; `_claimAtMs` silence/auth timeouts drop and free the session; stopViewer/both accept paths reset claim state; third socket still refused at the slot table.
|
||||||
|
- `config.h`: `kClaimMsgCap = 64`, `kClaimTimeoutMs = 15000` with reasoning.
|
||||||
|
- `tests/test_claim.cpp` (95 checks) registered in `run_tests.sh`.
|
||||||
|
|
||||||
|
**Verification** — `bash .agents/validate.sh` → **validation OK**: 4921 checks plain and sanitised (12 Tests-table rows match; `test_proto` 2359 and `test_relayctl` 271 **untouched and green** — no wire change, `X` still parses `Bad`); bridge/console/rollback all build; page 51 626 B unchanged; hygiene OK. Bridge RAM 99 728 B vs 99 616 B baseline (**+112 B**, matching the field comment); rollback +112 B likewise.
|
||||||
|
|
||||||
|
**Docs** — README: check counts (intro + quick start), new `test_claim` Tests-table row, `claim.h` in the Architecture map (nine→ten pure), rewrote the stale "second upgrade refused 503/E3" Security-model paragraph and the Wire-protocol refusals paragraph. AGENTS.md: invariant 8 restated (claim cannot drive the keyboard; dispatcher never sees a claim frame) + count. `.agents/HW_VERIFICATION.md`: task-01 device row recorded as **skipped (not run on device)** — live board still runs phase-02 firmware and the authenticating half needs the operator's passphrase (guessing feeds the shared 5-fail lockout this task wires); exact close-out commands written into the row.
|
||||||
|
|
||||||
|
**Deviations** — task text said the claim buffer is "a sixteenth" of the controller's; 64 vs `kWsMessageCap` 256 is a **quarter** — code, tests and README carry the measured ratio and say so. `ViewerRole` lives in `claim.h` with a `using` alias in `ControlServer` so the host suite walks the same type. No live changes made.
|
||||||
|
|
||||||
|
**Next pending task:** `03_session_takeover/02_takeover_funnel.md` (`X` claims the bridge; `E12`; funnel before adoption).
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+207
@@ -0,0 +1,207 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2359 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 268 checks, 0 failures
|
||||||
|
test_claim 95 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 271 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
element ids
|
||||||
|
page 113 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2359 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 268 checks, 0 failures
|
||||||
|
test_claim 95 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 271 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
element ids
|
||||||
|
page 113 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console -e rollback ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 30.4% (used 99728 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 39.7% (used 1249045 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.87 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 20.5% (used 67336 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 37.2% (used 1169305 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Processing rollback (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/rollback/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/rollback/firmware.elf
|
||||||
|
Checking size .pio/build/rollback/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 28.6% (used 93568 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.5% (used 1148945 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.870
|
||||||
|
console SUCCESS 00:00:00.864
|
||||||
|
rollback SUCCESS 00:00:00.863
|
||||||
|
========================= 3 succeeded in 00:00:02.597 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw strings ==
|
||||||
|
page bytes: 51626 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of these same bytes, both builds)
|
||||||
|
|
||||||
|
== README/AGENTS claims vs what this run measured ==
|
||||||
|
README/AGENTS claims: OK (4 921 checks quoted and measured in both runs; 12 Tests-table rows match their suites; 51 626 page bytes quoted; Backlog contiguous; every ws:// sentence names the rollback)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (109 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ -> HTTP 200 (TLS listener is up)
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 301, redirects to https://keyrelay.local/ (redirect-only: the post-phase-02 shape)
|
||||||
|
hw-probe tls 10.2.0.22:443 negotiated TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 (a self-signed verify failure is the expected shape; the fingerprint to compare it against is on the serial console)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console/rollback builds + page size
|
||||||
|
+ the numbers README/AGENTS quote + hygiene)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
Everything is 100 % green. Final report:
|
||||||
|
|
||||||
|
**Implemented (`03_session_takeover/02_takeover_funnel.md`)**
|
||||||
|
- `proto.h`: `Cmd::Takeover` for a lone `X` (trailing bytes → `Bad`), header direction table + `E12` error-list entry; `formatError` untouched.
|
||||||
|
- `control_server.{h,cpp}`: `handOverClaim()` — exact order `E12`→incumbent (best-effort) → `closeLink("taken over")` (the bug-10 funnel: disarm + release) → transport transplant → adopt (`_authed/_controllerSeen/_lastInboundMs/_connectedAtMs`, reader reset, `OK`+`sendStatus()`, `_onLinkChange(true)`) → claim slot fully vacated (state + buffers zeroed). `kErrTakenOver=12`, `_takeovers` counter + `takeovers()`, `sendOk()`; `arm()` never called. TLS slot transplant done as a *pointer swap* over two `Session` storage objects (no crypto state moves) + new `Session::forgetPendingWrite()` (documented: mbed TLS resumes half-records from its own buffer; without it the adopted TX path would wedge).
|
||||||
|
- `claim.h`: `X` joins the whitelist on its marked line, authenticated branch only; `main.cpp`: controller-side `X` = no-op answered `OK` (not `E6`); serial `link … taken=N`.
|
||||||
|
- Defect fixed (pre-existing, serves `E12`): the page read error codes as one character — `E10`/`E11`/`E12` misread as `E1`; now parsed to the first `:` (page 51 626 → 52 041 B, all pins moved with it).
|
||||||
|
|
||||||
|
**Verification** — `bash .agents/validate.sh` → **validation OK**: 4 954 checks plain + sanitised, 0 failures (`test_proto` 2 369, `test_claim` 102, `test_relayctl` 287 incl. the three handover pins — each demonstrated FAILING on an inverted tree, then reverted); `bridge/console/rollback` all build (bridge RAM 99 744 B = +16 B over task 01); page 52 041 B; hygiene OK. Device: **skipped (no device run)** recorded in `.agents/HW_VERIFICATION.md` — live board still runs phase-02 firmware and the procedure needs the operator's passphrase (guessing feeds the shared lockout); no live changes made; close-out commands written into the rows.
|
||||||
|
|
||||||
|
**Docs** — README: wire table `X` row, `E12` error code, "Single controller" paragraph now states takeover runs through the funnel and **cannot arm**, refusals/troubleshooting "no takeover" sentences corrected, Tests-table rows + totals, page-size quotes. AGENTS.md: check count + invariant 8's now-false "does nothing else" sentence only.
|
||||||
|
|
||||||
|
**Deviations** — the status-frame `takeovers`/JSON field is left to task 03 (whose file explicitly assigns "StatusSnapshot gains … `uint32_t takeovers`"); here the counter is published via the getter and the serial `status` line. Frames pipelined behind `X` become ordinary controller frames after adoption (documented — the handover itself still never arms).
|
||||||
|
|
||||||
|
**Next pending task:** `03_session_takeover/03_status_page_takeover_ui.md` (`busy`, TAKE OVER affordance, `E12` page handling).
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+209
@@ -0,0 +1,209 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2369 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 268 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 287 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
element ids
|
||||||
|
page 113 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2369 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 268 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 287 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
element ids
|
||||||
|
page 113 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console -e rollback ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 30.4% (used 99744 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 39.8% (used 1250533 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 20.6% (used 67352 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 37.2% (used 1170789 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.87 seconds =========================
|
||||||
|
|
||||||
|
Processing rollback (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/rollback/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/rollback/firmware.elf
|
||||||
|
Checking size .pio/build/rollback/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 28.6% (used 93568 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.6% (used 1150201 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.87 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.859
|
||||||
|
console SUCCESS 00:00:00.866
|
||||||
|
rollback SUCCESS 00:00:00.870
|
||||||
|
========================= 3 succeeded in 00:00:02.595 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw strings ==
|
||||||
|
page bytes: 52041 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of these same bytes, both builds)
|
||||||
|
|
||||||
|
== README/AGENTS claims vs what this run measured ==
|
||||||
|
README/AGENTS claims: OK (4 954 checks quoted and measured in both runs; 12 Tests-table rows match their suites; 52 041 page bytes quoted; Backlog contiguous; every ws:// sentence names the rollback)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (109 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ -> HTTP 200 (TLS listener is up)
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 301, redirects to https://keyrelay.local/ (redirect-only: the post-phase-02 shape)
|
||||||
|
hw-probe tls 10.2.0.22:443 negotiated TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 (a self-signed verify failure is the expected shape; the fingerprint to compare it against is on the serial console)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console/rollback builds + page size
|
||||||
|
+ the numbers README/AGENTS quote + hygiene)
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
Everything is 100 % green. Final report:
|
||||||
|
|
||||||
|
**Implemented (`03_session_takeover/03_status_page_takeover_ui.md`)**
|
||||||
|
- `status.h` + `main.cpp`: `StatusSnapshot` gains `busy` (= `control.linked()`) and `takeovers`; `statusJson()` emits `"busy":u` / `"tkov":u` — a link state and an event counter, no address/agent/identity anywhere (pinned). Longest legal frame measured at **349 B**; `kStatusJsonCap` stayed 384 because the test-enforced ≥32-byte headroom rule holds (35 left) — `config.h` comment rewritten with the new arithmetic (measured, not grown by guess; the task's "grow" instruction was conditional on the numbers, which the suite re-measures).
|
||||||
|
- `web_page.h`: link pill's third state `held by another controller` (from `s.busy` / claim non-adoption); **take over** button appears only after the board says held; one click sends `T` then `X` and nothing else, page lands DISARMED with a log line; `E12` disarms + releases mouse + aborts typing + pill `taken over` + auto-reconnect off (no re-claim fight); `E3` keeps its "no room for a claim" meaning with its own wording; Connection-card hint states takeover cannot arm.
|
||||||
|
- `relayctl.py`: `--take-over`; adoption probe (`OK`-then-status vs `OK`-then-silence = the board reporting busy; no probing frame sent); default = refusal naming `--take-over` with no `X` on the wire; flag on a free board = stated no-op; `_poll` now drains the whole batch (a status riding behind `OK` is kept, not abandoned — bug found and fixed).
|
||||||
|
- `claim.h`: the `H`-refusal comment corrected — the claimant's answer *is* its own non-adoption; `busy` serves pollers and post-handover frames.
|
||||||
|
|
||||||
|
**Verification** — `bash .agents/validate.sh` → **validation OK, exit 0**: 5 026 checks plain + sanitised, 0 failures (`test_proto` 2 379, `test_page` 142, `test_relayctl` 308 incl. T→X ordering / default refusal / free-board no-op / `T,X,H` through `main()`); `bridge/console/rollback` all build (bridge RAM 99 744 B — unchanged; Flash 1 257 933 B, page +7 245 B); page 59 286 B, `test_conn` chunk math updated (41 chunks / 11 ticks); hygiene OK. Device: **skipped (no device run)** recorded in `.agents/HW_VERIFICATION.md` — a board is reachable but runs pre-phase-03 firmware, and the two-device gesture needs the operator's passphrase (guessing feeds the shared lockout); **no live changes made**; close-out commands written into the row.
|
||||||
|
|
||||||
|
**Docs** — README: check count ×2, page 59 286 B, S-frame row (`busy`/`tkov`), trust-table row "someone holding the passphrase … can take the bridge over, cannot arm it by taking it", Controller-page pill/take-over rows, Security-model busy-is-not-identity sentence, CLI `--take-over` docs + example, Troubleshooting page/CLI rows, Tests-table rows/descriptions. AGENTS.md: check count only (its stale-fact test).
|
||||||
|
|
||||||
|
**Next pending task:** `03_session_takeover/04_takeover_tests_docs.md` (`relayctl`/frame test completion, pin-inversion demonstration, README final pass).
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+213
@@ -0,0 +1,213 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2379 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 280 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the busy board and --take-over
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 308 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
the busy board and the take-over
|
||||||
|
element ids
|
||||||
|
page 142 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2379 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 280 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the busy board and --take-over
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 308 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
the busy board and the take-over
|
||||||
|
element ids
|
||||||
|
page 142 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console -e rollback ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 30.4% (used 99744 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 40.0% (used 1257933 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.87 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 20.6% (used 67352 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 37.5% (used 1178173 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.85 seconds =========================
|
||||||
|
|
||||||
|
Processing rollback (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/rollback/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/rollback/firmware.elf
|
||||||
|
Checking size .pio/build/rollback/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 28.6% (used 93568 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.8% (used 1157609 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.866
|
||||||
|
console SUCCESS 00:00:00.855
|
||||||
|
rollback SUCCESS 00:00:00.858
|
||||||
|
========================= 3 succeeded in 00:00:02.579 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw strings ==
|
||||||
|
page bytes: 59286 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of these same bytes, both builds)
|
||||||
|
|
||||||
|
== README/AGENTS claims vs what this run measured ==
|
||||||
|
README/AGENTS claims: OK (5 026 checks quoted and measured in both runs; 12 Tests-table rows match their suites; 59 286 page bytes quoted; Backlog contiguous; every ws:// sentence names the rollback)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (109 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ -> HTTP 200 (TLS listener is up)
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 301, redirects to https://keyrelay.local/ (redirect-only: the post-phase-02 shape)
|
||||||
|
hw-probe tls 10.2.0.22:443 negotiated TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 (a self-signed verify failure is the expected shape; the fingerprint to compare it against is on the serial console)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console/rollback builds + page size
|
||||||
|
+ the numbers README/AGENTS quote + hygiene)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
**Report — task 03/04 (`04_takeover_tests_docs.md`)**
|
||||||
|
|
||||||
|
- Implemented:
|
||||||
|
- Verified the three takeover source pins in `tests/test_relayctl.py` (funnel-before-adoption; no `arm(`/`setArmed`/`armed = true` in `handOverClaim` or the controller-side `X` case; whitelist consulted before the Takeover branch and `X` only in `claim.h`'s authenticated branch; `E12` named code + incumbent send) — all present and re-read against `control_server.cpp`/`claim.h`/`main.cpp`.
|
||||||
|
- Re-read bug 10's three existing pins + the 4-writer `_lastInboundMs` census against the phase-03-touched code: sentences still true (not just green).
|
||||||
|
- README: takeover bullet in "Disarmed is the only default"; end-to-end takeover procedure (`watch_target` + `key KeyA --hold 30000` + `--take-over status`, key-up-before-first-newcomer-key ordering) + evidence-table row; bugs 12 (page read `E<code>` as one char — `E10/E11/E12` misread as `E1`) and 13 (relayctl `_poll` abandoned the batch behind `OK`, breaking the adoption probe) added; section → "Thirteen bugs", all 6 cross-refs (README ×3, AGENTS.md ×2, `tls_pem.h` comment) updated.
|
||||||
|
- Page safety sentences confirmed in `test_page.js` (disarm/mouse-release/run-end/`taken over`/no-auto-reclaim on `E12`; nothing sent before the click; `E3` distinct). AGENTS.md: stale bug-count titles fixed only; invariant 8 already described the claim.
|
||||||
|
- Tests/lint: `bash .agents/validate.sh` → **validation OK**: 5 026 checks ×2 (plain + `SANITIZE=1`), 12 Tests-table rows match suites, page 59 286 B, Backlog contiguous, ws:// sentences clean; `bridge`/`console`/`rollback` builds SUCCESS; hygiene OK.
|
||||||
|
- Pin inversions proven (then reverted, no leftovers): (a) `closeLink` moved after adoption → exactly 1 FAIL ("funnel runs BEFORE…"); (b) `rearm();` inserted → 1 FAIL ("no `arm(`…"); (c) `claim.h` admits `X` unauthed → pin FAIL + `test_claim` matrix cell FAIL.
|
||||||
|
- README/AGENTS changes: as above; AGENTS.md gained no prose, only stale-fact fixes.
|
||||||
|
- Live infra: **no live changes.** A board is reachable but runs pre-phase-03 firmware; the two-controller takeover needs the operator's passphrase (guessing feeds the shared 5-fail lockout) and a reflash is a device side effect — the takeover rows in `.agents/HW_VERIFICATION.md` stand as `skipped (no device run)` with the exact close-out commands.
|
||||||
|
- Next pending: phase-03 final pass; then `04_controller_page_responsive/01_responsive_layout.md`.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+213
@@ -0,0 +1,213 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2379 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 280 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the busy board and --take-over
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 308 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
the busy board and the take-over
|
||||||
|
element ids
|
||||||
|
page 142 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2379 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 280 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the busy board and --take-over
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 308 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
the busy board and the take-over
|
||||||
|
element ids
|
||||||
|
page 142 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console -e rollback ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 30.4% (used 99744 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 40.0% (used 1257933 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.88 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 20.6% (used 67352 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 37.5% (used 1178173 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.87 seconds =========================
|
||||||
|
|
||||||
|
Processing rollback (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/rollback/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/rollback/firmware.elf
|
||||||
|
Checking size .pio/build/rollback/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 28.6% (used 93568 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.8% (used 1157609 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.88 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.879
|
||||||
|
console SUCCESS 00:00:00.867
|
||||||
|
rollback SUCCESS 00:00:00.882
|
||||||
|
========================= 3 succeeded in 00:00:02.628 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw strings ==
|
||||||
|
page bytes: 59286 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of these same bytes, both builds)
|
||||||
|
|
||||||
|
== README/AGENTS claims vs what this run measured ==
|
||||||
|
README/AGENTS claims: OK (5 026 checks quoted and measured in both runs; 12 Tests-table rows match their suites; 59 286 page bytes quoted; Backlog contiguous; every ws:// sentence names the rollback)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (109 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ -> HTTP 200 (TLS listener is up)
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 301, redirects to https://keyrelay.local/ (redirect-only: the post-phase-02 shape)
|
||||||
|
hw-probe tls 10.2.0.22:443 negotiated TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 (a self-signed verify failure is the expected shape; the fingerprint to compare it against is on the serial console)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console/rollback builds + page size
|
||||||
|
+ the numbers README/AGENTS quote + hygiene)
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
**Final verification pass** (all three task reports PASS; tasks already in `complete/`). One real defect found and fixed.
|
||||||
|
|
||||||
|
**Commands / results.** `bash tests/run_tests.sh` → `RESULT: all suites passed`, **5 104** checks (page **220**). `SANITIZE=1 bash tests/run_tests.sh` → passed. `bash .agents/validate.sh` → `validation OK`; bridge/console/rollback `SUCCESS`; `page bytes: 59262`; `README/AGENTS claims: OK`; `repo hygiene: OK`. Real-browser leg (headless Chromium, served assembled bytes): no horizontal scroll at 360/390/768/1440, ARM first-in-`main` and sticky, log height-capped, all 5 folds openable, all 3 changed page statements execute.
|
||||||
|
|
||||||
|
**Fix.** The folded notes' `<summary>` — the only route to five pinned safety sentences — rendered **17 px** on coarse pointers, under the page's own 44 px thumb floor, while every byte-level assertion stayed green. `src/web_page.h`: `button.k,.pill,input,summary{min-height:44px}` + `summary{padding:13px 0}`; `tests/test_page.js` +1 check (219→220); mutation-tested (reverting the CSS fails host *and* browser legs, then restored byte-for-byte).
|
||||||
|
|
||||||
|
**Criteria.** 1 ✓ browser-measured (physical phone still unobserved). 2 ✓ 220 checks + prose untouched. 3 ✓ page 59 286→**59 262 B** (−24 vs phase start), README = gate figure. 4 ✓ one `test_page` row at 220, intro 5 104 = measured. 5 ✓ gate green; phases 01–03 assertions unchanged apart from the re-pinned byte constant.
|
||||||
|
|
||||||
|
**Docs.** Re-pinned 59 262 (README, `config.h`, `test_conn.cpp`), totals 5 103→5 104, README Tests row 220; re-measured footprint rows (flash 1 257 901 / 1 178 141 / 1 157 593, TLS +6 176/+100 308); fixed stale "48 KB"/"~50 KB" (×4 src comments, ×2 README) and the 51 626-byte page-delivery row; added **bug 14** with root cause and renamed *Thirteen*→*Fourteen bugs* in README + AGENTS.md's two stale references; added the Phase 04 browser+device row to `.agents/HW_VERIFICATION.md`.
|
||||||
|
|
||||||
|
**Deviations.** Device leg stays open: live board serves 51 626 B (pre-phase image); no reflash attempted, per the standing no-unattended-reflash rule — no byte-equality claim made. `ruff check tools tests`: 29 findings, identical at HEAD and `--isolated`, `E9,F` clean, no repo lint config or gate step → left untouched. `.agents/validate.sh` is **untracked and has never been committed** (`git log --all --` empty) though README/AGENTS/PLAN cite it 13× — harness needs `git add` it; I did not stage anything.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+223
@@ -0,0 +1,223 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2379 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 280 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the busy board and --take-over
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 308 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
the busy board and the take-over
|
||||||
|
arming and the S= state frames
|
||||||
|
responsive layout
|
||||||
|
the safety statements that must never be tidied away
|
||||||
|
the folded notes are folded, not deleted
|
||||||
|
the page's constants are the board's constants
|
||||||
|
element ids
|
||||||
|
page 220 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2379 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 280 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the busy board and --take-over
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 308 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
the busy board and the take-over
|
||||||
|
arming and the S= state frames
|
||||||
|
responsive layout
|
||||||
|
the safety statements that must never be tidied away
|
||||||
|
the folded notes are folded, not deleted
|
||||||
|
the page's constants are the board's constants
|
||||||
|
element ids
|
||||||
|
page 220 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console -e rollback ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 30.4% (used 99744 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 40.0% (used 1257901 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.79 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 20.6% (used 67352 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 37.5% (used 1178141 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.78 seconds =========================
|
||||||
|
|
||||||
|
Processing rollback (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/rollback/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/rollback/firmware.elf
|
||||||
|
Checking size .pio/build/rollback/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 28.6% (used 93568 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.8% (used 1157593 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.79 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.791
|
||||||
|
console SUCCESS 00:00:00.781
|
||||||
|
rollback SUCCESS 00:00:00.790
|
||||||
|
========================= 3 succeeded in 00:00:02.361 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw strings ==
|
||||||
|
page bytes: 59262 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of these same bytes, both builds)
|
||||||
|
|
||||||
|
== README/AGENTS claims vs what this run measured ==
|
||||||
|
README/AGENTS claims: OK (5 104 checks quoted and measured in both runs; 12 Tests-table rows match their suites; 59 262 page bytes quoted; Backlog contiguous; every ws:// sentence names the rollback)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (131 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ -> HTTP 200 (TLS listener is up)
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 301, redirects to https://keyrelay.local/ (redirect-only: the post-phase-02 shape)
|
||||||
|
hw-probe tls 10.2.0.22:443 negotiated TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 (a self-signed verify failure is the expected shape; the fingerprint to compare it against is on the serial console)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console/rollback builds + page size
|
||||||
|
+ the numbers README/AGENTS quote + hygiene)
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
**Task 01 — responsive layout: done.**
|
||||||
|
|
||||||
|
**Implemented**
|
||||||
|
- `src/web_page.h` `<style>`: viewport `viewport-fit=cover` (no `maximum-scale`/`user-scalable`); `main` gains `calc(14px + env(safe-area-inset-bottom))`; `.arm` sticky `top:0;z-index:5` + `touch-action:manipulation`/`user-select:none` (also on `button.k`, `label.opt`, `.mousepad`); `#log` `overscroll-behavior:contain`; `@media (max-width:720px)` → `main` flex column (what makes the sticky ARM bar actually stick — sticky grid items never move), `.grid` forced `1fr`, tighter padding, `font-size:16px` inputs (iOS focus-zoom, named in the comment), log `max-height:40vh`; `@media (pointer:coarse)` → 44 px min-height floor on `button.k`/`.pill`/`input`, min-width floor on chord buttons/checkboxes. No markup/id changes.
|
||||||
|
- `tests/test_page.js`: +17 byte-level layout checks (viewport-fit, no zoom lock, 720 px breakpoint + one column + flex-column switch, 16 px floor, log cap, coarse floors, sticky ARM, overscroll, safe-area calc, touch-action, user-select, ARM first in `main`).
|
||||||
|
- Header comment paragraph on laptop+one-handed-phone layout and why the ARM bar is sticky.
|
||||||
|
- Re-pinned page size everywhere: 59 286 → **60 780 B** (42 chunks/11 ticks) in `test_conn.cpp` `kServedPageBytes` + its loop expectation, `config.h` comments, README; `test_conn` 280→282, `test_page` 142→159, totals 5026→**5045** in README ×2 + AGENTS.md; README Controller-page section gains the responsive paragraph + sticky-ARM row note; `.agents/HW_VERIFICATION.md` Phase 04 rows filled.
|
||||||
|
|
||||||
|
**Tests**: `bash tests/run_tests.sh` → 12 suites, 5045 checks, 0 failures ("RESULT: all suites passed"); `bash .agents/validate.sh` → **validation OK** (both suite runs, bridge/console/rollback builds, page bytes 60 780 quoted==measured, hygiene). Bridge RAM 99 744 B / Flash 1 259 421 B.
|
||||||
|
|
||||||
|
**Docs**: README + AGENTS.md numbers/paragraphs above.
|
||||||
|
|
||||||
|
**Decisions/deviations**: README page-byte/total figures updated now (task 03's scope) because the gate's doc check would otherwise fail. Device legs recorded per the hardware contract: capped `curl` pre-edit returned 51 626/`dd16e195…` (board still on pre-03 firmware); reflash and the 360 px phone observation → `skipped (no device)` / unobserved (live HID keyboard into the build host; no phone headlessly).
|
||||||
|
|
||||||
|
**Next pending task**: `04_controller_page_responsive/02_prose_trim_pinned.md`.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+215
@@ -0,0 +1,215 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2379 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 282 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the busy board and --take-over
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 308 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
the busy board and the take-over
|
||||||
|
responsive layout
|
||||||
|
element ids
|
||||||
|
page 159 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2379 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 282 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the busy board and --take-over
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 308 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
the busy board and the take-over
|
||||||
|
responsive layout
|
||||||
|
element ids
|
||||||
|
page 159 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console -e rollback ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 30.4% (used 99744 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 40.0% (used 1259421 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.87 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 20.6% (used 67352 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 37.5% (used 1179661 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Processing rollback (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/rollback/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/rollback/firmware.elf
|
||||||
|
Checking size .pio/build/rollback/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 28.6% (used 93568 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.8% (used 1159113 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.87 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.870
|
||||||
|
console SUCCESS 00:00:00.859
|
||||||
|
rollback SUCCESS 00:00:00.870
|
||||||
|
========================= 3 succeeded in 00:00:02.599 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw strings ==
|
||||||
|
page bytes: 60780 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of these same bytes, both builds)
|
||||||
|
|
||||||
|
== README/AGENTS claims vs what this run measured ==
|
||||||
|
README/AGENTS claims: OK (5 045 checks quoted and measured in both runs; 12 Tests-table rows match their suites; 60 780 page bytes quoted; Backlog contiguous; every ws:// sentence names the rollback)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (131 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ -> HTTP 200 (TLS listener is up)
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 301, redirects to https://keyrelay.local/ (redirect-only: the post-phase-02 shape)
|
||||||
|
hw-probe tls 10.2.0.22:443 negotiated TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 (a self-signed verify failure is the expected shape; the fingerprint to compare it against is on the serial console)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console/rollback builds + page size
|
||||||
|
+ the numbers README/AGENTS quote + hygiene)
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
**Task 02 — prose trim, pinned: done.**
|
||||||
|
|
||||||
|
- **Assertions first** (`tests/test_page.js`, 159→**184**): 21 new safety-sentence pins matched as *text* out of the served markup (tags stripped, entities decoded, case/whitespace folded, so a sentence survives a rewrap and a move into `<details>` but fails when deleted) — every-key/including-Enter + which machine + permission rule, banner before every fold, nothing-forwarded-while-disarmed + what disarms, `Esc` frees cursor *and* releases buttons + never reaches target + button restatement, stop-typing abandons the run, characters-are-positions/US layout, no-history/nothing-saved/nothing-scheduled, takeover **cannot arm — every handover lands disarmed**, clear-page warning + its fix, CAD VT-reboot, `dflt:1`-driven shout (plus "no static default-passphrase text in markup"); +2 folded-structure pins (`kv` wraps, Log option row) and `TEXT_FRAME_MAX`/`TEXT_JOB_MAX` re-pinned to `config.h`.
|
||||||
|
- **Fold** (`src/web_page.h`): 5 `<details>` (chords/KDE, passphrase, forwarded mouse, paste run, footer shortcuts), one-line summaries left visible, arming banner untouched, multi-sentence `title` tooltips shortened, inline styles → `.mt/.dim/.hot/.dimv`. Visible prose **2 866 → 862 B (~30 %)**; `#banner` byte-identical, all 11 `data-chord` values and every script-reached id intact, HTML balance checked.
|
||||||
|
- **Pins proven to bite**: 26 mutations (delete/soften each sentence, move the banner behind a fold, drift both constants) each fired its own check; file restored byte-for-byte.
|
||||||
|
- **Tests**: `node tests/test_page.js` 184/0 — run **before** and **after** the trim; `bash tests/run_tests.sh` and `SANITIZE=1 …` → *all suites passed* (5 068 each); `bash .agents/validate.sh` → **validation OK** (bridge/console/rollback SUCCESS; page bytes 59 223 quoted==measured; hygiene OK). `ruff` is not configured or gated in this repo; its 29 findings are all pre-existing in untouched `tools/*.py`/`tests/test_relayctl.py` — left alone.
|
||||||
|
- **Docs**: README Controller-page section gains the fold + "pinned by `tests/test_page.js`" paragraph and a table row; re-pinned page size 60 780→**59 223** (README ×2, `test_conn.cpp` `kServedPageBytes` + 41 chunks/11 ticks, `config.h`), footprint flash pair −64 B, Tests rows `test_page` 184 / `test_conn` 280, totals **5 068** in README ×2 and AGENTS.md. AGENTS.md: only the stale check count (per its no-expansion rule).
|
||||||
|
- **Decisions/deviations**: prose is only 2.5 KB of a 60 KB page and the fold *moves* text rather than deleting it, so meeting "page smaller in bytes" also needed the four longest `<script>` comment blocks and the section-divider rules tightened — every rule/why kept, README stays the durable record. Result 59 223 B: −1 557 vs this task's start, and below the 59 286 B the phase opened at. Device: one capped read-only `curl -sk https://keyrelay.local/` → 51 626 / `dd16e195…` (board still pre-phase-03); reflash and the 360 px phone observation recorded `skipped (no device)` in `.agents/HW_VERIFICATION.md`.
|
||||||
|
- **Next pending task**: `04_controller_page_responsive/03_suite_size_and_docs.md`.
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+219
@@ -0,0 +1,219 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2379 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 280 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the busy board and --take-over
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 308 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
the busy board and the take-over
|
||||||
|
responsive layout
|
||||||
|
the safety statements that must never be tidied away
|
||||||
|
the page's constants are the board's constants
|
||||||
|
element ids
|
||||||
|
page 184 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2379 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 280 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the busy board and --take-over
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 308 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
the busy board and the take-over
|
||||||
|
responsive layout
|
||||||
|
the safety statements that must never be tidied away
|
||||||
|
the page's constants are the board's constants
|
||||||
|
element ids
|
||||||
|
page 184 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console -e rollback ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 30.4% (used 99744 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 40.0% (used 1257869 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.91 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 20.6% (used 67352 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 37.5% (used 1178109 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.86 seconds =========================
|
||||||
|
|
||||||
|
Processing rollback (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/rollback/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/rollback/firmware.elf
|
||||||
|
Checking size .pio/build/rollback/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 28.6% (used 93568 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.8% (used 1157545 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.85 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.910
|
||||||
|
console SUCCESS 00:00:00.858
|
||||||
|
rollback SUCCESS 00:00:00.849
|
||||||
|
========================= 3 succeeded in 00:00:02.617 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw strings ==
|
||||||
|
page bytes: 59223 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of these same bytes, both builds)
|
||||||
|
|
||||||
|
== README/AGENTS claims vs what this run measured ==
|
||||||
|
README/AGENTS claims: OK (5 068 checks quoted and measured in both runs; 12 Tests-table rows match their suites; 59 223 page bytes quoted; Backlog contiguous; every ws:// sentence names the rollback)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (131 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ -> HTTP 200 (TLS listener is up)
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 301, redirects to https://keyrelay.local/ (redirect-only: the post-phase-02 shape)
|
||||||
|
hw-probe tls 10.2.0.22:443 negotiated TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 (a self-signed verify failure is the expected shape; the fingerprint to compare it against is on the serial console)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console/rollback builds + page size
|
||||||
|
+ the numbers README/AGENTS quote + hygiene)
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
**Task 03 — suite coverage, page size, README: done.**
|
||||||
|
|
||||||
|
- **`tests/test_page.js` 184→219**: new sections — arming & `S=` state frames on the wire (un-linked ARM click sends nothing; `A1` alone, `A0`+`R` in order, no optimistic flip either way, board-status arms/mirrors; disarmed press forwards nothing; whole-state restatements `S=00:04`→`S=02:05`→`S=00:`, autorepeat silent, page-field keys stay local but a modifier released *above* a field is let go; `Ctrl+Alt+Esc` = `R,A0`; every emitted frame matches the parser's wire form); folded-note structure (exactly 5 `<details>`, each with a naming summary + real sentences — folded ≠ deleted); id-coverage self-test proving the markup-vs-shim comparison names a fabricated id; `vm.runInContext` now tags `web_page.js` (stack traces + V8 coverage); stale "no CSS, no layout" header comment corrected.
|
||||||
|
- **Every new check proven to bite**: 10 mutations of `web_page.h` (delete/hollow/strip a fold, optimistic flip, repeat leak, stranded Shift, `R`-without-`A0`, inverted `A0,R`, broken wire form, config and page-copy constant drift) each fired its check; both files restored byte-for-byte (`sha256sum -c` OK). No `src/*` change from this task.
|
||||||
|
- **Suite/gate** (captured output): `bash tests/run_tests.sh` and `SANITIZE=1 …` → 12 suites, **5103 checks, 0 failures** each; `bash .agents/validate.sh` → **validation OK** (bridge/console/rollback SUCCESS; `page bytes: 59223` quoted==measured; docs claims OK; hygiene OK). V8 coverage of the page script: 86 % of code chars executed, 73/96 functions entered.
|
||||||
|
- **README**: one `test_page` row at **219** (no duplicate; the 47-check row is gone), intro + Quick-start totals → **5103**, Controller-page section now names the measurement (`curl -sk https://keyrelay.local/ | wc -c` vs the gate's `page bytes:` line, device leg recorded in HW_VERIFICATION). Quick start/Troubleshooting checked — no old-layout statements left. **AGENTS.md**: only the stale check count (per its no-expansion rule).
|
||||||
|
- **HW_VERIFICATION.md**: one capped probe ran — `curl -sk https://keyrelay.local/ | wc -c` → 51 626 / `dd16e195…` (board still pre-phase-03; tree's assembled page 59 223 / `dc26b441…b634`); phone row stays `skipped (no device)` with the 360 px / folded-notes-expandable checklist and commands. Byte-equality closes on the next flash (standing no-unattended-reflash call, as tasks 01–02 recorded).
|
||||||
|
- **Next pending task**: `05_mobile_controller` (this phase's task file moves to complete/ by the harness).
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
ok
|
||||||
+223
@@ -0,0 +1,223 @@
|
|||||||
|
== host suites: bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2379 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 280 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the busy board and --take-over
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 308 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
the busy board and the take-over
|
||||||
|
arming and the S= state frames
|
||||||
|
responsive layout
|
||||||
|
the safety statements that must never be tidied away
|
||||||
|
the folded notes are folded, not deleted
|
||||||
|
the page's constants are the board's constants
|
||||||
|
element ids
|
||||||
|
page 219 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== host suites: SANITIZE=1 bash tests/run_tests.sh ==
|
||||||
|
compiler: g++ inside localhost/ai-sandbox:ubuntu-9a08b1d500b0 (repo mounted at /work)
|
||||||
|
sanitizers: on
|
||||||
|
keystate 40 checks, 0 failures
|
||||||
|
proto 2379 checks, 0 failures
|
||||||
|
text 459 checks, 0 failures
|
||||||
|
mouse 48 checks, 0 failures
|
||||||
|
output 153 checks, 0 failures
|
||||||
|
ws_codec 135 checks, 0 failures
|
||||||
|
test_tls_pem 862 checks, 0 failures
|
||||||
|
test_tls_session 118 checks, 0 failures
|
||||||
|
test_conn 280 checks, 0 failures
|
||||||
|
test_claim 102 checks, 0 failures
|
||||||
|
handshake
|
||||||
|
frame encoding
|
||||||
|
frame decoding
|
||||||
|
key table
|
||||||
|
pasted text
|
||||||
|
mouse
|
||||||
|
protocol strings
|
||||||
|
the fail-safe wiring
|
||||||
|
the bridge handover (phase 03)
|
||||||
|
one routing path, two transports
|
||||||
|
the client side of pinning
|
||||||
|
the busy board and --take-over
|
||||||
|
the target-side verifier
|
||||||
|
t= 0.00s >>> Ctrl+Alt+Del <<<
|
||||||
|
relayctl 308 checks, 0 failures
|
||||||
|
the log stays readable
|
||||||
|
characters
|
||||||
|
key names in the log
|
||||||
|
sending text
|
||||||
|
refusing what it cannot type
|
||||||
|
stopping
|
||||||
|
the mouse
|
||||||
|
the connect path
|
||||||
|
the channel pill and the certificate line
|
||||||
|
the busy board and the take-over
|
||||||
|
arming and the S= state frames
|
||||||
|
responsive layout
|
||||||
|
the safety statements that must never be tidied away
|
||||||
|
the folded notes are folded, not deleted
|
||||||
|
the page's constants are the board's constants
|
||||||
|
element ids
|
||||||
|
page 219 checks, 0 failures
|
||||||
|
RESULT: all suites passed
|
||||||
|
|
||||||
|
== firmware: platformio run -e bridge -e console -e rollback ==
|
||||||
|
Processing bridge (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/bridge/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/bridge/firmware.elf
|
||||||
|
Checking size .pio/build/bridge/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 30.4% (used 99744 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 40.0% (used 1257869 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.80 seconds =========================
|
||||||
|
|
||||||
|
Processing console (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/console/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/console/firmware.elf
|
||||||
|
Checking size .pio/build/console/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [== ] 20.6% (used 67352 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 37.5% (used 1178109 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.79 seconds =========================
|
||||||
|
|
||||||
|
Processing rollback (platform: espressif32@8.0.0; board: esp32-s3-devkitc-1; framework: arduino)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (8.0.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 4.30312.261024 (3.3.12)
|
||||||
|
- framework-arduinoespressif32-esp32s3-libs @ 3.3.12
|
||||||
|
- tool-esptool @ 2.50400.0 (5.4.0)
|
||||||
|
- tool-riscv32-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- tool-xtensa-esp-elf-gdb @ 12.1.0+20221002
|
||||||
|
- toolchain-riscv32-esp @ 14.2.0+20260121
|
||||||
|
- toolchain-xtensa-esp-elf @ 14.2.0+20260121
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 45 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- Adafruit NeoPixel @ 1.15.5
|
||||||
|
|-- ESPmDNS @ 3.3.12
|
||||||
|
|-- USB @ 3.3.12
|
||||||
|
|-- Preferences @ 3.3.12
|
||||||
|
|-- WiFi @ 3.3.12
|
||||||
|
Building in release mode
|
||||||
|
Generating partitions .pio/build/rollback/partitions.bin
|
||||||
|
Retrieving maximum program size .pio/build/rollback/firmware.elf
|
||||||
|
Checking size .pio/build/rollback/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [=== ] 28.6% (used 93568 bytes from 327680 bytes)
|
||||||
|
Flash: [==== ] 36.8% (used 1157545 bytes from 3145728 bytes)
|
||||||
|
========================= [SUCCESS] Took 0.79 seconds =========================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
bridge SUCCESS 00:00:00.798
|
||||||
|
console SUCCESS 00:00:00.792
|
||||||
|
rollback SUCCESS 00:00:00.790
|
||||||
|
========================= 3 succeeded in 00:00:02.380 =========================
|
||||||
|
|
||||||
|
== page size: src/web_page.h raw strings ==
|
||||||
|
page bytes: 59223 (a reflashed device must serve exactly this many; kIndexHtmlLen is sizeof(kIndexHtml)-1 of these same bytes, both builds)
|
||||||
|
|
||||||
|
== README/AGENTS claims vs what this run measured ==
|
||||||
|
README/AGENTS claims: OK (5 103 checks quoted and measured in both runs; 12 Tests-table rows match their suites; 59 223 page bytes quoted; Backlog contiguous; every ws:// sentence names the rollback)
|
||||||
|
|
||||||
|
== repo hygiene ==
|
||||||
|
repo hygiene: OK (131 tracked files, no ignored-tracked files, no key-material shapes, no PII hits)
|
||||||
|
|
||||||
|
== hardware context (informational; cannot change the gate result) ==
|
||||||
|
hw-probe serial /dev/ttyACM0 present, readable (NOT opened: opening toggles DTR/RTS and resets the board)
|
||||||
|
hw-probe mdns keyrelay.local resolves -> 10.2.0.22
|
||||||
|
hw-probe https https://keyrelay.local/ -> HTTP 200 (TLS listener is up)
|
||||||
|
hw-probe http http://keyrelay.local/ -> HTTP 301, redirects to https://keyrelay.local/ (redirect-only: the post-phase-02 shape)
|
||||||
|
hw-probe tls 10.2.0.22:443 negotiated TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 (a self-signed verify failure is the expected shape; the fingerprint to compare it against is on the serial console)
|
||||||
|
hw-probe uv /home/ducoterra/.local/bin/uv — firmware builds available
|
||||||
|
hw-probe node /usr/bin/node — test_page.js runs
|
||||||
|
hw-probe evdev board 046d:c33f visible: keyboard=/dev/input/event19 (readable) mouse=/dev/input/event20 (readable) — python3 tools/watch_target.py runs ON THIS machine (the target)
|
||||||
|
validation OK (suites + sanitised suites + bridge/console/rollback builds + page size
|
||||||
|
+ the numbers README/AGENTS quote + hygiene)
|
||||||
@@ -10,7 +10,7 @@ The README is the single source of truth for behaviour, protocol, safety and mea
|
|||||||
This file is the operating manual: what must not break, how to build and verify, and where
|
This file is the operating manual: what must not break, how to build and verify, and where
|
||||||
the traps are. **Do not fork the protocol or the safety model into `docs/`** — a second copy
|
the traps are. **Do not fork the protocol or the safety model into `docs/`** — a second copy
|
||||||
drifts, and two paths that can disagree about arming is the exact failure this project keeps
|
drifts, and two paths that can disagree about arming is the exact failure this project keeps
|
||||||
having to fix (see README, "Ten bugs").
|
having to fix (see README, "Fourteen bugs").
|
||||||
|
|
||||||
## Phased execution
|
## Phased execution
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ This repository runs work through `.agents/phases/`:
|
|||||||
button, or the serial console — nothing else may gain the ability. The mouse is captured,
|
button, or the serial console — nothing else may gain the ability. The mouse is captured,
|
||||||
never grabbed: Pointer Lock, so `Esc` (the browser's own, never forwarded) always frees
|
never grabbed: Pointer Lock, so `Esc` (the browser's own, never forwarded) always frees
|
||||||
it. `mouse off` over serial removes the pointer interface at the descriptor level, which
|
it. `mouse off` over serial removes the pointer interface at the descriptor level, which
|
||||||
needs a reboot and is the rollback.
|
needs a reboot and is the pointer's rollback (the transport rollback is `-D KR_TLS=0`).
|
||||||
3. **Every fail-safe cancels work in flight; nothing "pauses"** — and **no fail-safe may be
|
3. **Every fail-safe cancels work in flight; nothing "pauses"** — and **no fail-safe may be
|
||||||
gated on the absence of the thing it detects.** `ControlServer::linked()` is false when a
|
gated on the absence of the thing it detects.** `ControlServer::linked()` is false when a
|
||||||
controller is gone, so gating the silence-disarm on it made the backstop for a vanished
|
controller is gone, so gating the silence-disarm on it made the backstop for a vanished
|
||||||
@@ -57,11 +57,19 @@ This repository runs work through `.agents/phases/`:
|
|||||||
VT it reboots the target. Same reasoning for the VT-switch chords.
|
VT it reboots the target. Same reasoning for the VT-switch chords.
|
||||||
7. **One frame is one command.** ASCII, no JSON inbound, ≤ `kWsMessageCap`. Long payloads are
|
7. **One frame is one command.** ASCII, no JSON inbound, ≤ `kWsMessageCap`. Long payloads are
|
||||||
the *controller's* problem to split, with the board reporting how much it still holds.
|
the *controller's* problem to split, with the board reporting how much it still holds.
|
||||||
8. **The keyboard is exclusive; the board is not.** A second `/ws` upgrade is refused (`E3`);
|
8. **The keyboard is exclusive; the board is not.** A second `/ws` upgrade cannot drive
|
||||||
read-only HTTP is served even while a controller holds the bridge. Keep both routes going
|
the keyboard: it may only stand in the spare slot as a pending claim that
|
||||||
through `serveReadonly()` so they cannot drift apart.
|
authenticates (same passphrase, same shared lockout) and may then send nothing but
|
||||||
|
`K` and the deliberate `X` — `X` hands the bridge over *through the release funnel*
|
||||||
|
(incumbent told with `E12`, every key and button released) and the board lands
|
||||||
|
**disarmed**; nothing reachable from `X` may arm. Every other frame is refused by
|
||||||
|
the one whitelist in `src/claim.h`, which drops the claim, and no claim frame ever
|
||||||
|
reaches the command dispatcher. A second claim, or a third socket, is
|
||||||
|
refused (`503`/`E3`, or the slot refusal); read-only HTTP is served even while a
|
||||||
|
controller holds the bridge. Keep both routes going through `serveReadonly()` so they
|
||||||
|
cannot drift apart.
|
||||||
|
|
||||||
## Three tables that must agree
|
## Tables that must agree
|
||||||
|
|
||||||
| table | where | names | pinned by |
|
| table | where | names | pinned by |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
@@ -70,8 +78,11 @@ This repository runs work through `.agents/phases/`:
|
|||||||
| `strokeFor()` | `src/text.h` | character → modifier + usage | `tests/test_text.cpp` (whole printable range walked) + tool/board agreement on all 128 ASCII codes |
|
| `strokeFor()` | `src/text.h` | character → modifier + usage | `tests/test_text.cpp` (whole printable range walked) + tool/board agreement on all 128 ASCII codes |
|
||||||
| mouse button bits | `src/mouse.h`, `src/web_page.h` (`MOUSE_BTN`), `tools/relayctl.py` (`MOUSE_BUTTONS`) | HID bit → DOM `MouseEvent.button` **and** name | `test_proto.cpp` (page↔config), `test_relayctl.py` (tool↔page↔config) |
|
| mouse button bits | `src/mouse.h`, `src/web_page.h` (`MOUSE_BTN`), `tools/relayctl.py` (`MOUSE_BUTTONS`) | HID bit → DOM `MouseEvent.button` **and** name | `test_proto.cpp` (page↔config), `test_relayctl.py` (tool↔page↔config) |
|
||||||
| evdev axis codes | `tools/watch_target.py` (`REL_*`) | kernel code → label, for the *verifier* | `test_relayctl.py` — pinned to `<linux/input-event-codes.h>`, **not** to the report's byte order |
|
| evdev axis codes | `tools/watch_target.py` (`REL_*`) | kernel code → label, for the *verifier* | `test_relayctl.py` — pinned to `<linux/input-event-codes.h>`, **not** to the report's byte order |
|
||||||
|
| certificate names | `src/tls_pem.h` (`sanEntries`, `kSanOidTlv`, `sanExtensionFullDer`) | the CN + SAN the certificate carries, **and the OID that labels the SAN**, and the same list rendered for the operator | `test_tls_pem.cpp` — the CN must be `kHostname` + `.local`, the 34 DER bytes were cross-checked against what OpenSSL writes for those three names, and so were the 43 bytes of the whole extension: the writer takes the OID as **DER content bytes** (`55 1D 11`), and handing it the dotted text produces a valid certificate with no SAN (README bug 11) |
|
||||||
|
| mbed TLS return codes + the refusal alert | `src/tls_session.h` (`kMbed*`, `mapIo`, `renderFatalAlert`) | "would block" vs "peer gone" vs "fatal" for every session call; the 7-byte RFC 5246 alert record a slot-cap refusal sends | `test_tls_session.cpp` walks the tables; `static_assert`s in `tls_session.cpp` weld the three copied error-code literals to the shipped library's macros — the code values differ between mbed TLS 2.x and 3.x, which is exactly how a confident transplant goes wrong |
|
||||||
|
| page build marker | `src/web_page.h` (`#if KR_TLS` between the raw-string chunks) | which build served the page — the TLS page refuses an `http:` origin outright; only the rollback page may speak plaintext | `test_page.js`, `test_relayctl.py` and `validate.sh` each assemble the served bytes from the chunks, and the two marker branches are asserted **equal length**, so one page byte count covers both builds |
|
||||||
|
|
||||||
The last row exists because it was guessed from the HID report's field order and was wrong
|
The evdev row exists because it was guessed from the HID report's field order and was wrong
|
||||||
(`REL_WHEEL` is 8, not 2): a working wheel looked dead and the summary said no scroll
|
(`REL_WHEEL` is 8, not 2): a working wheel looked dead and the summary said no scroll
|
||||||
happened. Codes the verifier has no name for are now counted and printed rather than
|
happened. Codes the verifier has no name for are now counted and printed rather than
|
||||||
dropped — the same fix as giving F13–F24 names. **When you add a table that decodes
|
dropped — the same fix as giving F13–F24 names. **When you add a table that decodes
|
||||||
@@ -87,11 +98,13 @@ the page and `TEXT_FRAME_MAX` / `MAX_PASTE_CHARS` in `relayctl.py` are hand-copi
|
|||||||
## Build, test, flash
|
## Build, test, flash
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tests/run_tests.sh # 3406 checks, no hardware
|
tests/run_tests.sh # 5104 checks, no hardware
|
||||||
SANITIZE=1 tests/run_tests.sh # + ASan/UBSan — use it for anything touching buffers
|
SANITIZE=1 tests/run_tests.sh # + ASan/UBSan — use it for anything touching buffers
|
||||||
# PlatformIO. The venv below runs Python 3.15, which esptool REFUSES ("Python version must
|
# PlatformIO. The venv below runs Python 3.15, which esptool REFUSES ("Python version must
|
||||||
# be 3.10..3.14") -- builds fine, dies at upload. Use uv's 3.14 for anything with -t upload:
|
# be 3.10..3.14") -- builds fine, dies at upload. Use uv's 3.14 for anything with -t upload:
|
||||||
uv run --python 3.14 --with platformio python -m platformio run -e bridge -e console
|
uv run --python 3.14 --with platformio python -m platformio run -e bridge -e console -e rollback
|
||||||
|
# `rollback` is [env:bridge] with -DKR_TLS=0: the plaintext way back. The gate builds it on
|
||||||
|
# every run, because a way back that stopped compiling is not a way back.
|
||||||
python3 tools/to_bootloader.py # 1200-baud touch → ROM download mode
|
python3 tools/to_bootloader.py # 1200-baud touch → ROM download mode
|
||||||
uv run --python 3.14 --with platformio python -m platformio run -e bridge -t upload
|
uv run --python 3.14 --with platformio python -m platformio run -e bridge -t upload
|
||||||
```
|
```
|
||||||
@@ -113,12 +126,17 @@ Environment facts that will otherwise cost you a cycle:
|
|||||||
thing that checks the no-HID `#else` branches.
|
thing that checks the no-HID `#else` branches.
|
||||||
- **The page is served from flash.** A change to `src/web_page.h` is invisible on the network
|
- **The page is served from flash.** A change to `src/web_page.h` is invisible on the network
|
||||||
until the board is reflashed — and a tab that is already open keeps the old page in memory
|
until the board is reflashed — and a tab that is already open keeps the old page in memory
|
||||||
until it is reloaded. Confirm with `curl -s http://keyrelay.local/ | wc -c` against the size
|
until it is reloaded. Confirm with `curl -sk https://keyrelay.local/ | wc -c` against the
|
||||||
of the raw string in `src/web_page.h`, and grep the served bytes for the element you added.
|
`page bytes:` line the gate prints (the raw-string chunks plus the 19-byte build marker;
|
||||||
- **This directory is a git repository** (one commit, `Initial import`). Use it: `git diff`
|
equal-length branches make it one number for both builds), and grep the served bytes for
|
||||||
before and after any sweeping edit. Note that the commit predates the mouse and
|
the element you added. Plain `http://` returns a 301 and no page bytes at all (task 04), so a `curl -s
|
||||||
`output.h` work, so `git checkout` reverts far more than you asked for — restore a
|
http://…` that prints a redirect body means the transport is working, not that the page is
|
||||||
*region*, not a file.
|
stale — fetch over `https://`, and `curl -s -L` is not a substitute: it follows to the same
|
||||||
|
bytes whether or not the flash changed.
|
||||||
|
- **This directory is a git repository** with a short history that predates most of the
|
||||||
|
current tree. Use it: `git diff` before and after any sweeping edit, and remember that
|
||||||
|
`git checkout <file>` reverts far more than you asked for — restore a *region*, not a
|
||||||
|
file.
|
||||||
- **Never do a multi-line `.*?` regex rewrite across a source file.** One did
|
- **Never do a multi-line `.*?` regex rewrite across a source file.** One did
|
||||||
`#if KR_DEBUG_LINK.*?#endif` with `DOTALL` to remove a diagnostic block and deleted ~500
|
`#if KR_DEBUG_LINK.*?#endif` with `DOTALL` to remove a diagnostic block and deleted ~500
|
||||||
lines of `main.cpp` — `onMessage()`, `setup()`, the console, the USB handlers — because
|
lines of `main.cpp` — `onMessage()`, `setup()`, the console, the USB handlers — because
|
||||||
@@ -148,12 +166,23 @@ wired now — but ask, of every green check, *what would still pass if this were
|
|||||||
|
|
||||||
## Editing rules
|
## Editing rules
|
||||||
|
|
||||||
- **Pure modules** (`keystate.h`, `proto.h`, `text.h`, `ws_codec.h`, `sha1.h`) include no
|
- **Pure modules** (`keystate.h`, `mouse.h`, `output.h`, `proto.h`, `status.h`, `text.h`,
|
||||||
Arduino or ESP-IDF headers. That is what lets the identical code run on a host under
|
`ws_codec.h`, `sha1.h`, `tls_pem.h`, `claim.h`) include no Arduino or ESP-IDF headers. That is what lets the
|
||||||
sanitisers. Keep it.
|
identical code run on a host under sanitisers. Keep it — and keep the *crypto library*
|
||||||
- **`web_page.h`** is a C++ raw string: no compiler looks at the HTML or JS. Run
|
out too: `tls_pem.h` handles the identity's strings, `tls.h`/`tls.cpp` handle the
|
||||||
`tests/test_page.js` after touching it (it executes the page against a DOM shim and checks
|
library, and the host suite includes the first and never the second. `tls_session.h`
|
||||||
that every element id the script reaches for exists in the markup).
|
follows the same split inside one file: its policy half (deadline, admission,
|
||||||
|
library-return table, alert record, queues) is pure and host-tested, and its
|
||||||
|
`kr::tls::Session` class hides behind `KR_TLS && ESP_PLATFORM` — with the mbed TLS
|
||||||
|
includes **outside** the namespace, because inside it they declare `::FILE` as
|
||||||
|
`kr::tls::FILE` and every later stdio user in the translation unit dies. `conn.h` splits the
|
||||||
|
same way and for the same reason: `plaintextRoute()`, the HTTP response head, the redirect
|
||||||
|
builder and the page send cursor are pure and walked by `test_conn.cpp`; only `kr::Conn`
|
||||||
|
itself needs `WiFiClient`.
|
||||||
|
- **`web_page.h`** is C++ raw string (plural since task 05: chunks joined by one
|
||||||
|
`#if KR_TLS` build marker of equal-length branches): no compiler looks at the HTML or JS.
|
||||||
|
Run `tests/test_page.js` after touching it (it executes the assembled page against a DOM
|
||||||
|
shim and checks that every element id the script reaches for exists in the markup).
|
||||||
- **A protocol change is four edits, minimum**: `proto.h` (parser + `format*` + its header
|
- **A protocol change is four edits, minimum**: `proto.h` (parser + `format*` + its header
|
||||||
comment), `web_page.h`, `relayctl.py`, and the README Wire protocol table + error-code list.
|
comment), `web_page.h`, `relayctl.py`, and the README Wire protocol table + error-code list.
|
||||||
Tests that pin the wire format are `test_proto.cpp` and `test_relayctl.py`.
|
Tests that pin the wire format are `test_proto.cpp` and `test_relayctl.py`.
|
||||||
@@ -161,12 +190,19 @@ wired now — but ask, of every green check, *what would still pass if this were
|
|||||||
Safety model, Security model trust-boundary table, Controller page, Wire protocol,
|
Safety model, Security model trust-boundary table, Controller page, Wire protocol,
|
||||||
Architecture file map, Tests table, Backlog, and the docstring at the top of whichever module
|
Architecture file map, Tests table, Backlog, and the docstring at the top of whichever module
|
||||||
you changed. This README documents *why* decisions were made — keep the reasoning, don't just
|
you changed. This README documents *why* decisions were made — keep the reasoning, don't just
|
||||||
restate the behaviour.
|
restate the behaviour. `.agents/validate.sh` checks the mechanical half so nobody has to
|
||||||
|
remember it: the check count and the page byte count the docs quote must equal what the run
|
||||||
|
measured, the Backlog must be numbered 1..N with nothing closed left inside it, and every line
|
||||||
|
that mentions a plaintext `ws://` session must name the rollback build on the same line (wrap
|
||||||
|
the sentence so it does — the point is that no sentence can read as if plaintext were default).
|
||||||
- Secrets: never in a file, a URL, a log or a shell history. The serial `token` command does not
|
- Secrets: never in a file, a URL, a log or a shell history. The serial `token` command does not
|
||||||
echo it; keep it that way. `/status` takes the token as a query parameter because `curl` is
|
echo it; keep it that way. `/status` takes the token as a query parameter because `curl` is
|
||||||
the point of that endpoint — do not add the token to any other URL.
|
the point of that endpoint — do not add the token to any other URL. The TLS private key is
|
||||||
|
the same rule with more teeth: it is generated on the board and lives only in NVS, so
|
||||||
|
nothing may print it, hash-and-print it in a way that reads as the key, or be added to the
|
||||||
|
tree as a `.pem`. Fingerprint yes, key never.
|
||||||
- `TODO.md` holds **open items only**; clear it when they are done. The durable record is the
|
- `TODO.md` holds **open items only**; clear it when they are done. The durable record is the
|
||||||
README: a fixed bug belongs in "Ten bugs this verification caught" with its root cause and
|
README: a fixed bug belongs in "Fourteen bugs this verification caught" with its root cause and
|
||||||
how it was proven, a new capability in the section that describes it, and a rule worth
|
how it was proven, a new capability in the section that describes it, and a rule worth
|
||||||
repeating here. A TODO that has accumulated resolved items tells the next reader nothing about
|
repeating here. A TODO that has accumulated resolved items tells the next reader nothing about
|
||||||
what is left to do.
|
what is left to do.
|
||||||
@@ -174,6 +210,15 @@ wired now — but ask, of every green check, *what would still pass if this were
|
|||||||
## Current defaults worth knowing
|
## Current defaults worth knowing
|
||||||
|
|
||||||
Default passphrase `changeme` (the page and `status` shout about it via `dflt:1`); AP
|
Default passphrase `changeme` (the page and `status` shout about it via `dflt:1`); AP
|
||||||
`keyrelay-ctl` / `keyrelay-ctl-pass`; `http://keyrelay.local` or `192.168.4.1`; paste pace
|
`keyrelay-ctl` / `keyrelay-ctl-pass`; **`https://keyrelay.local`** or `https://192.168.4.1`
|
||||||
|
(port 443 — `:80` answers a 301 to it, a 426 to a plaintext `/ws` upgrade or to any request
|
||||||
|
carrying `?token=`, and nothing else; plaintext only in the `-D KR_TLS=0` rollback, which is
|
||||||
|
`pio run -e rollback`); the TLS identity is an ECDSA secp256r1 keypair and self-signed
|
||||||
|
certificate generated on the board's first boot and stored in NVS (`tlscrt`, `tlskey`,
|
||||||
|
`tlscrtfp`) so it survives reflashing — rotated only by `nvs erase`, printed once at that first
|
||||||
|
boot and again on demand by the serial `cert` command, which prints the SHA-256 fingerprint, the
|
||||||
|
validity window and the names, and has no argument form that yields the key; at most 2 live TLS
|
||||||
|
sessions (`kTlsMaxSessions`, RAM-sized, a third is refused with an alert) and a 10 s handshake
|
||||||
|
deadline (`kTlsHandshakeMs`); paste pace
|
||||||
12 ms/character, 120 characters per frame, 2048 per run; silence disarm 8 s; NVS holds the
|
12 ms/character, 120 characters per frame, 2048 per run; silence disarm 8 s; NVS holds the
|
||||||
station credentials and passphrase and survives reflashing.
|
station credentials and passphrase and survives reflashing.
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ lib_deps =
|
|||||||
build_flags =
|
build_flags =
|
||||||
${usb.fs_include}
|
${usb.fs_include}
|
||||||
-DARDUINO_USB_MODE=0
|
-DARDUINO_USB_MODE=0
|
||||||
|
; TLS on: the page and the WebSocket are served over HTTPS/wss with an identity the
|
||||||
|
; board generated itself (src/tls.h). The rollback value is KR_TLS=0 -- plain http://
|
||||||
|
; and ws:// only, exactly the pre-phase-02 behaviour, same standing as `mouse off`.
|
||||||
|
; It is a compile-time switch because a device that cannot be reached over TLS after a
|
||||||
|
; bad provisioning change needs a way back that is a reflash, not a repair.
|
||||||
|
-DKR_TLS=1
|
||||||
; CDC_ON_BOOT=0 is what makes the Logitech identity possible: with 1,
|
; CDC_ON_BOOT=0 is what makes the Logitech identity possible: with 1,
|
||||||
; cores/esp32/main.cpp calls USB.begin() from app_main() before setup() runs, the
|
; cores/esp32/main.cpp calls USB.begin() from app_main() before setup() runs, the
|
||||||
; first tinyusb_init() fixes the VID/PID/strings as Espressif's, and every
|
; first tinyusb_init() fixes the VID/PID/strings as Espressif's, and every
|
||||||
@@ -79,3 +85,31 @@ build_flags =
|
|||||||
${usb.fs_include}
|
${usb.fs_include}
|
||||||
-DARDUINO_USB_MODE=1
|
-DARDUINO_USB_MODE=1
|
||||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
; Same TLS default as bridge: the control plane is what this environment exists to
|
||||||
|
; diagnose, so it must behave identically. Roll back with -DKR_TLS=0 (see env:rollback).
|
||||||
|
-DKR_TLS=1
|
||||||
|
|
||||||
|
; ---------------------------------------------------------------------------
|
||||||
|
; Rollback: the bridge with TLS compiled out, i.e. the pre-phase-02 build -- plain
|
||||||
|
; http:// page and ws:// socket on :80, no :443, no identity in NVS. PLAN.md locks this
|
||||||
|
; in as the way back from a provisioning change that left a board unreachable over TLS:
|
||||||
|
; the fix is a reflash, not a repair, and a way back only counts if it still builds.
|
||||||
|
; That is why it is an environment and not a remembered command line: `pio run` has no
|
||||||
|
; --build-flags option in this PlatformIO, and a second -DKR_TLS on the command line
|
||||||
|
; beside the -DKR_TLS=1 above is a macro redefinition whose winner depends on flag order.
|
||||||
|
; Unflagging the default and setting 0 is the only spelling with no ambiguity -- and
|
||||||
|
; .agents/validate.sh builds this env on every run, so the rollback cannot quietly rot.
|
||||||
|
;
|
||||||
|
; Nothing about arming moves here: KR_TLS changes which port carries bytes, never who may
|
||||||
|
; type (src/config.h, README "The rollback"). The page this image serves is the KR_TLS=0
|
||||||
|
; branch of the marker in src/web_page.h; it is the only build where a plaintext ws://
|
||||||
|
; session is the normal case, and the only one the README permits it in.
|
||||||
|
; ---------------------------------------------------------------------------
|
||||||
|
[env:rollback]
|
||||||
|
extends = env:bridge
|
||||||
|
build_flags =
|
||||||
|
${env:bridge.build_flags}
|
||||||
|
-DKR_TLS=0
|
||||||
|
build_unflags =
|
||||||
|
${env:bridge.build_unflags}
|
||||||
|
-DKR_TLS=1
|
||||||
|
|||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
/**
|
||||||
|
* keyrelay -- the pending claim: the state machine of the second socket that may
|
||||||
|
* authenticate but can do nothing else. Pure logic, host-tested
|
||||||
|
* (tests/test_claim.cpp): no Arduino, no IDF, no sockets.
|
||||||
|
*
|
||||||
|
* Why this door exists
|
||||||
|
* --------------------
|
||||||
|
* "No room" used to be the only answer a second `/ws` upgrade ever got, which turned
|
||||||
|
* the most ordinary accident -- a laptop tab that kept the socket open -- into a dead
|
||||||
|
* board until you found the other tab or rebooted. A claim is therefore not a new
|
||||||
|
* privilege: it is the SAME privilege (the passphrase) exercised from a different
|
||||||
|
* socket, plus one deliberate act. This module holds the predicates of that act's
|
||||||
|
* first half -- stand in the doorway -- because the predicates are the whole safety
|
||||||
|
* argument, and an argument nothing executes is an argument a later edit can undo
|
||||||
|
* without noticing. control_server.cpp keeps the routing and the sockets; the answer
|
||||||
|
* to "what may this frame be, in this role, doing that?" lives here and is walked by
|
||||||
|
* tests under sanitisers, in the style of output.h.
|
||||||
|
*
|
||||||
|
* The load-bearing rule of everything here: a pending claim can never produce an
|
||||||
|
* armed board. Nothing in this file knows that arming exists; the whitelist below is
|
||||||
|
* what makes that true by construction rather than by care.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "proto.h"
|
||||||
|
#include "tls_session.h" // tls::deadlinePassed -- one wrap-safe clock arithmetic
|
||||||
|
|
||||||
|
namespace kr {
|
||||||
|
namespace claim {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What the spare slot is for.
|
||||||
|
*
|
||||||
|
* `Readonly` is the long-standing behaviour: the read-only routes through
|
||||||
|
* `serveReadonly()`, one response, close. `Claim` is a pending takeover claim --
|
||||||
|
* a second controller that has completed the WebSocket upgrade and (if its
|
||||||
|
* passphrase was right) authenticated, and holds nothing. The spare slot exists
|
||||||
|
* exactly once -- one incumbent plus one more, never one incumbent plus two
|
||||||
|
* claimants -- which is the same shape the server already had and the TLS-session
|
||||||
|
* ceiling from phase 02 (`kTlsMaxSessions` = 2), so a claim costs no new socket,
|
||||||
|
* no new buffer class and no new auth path.
|
||||||
|
*/
|
||||||
|
enum class ViewerRole : uint8_t { Readonly, Claim };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One claim at a time. A second `/ws` upgrade on a slot that already carries a
|
||||||
|
* claim gets the `503` the slot always answered with; a third *socket* never gets
|
||||||
|
* this far -- it is refused at the slot table (`tls::admitConnection`), which is
|
||||||
|
* RAM policy, not takeover policy, and does not move.
|
||||||
|
*/
|
||||||
|
inline bool acceptsUpgrade(ViewerRole role) { return role != ViewerRole::Claim; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* THE WHITELIST -- the whole safety argument of the pending claim, as one visible
|
||||||
|
* list. A frame from the spare slot is honoured only when this says so:
|
||||||
|
*
|
||||||
|
* allowed, unauthenticated: T<token> -- the credential the claim is made of
|
||||||
|
* allowed, authenticated: T<token> -- re-present it (same compare, same lockout)
|
||||||
|
* K -- keepalive, freshness only
|
||||||
|
* X -- claim the bridge: the one
|
||||||
|
* deliberate act this door exists for. It
|
||||||
|
* joins the whitelist HERE, nowhere else,
|
||||||
|
* and only in the authenticated branch --
|
||||||
|
* the handover it triggers
|
||||||
|
* (control_server.cpp::handOverClaim)
|
||||||
|
* runs the release funnel, lands the
|
||||||
|
* board DISARMED, and cannot arm: an
|
||||||
|
* unauthenticated `X` would be the whole
|
||||||
|
* privilege with none of the proof.
|
||||||
|
*
|
||||||
|
* refused in both states, spelled out because this list is why "a pending claim can
|
||||||
|
* arm nothing" is checkable rather than believed:
|
||||||
|
*
|
||||||
|
* A1 / A0 arm / disarm -- arming is never reachable from a claim
|
||||||
|
* S= key state -- nothing a claim sends reaches the target
|
||||||
|
* C, Y= chords, Ctrl+Alt+Del included (it is fixed on the device, and the
|
||||||
|
* device never sees this frame at all)
|
||||||
|
* P= pasted text
|
||||||
|
* M= mouse reports
|
||||||
|
* R release -- a claim must not be able to disturb the
|
||||||
|
* incumbent's keyboard either; only the funnel
|
||||||
|
* on a real link change does that
|
||||||
|
* H status -- the status frame is the controller's, and
|
||||||
|
* phase 03 does not weaken that even for the
|
||||||
|
* claimant: the claimant's answer IS its own
|
||||||
|
* non-adoption (`OK` with no status frame
|
||||||
|
* behind it means the bridge is held -- the
|
||||||
|
* `busy` field exists so a /status poller and
|
||||||
|
* the post-handover frames say the same thing
|
||||||
|
* in counter-and-link-state terms). Asking
|
||||||
|
* `H` here earns `E6` and the drop, which is
|
||||||
|
* exactly what the page's own detection reads
|
||||||
|
* None/Bad -- an unparseable frame from a non-privileged socket is a drop, not
|
||||||
|
* a conversation
|
||||||
|
*
|
||||||
|
* A `Readonly` slot has no WebSocket at all, so no frame is honoured in that role;
|
||||||
|
* the parameter is here so the predicate is the single authority over both roles and
|
||||||
|
* the test can walk the whole matrix.
|
||||||
|
*/
|
||||||
|
inline bool honours(ViewerRole role, bool authed, proto::Cmd cmd) {
|
||||||
|
if (role != ViewerRole::Claim) return false;
|
||||||
|
if (!authed) return cmd == proto::Cmd::Auth;
|
||||||
|
switch (cmd) {
|
||||||
|
case proto::Cmd::Auth:
|
||||||
|
case proto::Cmd::KeepAlive:
|
||||||
|
case proto::Cmd::Takeover: // `X` -- the deliberate act, authenticated branch only
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the claim past its clock? One anchor (`_claimAtMs`), two limits:
|
||||||
|
*
|
||||||
|
* unauthenticated kAuthTimeoutMs -- exactly the controller's rule: open the
|
||||||
|
* socket, present the passphrase, or go away
|
||||||
|
* authenticated kClaimTimeoutMs -- a claim that has not asked for the bridge
|
||||||
|
* by now is dropped and its session freed
|
||||||
|
* (config.h: an unanswered claim sitting on
|
||||||
|
* one of two TLS sessions is a nuisance
|
||||||
|
* attack, and 15 s is plenty for a click)
|
||||||
|
*
|
||||||
|
* The anchor is refreshed only by frames of this claim, and it is NEVER
|
||||||
|
* `_lastInboundMs`: a claim attempt is not the operator being alive, and the
|
||||||
|
* controller-silence fail-safe must not be held open by a socket that is trying to
|
||||||
|
* take over. That distinction is `readSome()`'s comment and bug 10; this function is
|
||||||
|
* where the claim's own clock lives instead. Wrap-safe via deadlinePassed, so the
|
||||||
|
* 49.7-day `millis()` rollover can neither make a claim immortal nor evict a live one.
|
||||||
|
*/
|
||||||
|
inline bool expired(bool authed, uint32_t nowMs, uint32_t anchorMs) {
|
||||||
|
return tls::deadlinePassed(nowMs, anchorMs, authed ? kClaimTimeoutMs : kAuthTimeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace claim
|
||||||
|
} // namespace kr
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user