docs: remove the quic-resilience plan document
- commit
- fa28b1cd525d48c296034f62f47e30c1c695718c
- author
- brice · · 2026-10-07 10:56 UTC
- parent
- 12ef17b
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email - refs
- feat/quic-resilience
1 file · +0 -74
D docs/plans/quic-resilience.md +0 -74
| @@ -1,74 +0,0 @@ | |||
| 1 | - | # Plan: mosh-like remote resilience | |
| 2 | - | ||
| 3 | - | Goal: on a lossy or intermittent link (4G, roaming, sleep), the UI never | |
| 4 | - | freezes, typed input is kept and delivered when the link returns, and SSH is | |
| 5 | - | used only to authenticate the first QUIC handshake. | |
| 6 | - | ||
| 7 | - | Reference: [mosh](https://github.com/mobile-shell/mosh) | |
| 8 | - | (`src/network/transportsender-impl.h`, `src/frontend/terminaloverlay.cc`). | |
| 9 | - | ||
| 10 | - | ## Diagnosis (before Phases 1 and 2) | |
| 11 | - | ||
| 12 | - | 1. Input path blocks on the network: `sessionAttachmentWorker.send` waits for | |
| 13 | - | `SendClient`, which waits for the carriage write. While QUIC stalls, the | |
| 14 | - | worker loop stops reading keys, servicing overlays, and painting. | |
| 15 | - | 2. Every reconnect re-runs SSH: `_broker-mux-quic-proxy` serves one | |
| 16 | - | connection with one-time credentials and exits with it. | |
| 17 | - | 3. No connection migration: one UDP socket per connection, no `AddPath`. | |
| 18 | - | 4. Slow detection, early give-up: keepalive 15 s / idle 60 s; 5 resume | |
| 19 | - | attempts over a few seconds, then back to the picker. | |
| 20 | - | 5. `LinkState` is only ever `Offline` at teardown; `degraded`/`probing` are | |
| 21 | - | documented but never produced. | |
| 22 | - | 6. Daemon ignores `Input.InputSeq`, so input in flight at a drop is lost or | |
| 23 | - | would be duplicated by a replay. | |
| 24 | - | ||
| 25 | - | ## Phase 1 — non-blocking client send (done) | |
| 26 | - | ||
| 27 | - | - Per-attachment ordered outbox drained by one sender goroutine; the worker | |
| 28 | - | loop enqueues and keeps running. Write failure settles the run as before. | |
| 29 | - | - Bounded by count and bytes. An input delivery stays undecided until the | |
| 30 | - | outbox wrote it, so the pump reads no newer keys meanwhile (backpressure) | |
| 31 | - | and a link failure preserves exactly the unwritten keys for the resume. | |
| 32 | - | - Lifecycle exits drain the outbox for at most 2 s. | |
| 33 | - | ||
| 34 | - | ## Phase 2 — patient resume (done, except status text) | |
| 35 | - | ||
| 36 | - | - QUIC keepalive 2 s; idle timeout stays 60 s. | |
| 37 | - | - A route that stays down keeps resuming for the 15-minute park window; a | |
| 38 | - | session that drops right after attaching still gives up after 5 flaps. | |
| 39 | - | - During the resume backoff, Ctrl-C or a lone Esc returns to the picker; | |
| 40 | - | other keys are kept (bounded) and replayed into the resumed session. | |
| 41 | - | - TODO: status bar "last contact Ns ago" while resuming. | |
| 42 | - | ||
| 43 | - | ## Phase 3 — exactly-once input across resume (protocol bump) | |
| 44 | - | ||
| 45 | - | - Daemon tracks last applied `InputSeq` per attachment; drops `<=` seqs. | |
| 46 | - | - `Welcome` on resume carries the last applied seq; client replays unacked | |
| 47 | - | input after it. Bump `protocol.Version`. | |
| 48 | - | ||
| 49 | - | ## Phase 4 — SSH only for first handshake (remote helpers, trust) | |
| 50 | - | ||
| 51 | - | - Remote QUIC proxy outlives one connection for the park window and issues a | |
| 52 | - | resume ticket (bearer secret, bound to cert fingerprint + session, expiry, | |
| 53 | - | single active use, memory only). | |
| 54 | - | - Reconnect dials QUIC directly with the ticket; SSH only on rejection/expiry. | |
| 55 | - | - Picker host health uses the live link + a ticket-based QUIC probe, never | |
| 56 | - | SSH. | |
| 57 | - | - Needs a security review (ticket theft, replay, expiry) before merge. | |
| 58 | - | ||
| 59 | - | ## Phase 5 — real link states and path migration | |
| 60 | - | ||
| 61 | - | - Derive `degraded`/`probing` from RTT and last-packet age in the adapter. | |
| 62 | - | - On stall or local address change, `AddPath` + probe + switch. | |
| 63 | - | ||
| 64 | - | ## Phase 6 — client-rendered palette on remote sessions | |
| 65 | - | ||
| 66 | - | - The command palette is rendered by the serving daemon, so each keystroke | |
| 67 | - | costs a full round trip on a remote session. Render it client-side like | |
| 68 | - | the session picker, fed by the daemon's command catalogue, and send only | |
| 69 | - | the chosen command. | |
| 70 | - | ||
| 71 | - | ## Phase 7 — optional predictive local echo | |
| 72 | - | ||
| 73 | - | - mosh-style overlay with underline-until-confirmed. Breaks the "thin client | |
| 74 | - | interprets nothing" rule; needs an explicit design decision. | |
| @@ -1,74 +0,0 @@ | |||||
| 1 | - | # Plan: mosh-like remote resilience | |||
| 2 | - | ||||
| 3 | - | Goal: on a lossy or intermittent link (4G, roaming, sleep), the UI never | |||
| 4 | - | freezes, typed input is kept and delivered when the link returns, and SSH is | |||
| 5 | - | used only to authenticate the first QUIC handshake. | |||
| 6 | - | ||||
| 7 | - | Reference: [mosh](https://github.com/mobile-shell/mosh) | |||
| 8 | - | (`src/network/transportsender-impl.h`, `src/frontend/terminaloverlay.cc`). | |||
| 9 | - | ||||
| 10 | - | ## Diagnosis (before Phases 1 and 2) | |||
| 11 | - | ||||
| 12 | - | 1. Input path blocks on the network: `sessionAttachmentWorker.send` waits for | |||
| 13 | - | `SendClient`, which waits for the carriage write. While QUIC stalls, the | |||
| 14 | - | worker loop stops reading keys, servicing overlays, and painting. | |||
| 15 | - | 2. Every reconnect re-runs SSH: `_broker-mux-quic-proxy` serves one | |||
| 16 | - | connection with one-time credentials and exits with it. | |||
| 17 | - | 3. No connection migration: one UDP socket per connection, no `AddPath`. | |||
| 18 | - | 4. Slow detection, early give-up: keepalive 15 s / idle 60 s; 5 resume | |||
| 19 | - | attempts over a few seconds, then back to the picker. | |||
| 20 | - | 5. `LinkState` is only ever `Offline` at teardown; `degraded`/`probing` are | |||
| 21 | - | documented but never produced. | |||
| 22 | - | 6. Daemon ignores `Input.InputSeq`, so input in flight at a drop is lost or | |||
| 23 | - | would be duplicated by a replay. | |||
| 24 | - | ||||
| 25 | - | ## Phase 1 — non-blocking client send (done) | |||
| 26 | - | ||||
| 27 | - | - Per-attachment ordered outbox drained by one sender goroutine; the worker | |||
| 28 | - | loop enqueues and keeps running. Write failure settles the run as before. | |||
| 29 | - | - Bounded by count and bytes. An input delivery stays undecided until the | |||
| 30 | - | outbox wrote it, so the pump reads no newer keys meanwhile (backpressure) | |||
| 31 | - | and a link failure preserves exactly the unwritten keys for the resume. | |||
| 32 | - | - Lifecycle exits drain the outbox for at most 2 s. | |||
| 33 | - | ||||
| 34 | - | ## Phase 2 — patient resume (done, except status text) | |||
| 35 | - | ||||
| 36 | - | - QUIC keepalive 2 s; idle timeout stays 60 s. | |||
| 37 | - | - A route that stays down keeps resuming for the 15-minute park window; a | |||
| 38 | - | session that drops right after attaching still gives up after 5 flaps. | |||
| 39 | - | - During the resume backoff, Ctrl-C or a lone Esc returns to the picker; | |||
| 40 | - | other keys are kept (bounded) and replayed into the resumed session. | |||
| 41 | - | - TODO: status bar "last contact Ns ago" while resuming. | |||
| 42 | - | ||||
| 43 | - | ## Phase 3 — exactly-once input across resume (protocol bump) | |||
| 44 | - | ||||
| 45 | - | - Daemon tracks last applied `InputSeq` per attachment; drops `<=` seqs. | |||
| 46 | - | - `Welcome` on resume carries the last applied seq; client replays unacked | |||
| 47 | - | input after it. Bump `protocol.Version`. | |||
| 48 | - | ||||
| 49 | - | ## Phase 4 — SSH only for first handshake (remote helpers, trust) | |||
| 50 | - | ||||
| 51 | - | - Remote QUIC proxy outlives one connection for the park window and issues a | |||
| 52 | - | resume ticket (bearer secret, bound to cert fingerprint + session, expiry, | |||
| 53 | - | single active use, memory only). | |||
| 54 | - | - Reconnect dials QUIC directly with the ticket; SSH only on rejection/expiry. | |||
| 55 | - | - Picker host health uses the live link + a ticket-based QUIC probe, never | |||
| 56 | - | SSH. | |||
| 57 | - | - Needs a security review (ticket theft, replay, expiry) before merge. | |||
| 58 | - | ||||
| 59 | - | ## Phase 5 — real link states and path migration | |||
| 60 | - | ||||
| 61 | - | - Derive `degraded`/`probing` from RTT and last-packet age in the adapter. | |||
| 62 | - | - On stall or local address change, `AddPath` + probe + switch. | |||
| 63 | - | ||||
| 64 | - | ## Phase 6 — client-rendered palette on remote sessions | |||
| 65 | - | ||||
| 66 | - | - The command palette is rendered by the serving daemon, so each keystroke | |||
| 67 | - | costs a full round trip on a remote session. Render it client-side like | |||
| 68 | - | the session picker, fed by the daemon's command catalogue, and send only | |||
| 69 | - | the chosen command. | |||
| 70 | - | ||||
| 71 | - | ## Phase 7 — optional predictive local echo | |||
| 72 | - | ||||
| 73 | - | - mosh-style overlay with underline-until-confirmed. Breaks the "thin client | |||
| 74 | - | interprets nothing" rule; needs an explicit design decision. | |||