chore(agent): phase roadmap from TODO.md, 5 phases
The four open items in TODO.md become an executable pipeline: 01 the verification gate (host suites + sanitiser + both firmware envs, and the hardware contract so no unit blocks on a board nobody is holding), 02 HTTPS and wss:// with an identity the board generates itself into NVS, 03 session takeover that always lands disarmed, 04 a responsive page whose safety statements are pinned before they are trimmed, 05 the phone as a controller (press-and-hold pad, soft keyboard, modifier latches over a new Q= frame). TODO.md is cleared to its empty title; the items live in .agents/phases/ now. AGENTS.md gains the five protocol rules and nothing else. LLM-Generated: true
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
# Task 02 — `X`: hand the bridge over through the funnel
|
||||
|
||||
**Phase:** `03_session_takeover` · **Source:** `TODO.md:6` — "Need to be able to take control
|
||||
if another session is connected…"
|
||||
**Story:** `n/a`
|
||||
|
||||
## Objective
|
||||
|
||||
One authenticated frame from the pending claim — `X` — takes the bridge: the incumbent is
|
||||
told (`E12`), the release funnel runs, the board ends up **disarmed**, and only then is the
|
||||
claimant adopted as the controller. The handover is a link change that the rest of the
|
||||
firmware recognises, not a silent substitution of one socket for another.
|
||||
|
||||
## Work
|
||||
|
||||
1. `src/proto.h` — `Cmd::Takeover` for a lone `X`, documented in the header comment's
|
||||
direction table: `c -> d X claim this bridge for myself; requires authentication, and
|
||||
nothing else is honoured on a claim socket`. `proto::formatError` unchanged; add
|
||||
`kErrTakenOver = 12` to `control_server.h` ("your session was taken over by another
|
||||
controller") and name it in the error list comment.
|
||||
2. `src/control_server.cpp` — the handover, in this order and no other:
|
||||
1. `sendError(12, "session taken over")` on the **incumbent** socket, so its page can say
|
||||
why it stopped. It is best-effort: an unreadable incumbent must not block the handover.
|
||||
2. `closeLink("taken over")` on the incumbent — the existing funnel. `closeLink()` already
|
||||
disarms and releases for anything that reached the WebSocket phase (that is bug 10's
|
||||
fix; do not re-narrow it here) and calls `_onLinkChange(false)`, which in `main.cpp`
|
||||
runs `releaseEverything()`. **This must happen before adoption**, so the newcomer can
|
||||
never inherit a held key or an armed bridge.
|
||||
3. Promote the claim: move its conn into the controller slot, `_phase = WebSocket`,
|
||||
`_authed = true`, `_controllerSeen = true`, `_lastInboundMs = millis()`,
|
||||
`_connectedAtMs = nowMs`, reset the controller's reader, then `OK` + `sendStatus()` and
|
||||
`_onLinkChange(true)`.
|
||||
4. Clear the claim slot completely (`_claimAuthed = false`, reader reset, buffers zeroed)
|
||||
so a stale claim cannot be promoted twice.
|
||||
- `arm()` is not called, and nothing on this path can reach it. State that in a comment at
|
||||
the top of the handover function and make the function's name say it too
|
||||
(`handOverClaim()` or similar — not `takeover()` which reads like an arm).
|
||||
- If the incumbent is already gone by the time `X` arrives, the promotion still runs
|
||||
through the same steps (the funnel is a no-op when disarmed), so there is only one path.
|
||||
- `_rejected` keeps meaning "a socket was turned away", and a successful takeover gets its
|
||||
own counter (`_takeovers`), reported in the status frame (task 03) — counters only, never
|
||||
addresses or identities (invariant 4).
|
||||
3. `src/main.cpp` — `onMessage()` handles `Cmd::Takeover` for the case where an *existing*
|
||||
controller sends `X` (a no-op with `OK`, documented: you already hold it) — do not let it
|
||||
fall through to `E6` — and `fillStatus()` publishes the counter.
|
||||
4. `src/config.h` — nothing new; `kClaimTimeoutMs` from task 01 bounds the claim's life.
|
||||
|
||||
- ASSUMPTION: `X` is honoured **only** on a claim socket (or is a no-op from the controller).
|
||||
It is not a general command, so a controller cannot use it to reset its own session.
|
||||
- ASSUMPTION: the incumbent gets no warning window and no veto. Its operator is by
|
||||
definition absent — that is the situation the TODO describes — and a veto held by an
|
||||
absent tab is the lockout we are removing. The protection is that the claimant needed the
|
||||
passphrase, and that the board lands disarmed.
|
||||
- ASSUMPTION: `E12` is a new code rather than reusing `E3`. `E3` means "you were refused";
|
||||
`E12` means "you were dropped". One of those should be alarming on the incumbent's screen
|
||||
and the other should not.
|
||||
|
||||
## Testing & Quality
|
||||
|
||||
- `tests/test_proto.cpp`: `X` parses to `Cmd::Takeover`; `X` with trailing bytes is `Cmd::Bad`;
|
||||
`E12` formats; the render/parse round trip covers it; the existing 2 327 checks around chord
|
||||
labels and state specs stay exactly as they are.
|
||||
- `tests/test_relayctl.py` — the three source pins named in the phase overview: funnel before
|
||||
adoption, no `arm()` reachable from the `X` path, no promotion without `_authed`. Written
|
||||
the way the existing pins are written (a named assertion per sentence, with the sentence in
|
||||
the output) so the suite says out loud what it checked.
|
||||
- Device (best effort, one attempt): arm on tab A, hold a key, take over from tab B —
|
||||
`watch_target.py` shows the key-up and the board reports `armed:0`; tab A shows `E12`.
|
||||
Record the transcript in `.agents/HW_VERIFICATION.md` or `skipped (no device)`.
|
||||
|
||||
## Completion Criteria
|
||||
|
||||
- [ ] `X` from an authenticated claim drops the incumbent with `E12` and adopts the claim.
|
||||
- [ ] The board is disarmed immediately after every takeover, observed or recorded as
|
||||
unobserved-with-commands — never asserted without a mechanism.
|
||||
- [ ] The three source pins exist and each one fails when its sentence is broken (shown, then
|
||||
reverted).
|
||||
- [ ] `bash .agents/validate.sh` green; `_takeovers` counter visible; full test suite green.
|
||||
Reference in New Issue
Block a user