# PR #23647 proof audit — pass 1 (head 31019fac, base f054d1b8)

## Changed code
- `apps/web/src/connection/platform.ts:239-257` — `bootstrapSshBearerSession` catch inside `provisionDesktopSshEnvironment`.
  `sshPreparationError` (`:170-182`) yields Blocked(authentication) when message contains "cancel", else
  Transient(remote-unavailable, "Could not prepare the SSH environment: <msg>"). New: Transient + `descriptor.serverVersion !== APP_VERSION`
  + msg contains `[ssh_http:500]` → `ConnectionBlockedError{reason:"configuration", detail:"<label> runs T3 Code <srv>, but this desktop is <APP_VERSION>, and that server could not accept its pairing. Restart the T3 server on <label> so it runs <APP_VERSION>, then retry."}`
- `apps/desktop/src/ipc/methods/sshEnvironment.ts:86-90` — comment only; message `[ssh_http:<status>] SSH remote API request failed during bootstrap-bearer-session.` (status from `readSshHttpStatus`, null → no prefix).

## Callers (both reach the changed branch)
- A. Add flow: Settings › Connections › Add environment › SSH → `connectSshEnvironment` → `onboarding.ts:254 gateway.provision` (one-shot, no retry).
- B. Saved SSH env reconnect: supervisor → `resolver.ts:233 ssh.prepare` → `platform.ts:271 prepareDesktopSshEnvironment` → re-pair (`:312`) only when no saved token, or `/api/auth/session` says unauthenticated / missing standard scopes.
- Plain-browser web: `platform.ts:383-388` → Blocked(unsupported) "SSH environments are only available in the desktop app." Mobile: same stub (`apps/mobile/src/connection/platform.ts:226-232`). ⇒ only meaningful surface = web UI inside Electron (desktop dev). Plain web/mobile captures are N/A.

## Render sites (Transient → supervisor `backoff`/`connecting` → phase "reconnecting"; Blocked → "blocked" → phase "error"; `presentation.ts:36-64`)
- S1 Add dialog alert `AddEnvironmentDialog.tsx:577-579` (`formatDesktopSshConnectionError`, `ConnectionsSettings.logic.ts:289`): before "Could not prepare the SSH environment: [ssh_http:500] SSH remote API request failed during bootstrap-bearer-session." (IPC prefix stripped only for ensure-ssh-environment, so may show "Error invoking remote method 'desktop:bootstrap-ssh-bearer-session': …"); after: new detail.
- S2 Settings › Connections row `SavedEnvironmentRows.tsx:89-99`: "Reconnecting: <detail>" (tone error) vs "Connection failed: <detail>"; diagnosis summary w/ RetryCountdown "retrying in Ns" only while backoff (`ConnectionDiagnosisDetails.tsx:66-75`).
- S3 Composer banner `useSystemComposerBanners.tsx:202-238`: title "<label> is reconnecting" (warning, no Reconnect button, after grace) vs "<label> is offline" (error, Reconnect button); description headline `diagnosis.ts:133-136` = "T3 Code on <host> did not answer" in BOTH (detail text not in banner).
- S4 Status dot `ConnectionStatusDot.tsx:6-22`: bg-warning + ping (reconnecting) vs bg-destructive (error); tooltip `presentation.ts:67-94` "Failed to connect. Reconnecting... Reason: …" vs "Connection failed. Reason: …".

## Fixture rows (surface: Electron desktop dev only; widths 1440 + 390 (window resize), light + dark)
| Row | Seed (fake remote) | Expected after | Affected | Captures |
|---|---|---|---|---|
| R1a version-mismatch 500, Add flow | descriptor serverVersion=0.0.46-nightly.20261004.2652, POST /oauth/token→500 | S1 shows blocked explanation w/ both versions (before: generic transient text) | yes | S1 before/after ×1440/390 ×light/dark |
| R1b version-mismatch 500, saved env | pair OK against real head server first, then swap to fake (session→401) | S2 "Connection failed: <label> runs T3 Code …"; S3 "is offline" + Reconnect; S4 red; no retry loop (before: "Reconnecting: …", countdown) | yes | S2,S3,S4 before/after; `recording` ~60s showing before loops / after stays put |
| R2 matching version + 500 | serverVersion=APP_VERSION (0.1.1) | unchanged: transient, retries | no (control) | S2 or S1 after only, 1440 light |
| R3a differing version + 502 | /oauth/token→502 | transient "[ssh_http:502]…", retries | no (control) | S2 after, 1440 light |
| R3b differing version, no status | /oauth/token: destroy socket | transient, no prefix, retries | no (control) | S2 after, 1440 light |
| R4 already-blocked passes through | message containing "cancel" (e.g. 500 body irrelevant; hard to trigger via HTTP — unit-test only) | Blocked(authentication) unchanged | no | none (covered by code read; optional unit test) |
| R5 recovery | from R1b, kill fake, start real head server (0.1.1) as t3probe, then focus window or click Reconnect | blocked waits for wake signal (`supervisor.ts:941-957`) then connects; S3 banner clears, S2 "Connected" | yes (new path out) | `recording` 1440 dark |
| R6 different machine / no pairing token | n/a | untouched branches `platform.ts:223-237` | no | none |

## Seeding (single Linux VM, never touches ~/.t3 of ubuntu)
- Remote scripts write `$HOME/.t3` of the REMOTE user (`tunnel.ts:540-541,737-747`: pairing via `run-t3.sh auth pairing create --base-dir $HOME/.t3`, adoption of external server from `$HOME/.t3/userdata/server-runtime.json`). SSH to localhost as `ubuntu` would hit the live install ⇒ throwaway user is mandatory.
- Desktop only needs `ssh` binary + `-p port` + `-L local:127.0.0.1:remote` forward (`command.ts:106-117`, `tunnel.ts:1168`); BatchMode=yes, no -i/-F. OpenSSH resolves `~/.ssh` from passwd, not $HOME ⇒ a throwaway HOME for Electron does NOT redirect ssh config. Use the Add dialog's manual host/user/port fields instead of an alias; auth via a temp agent (`SSH_AUTH_SOCK`), no key in ~/.ssh.
- Steps (needs maintainer OK: creates a system user + host key trust, outside repo):
  1. `sudo useradd -m t3probe`; `ssh-keygen -t ed25519 -N '' -f /tmp/ev-23647/k`; install `/tmp/ev-23647/k.pub` into `/home/t3probe/.ssh/authorized_keys` (0600, owned t3probe).
  2. Host key trust without editing ~/.ssh: `ssh-keyscan -p 22 127.0.0.1 | sudo tee /etc/ssh/ssh_known_hosts` (system file; remove after).
  3. `eval $(ssh-agent -a /tmp/ev-23647/agent.sock); ssh-add /tmp/ev-23647/k`; check `ssh -o BatchMode=yes -p 22 t3probe@127.0.0.1 true`.
  4. Remote runner: desktop dev without `T3CODE_DEV_REMOTE_T3_SERVER_ENTRY_PATH` uses archive mode = downloads release 0.1.1 into /home/t3probe/.t3/runtime (needs internet). Alternative: set `T3CODE_DEV_REMOTE_T3_SERVER_ENTRY_PATH=<head>/apps/server/src/bin.ts` (needs node on t3probe's non-interactive PATH + repo readable by t3probe).
  5. Fake remote (as t3probe, ~40-line node http): on start write `/home/t3probe/.t3/userdata/server-runtime.json` `{"pid":<self>,"port":P,"origin":"http://127.0.0.1:P"}`; `GET /`→200; `GET /.well-known/t3/environment`→descriptor (environmentId, label "t3probe-box", platform, serverVersion=$FAKE_VERSION, capabilities — copy a real one from step-6 server); `POST /oauth/token`→$FAKE_STATUS (500/502/drop); `GET /api/auth/session`→401. Launcher adopts it as "external" (`tunnel.ts:650-686`), pairing create still runs the real runner against t3probe's own ~/.t3.
  6. Real server for R1b pre-pair / R5: `sudo -u t3probe -H node <head>/apps/server/src/bin.ts serve --host 127.0.0.1 --port 3901 --base-dir /home/t3probe/.t3` (writes runtime file itself). Track PIDs; kill only those.
  7. Client: `vp run dev:desktop` in the head worktree (and merge-base worktree for "before") under `xvfb-run`, with `SSH_AUTH_SOCK=/tmp/ev-23647/agent.sock`, worktree `.t3` state (default). APP_VERSION = 0.1.1 from package.json (`vite.config.ts:45`); do not override.
- Faithfulness ranking: (1) real older t3 build as the external server — only faithful if a release whose grant decode actually 500s is identified (not found in this pass); (2) fake remote above — exact descriptor + status contract the code branches on, recommended; (3) second t3 server from merge-base — same version 0.1.1 ⇒ cannot produce the mismatch, useless for R1.
- Cleanup: kill tracked PIDs, `ssh-agent -k`, `sudo rm /etc/ssh/ssh_known_hosts`, `sudo userdel -r t3probe`.
