Files
keyrelay/.agents/phases/complete/01_verification_gate/02_hardware_contract.md
T
ducoterra 121491bab4 phase: 01_verification_gate
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`).
2026-11-09 00:04:45 -05:00

4.3 KiB
Raw Blame History

Task 02 — The hardware contract: what the gate cannot see

Phase: 01_verification_gate · Source: TODO.md — (foundation; AGENTS.md "Verification 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.") Story: n/a

Objective

Make the device-only half of this roadmap explicit, runnable and non-blocking: a probe that reports what hardware is reachable right now, and a file that pairs every device-only claim with the exact command that would confirm it — so a unit running unattended never hangs on a board and never claims a device result it did not observe.

Work

  1. .agents/HW_VERIFICATION.md — three sections:
    • Baseline (measured, from task 01's gate run): bridge RAM 26.7% (87 592 B) / Flash 36.1% (1 135 293 B), console RAM 16.8% (55 200 B) / Flash 33.6% (1 056 081 B), 3406 host checks. Later phases quote a delta against these numbers.
    • Claims that need hardware, each with its command and its expected observation, at least: keystroke/chord delivery and which keys a chord really was (python3 tools/watch_target.py on the target); served-page byte agreement (curl -sk https://keyrelay.local/ | wc -c vs the gate's page count — http until phase 02 lands, https -k after); TLS handshake + the SHA-256 fingerprint printed once on the serial console; takeover actually releasing the incumbent's held keys (watch_target.py must show key-up before the new session's first key); the mobile soft keyboard and touch pad (a phone on the board's AP, watch_target.py for both the characters and the pointer deltas).
    • The blocking rule: a unit attempts a device step at most once, wrapped in timeout, and writes the outcome — ok, skipped (no device), or failed: … — with the command it ran into the section's table. It never loops on a port, never re-flashes to "see if it works", and never leaves the board in ROM download mode without saying so in the report (tools/to_bootloader.py drops it there deliberately; a power cycle is the only way back and nobody may be there to do it).
  2. tools/hw_probe.sh (executable, set -uo pipefail, no side effects) — prints one line per fact and always exits 0 (it informs, it does not gate): /dev/ttyACM*//dev/ttyUSB* present?; keyrelay.local resolves?; https reachable (curl -sk -m 2 -o /dev/null -w '%{http_code}')?; http reachable and does it redirect?; uv on PATH?; node on PATH?; is a watch_target.py-capable evdev device visible (informational only). Nothing is opened on a serial port except a test -e/test -r check — opening it toggles DTR/RTS and resets the board, so the probe must not.
  3. .agents/validate.sh — append bash tools/hw_probe.sh as the last, non-affecting line (its exit status is deliberately not folded into rc), with a comment saying why: it is context for whoever reads the gate output, not a check.
  4. README.md — no behaviour change yet; only if the "End-to-end verification" section contradicts what the probe just printed, fix the stale line. Do not restructure it here.
  • ASSUMPTION: at execution time a board may be attached and reachable but nobody is watching it, so "board available" is treated as best-effort. Any phase that needs an on-target second machine (watch_target.py) records skipped (no device) rather than inventing a result. This is the user-facing cost of running the roadmap unattended; the file is where they pick those checks up.

Testing & Quality

  • bash tools/hw_probe.sh exits 0 with and without a board, with and without uv, from any cwd; it never blocks (every probe has a -m/timeout).
  • The gate still passes with the probe line added, and its output cannot flip rc.
  • shellcheck-clean if shellcheck is installed; otherwise -n syntax check in the report.

Completion Criteria

  • bash tools/hw_probe.sh prints the six facts and exits 0 (paste its output).
  • .agents/HW_VERIFICATION.md covers every phase 02–05 device claim listed above, each with command + expected observation + the rule about one attempt.
  • bash .agents/validate.sh still exits 0.
  • Full test suite green; no firmware source touched.