# Proof audit, pass 1 — SSH managed-server version guard

PR [#29428](https://git.cafe/pingdotgg/t3code/pull/29428), fixing [#29416](https://git.cafe/pingdotgg/t3code/issues/29416). Audited head [b39fd40](https://git.cafe/pingdotgg/t3code/commit/b39fd402c62559744f892a2e164212a79e0a6ede), branch `fix/issue-29416`, against merge-base [edc37c9](https://git.cafe/pingdotgg/t3code/commit/edc37c96c810d42761ab510c6a3e9d0a741547dc). Checkout: `/home/ubuntu/wt/pr-29428`.

## Findings first

1. **Evidence gate remains open, as expected in pass 1.** There are no uploaded before/after runtime artifacts to audit. The current PR body has a Verification section with an inline after transcript, but no Evidence section linking uploaded artifacts. That excerpt is not a reviewable before/after capture. All A rows below are missing. This is a proof finding, not a confirmed production defect.
2. **Existing tests do not replace built-CLI proof.** The new launch tests run the real generated shell against a fake server/helper. The fake `keep-server` compares versions with `localeCompare`, while production uses `compareExactServiceVersions`; the helper unit test calls the pure function directly. Neither exercises production command registration, filesystem reading, exit status, and shell decision together. Capture with the real built CLI, not the fake helper from the test. The PR's described release-binary/helper shim must be labelled a harness; do not call it a full build of the reviewed head.
3. **A PID-mismatch fixture can accidentally exercise adoption instead of rejection.** A readable runtime file naming a different live PID with a valid loopback origin/port is examined before `keep-server`. A healthy foreign server is adopted as external; an old managed PID may be stopped there. Use a demonstrably dead, tracked fixture PID for A06, and use C03 for live foreign adoption. Never substitute an arbitrary PID that might belong to another process.

No production correctness blocker was established by this fixture derivation. Tests and captures were not run in this audit. CI state was read, but green checks are not capture evidence.

## Scope and call paths

There is no changed rendered component, layout, prop, viewport, theme, list count, loading spinner, or visual interaction. Adapt “render paths” to runtime decision paths. The platform axis for required captures is one Linux VM with POSIX `sh`; terminal width/theme are immaterial.

| Changed code / entry | Every relevant caller and path | Inputs and visible result |
| --- | --- | --- |
| [Generated launch script and builder](https://git.cafe/pingdotgg/t3code/blob/b39fd402c62559744f892a2e164212a79e0a6ede/packages/ssh/src/tunnel.ts#L526) | `buildRemoteLaunchScript` → `launchOrReuseRemoteServer` → `runSshCommand` (`sh -l -s -- <stateKey>`). Production caller: manager `createTunnelEntry` → `ensureTunnelEntry` → `ensureEnvironment`; direct callers also exist in tests. The export added in this PR permits the generated-shell test, not a new product entry point. | Runner selection, bytewise runner change, archive mode, remembered PID/port/managed flag, default-home runtime record, liveness/zombie state, HTTP readiness; returns launch JSON or an SSH launch failure. |
| [Desktop adapter](https://git.cafe/pingdotgg/t3code/blob/b39fd402c62559744f892a2e164212a79e0a6ede/apps/desktop/src/ssh/DesktopSshEnvironment.ts#L129) and [runner selection](https://git.cafe/pingdotgg/t3code/blob/b39fd402c62559744f892a2e164212a79e0a6ede/apps/desktop/src/main.ts#L108) | `DesktopSshEnvironment.ensureEnvironment` → SSH manager; desktop IPC → preload bridge → web connection platform. Initial provision uses `issuePairingToken: true`; saved bearer reconnect uses `false`; re-pairing uses the provision path. A healthy cached tunnel bypasses remote launch. A missing/stale tunnel enters it. | Packaged desktop selects `archiveVersion: appVersion`; configured development remote entry selects `nodeScriptPath` and Node engine range. A nonempty node path wins over archive mode. Pairing happens after launch and is unchanged. |
| [New helper](https://git.cafe/pingdotgg/t3code/blob/b39fd402c62559744f892a2e164212a79e0a6ede/apps/server/src/cli/sshHelper.ts#L132) | `binCli.makeCli` registers hidden `__ssh-helper`; new `keep-server <runtime-file> <pid>` calls `keepsRunningServer`. The launch shell invokes it only in the changed-runner/live-managed path and only for archive mode. Unit tests also call the exported predicate. | JSON must decode to numeric `pid` and string `serverVersion`; PID must equal the argument; server version must be exact SemVer and compare >= the CLI's embedded `packageJson.version`. Success: exit 0, no stdout; rejection: exit 1, no stdout; read exceptions: nonzero failure. Shell suppresses helper output and treats any nonzero result as permission to replace. |
| [Runtime producer](https://git.cafe/pingdotgg/t3code/blob/b39fd402c62559744f892a2e164212a79e0a6ede/apps/server/src/serverRuntimeState.ts#L77) | Real `t3 serve` publishes `~/.t3/userdata/server-runtime.json` under the sandbox HOME. Existing `runtime-port` also reads it before the keep decision. | Producer uses actual process PID and embedded server version. Helper ignores extra fields. `runtime-port` additionally needs an integer PID > 0, integer port, loopback HTTP origin, and live PID; it can correct a remembered port or trigger external adoption. |

Web/local web, mobile, provider adapters, MCP and scheduled task paths do not call this desktop SSH launch manager independently. Connecting those clients to the resulting environment does not add another guard implementation. No wire schema changed. Explicit disconnect/stop and pairing scripts are unchanged; preserving a server on reconnect does not prove it survives explicit disconnect or app shutdown.

## Common driver and evidence contract

This is a **labelled Linux local-SSH harness**, not a real SSH/network or rendered-client capture. It drives the real `launchOrReuseRemoteServer` through a PATH `ssh` stub which executes the supplied remote command/script locally. Preserve the transmitted script and stub argv in the evidence. The stub must preserve the sandbox HOME even for `sh -l`; login startup files can otherwise reset it. Log and assert the effective HOME before running the script. Do not connect to a real host.

Use separate fresh sandbox HOME directories per scenario and per before/after run. The driver's own module/dependency setup, output and logs must live beneath `/tmp/lead-29428-r1/proof-audit-1/`, with a new timestamped run directory each time. Read the supplied worktree; do not edit it or the live T3 home. Do not clone live user data. Fresh databases suffice for this guard; real data, credentials and agent runs are unnecessary.

Use `C = 0.1.6` (the reviewed package version), `L = 0.1.5`, and a real `H > C`, e.g. the PR's `0.1.7-nightly.20261102.4080` release if available. The connecting C CLI must be built from the audited source. Keep real older/newer release servers for A01 and A03. Record the source SHA, embedded `t3 --version`, artifact SHA-256, and whether a runtime is a complete build, an unmodified release, or a shim. A `.install-complete` marker plus an executable at `$HOME/.t3/runtime/versions/<version>/t3` preinstalls an archive-mode runtime without network/download variability. If using a wrapper around a real built `bin.mjs`, label that packaging harness explicitly. Never replace the production helper with the fake test helper or a hand-written comparator.

Use a target such as `{ alias: "proof-local", hostname: "proof-local", username: null, port: null }`, compute its real `remoteStateKey`, and use the same target for all reconnects in a scenario. State is under `$HOME/.t3/ssh-launch/<key>/{pid,port,managed,run-t3.sh,server.log}`. Runtime metadata is under `$HOME/.t3/userdata/server-runtime.json`. Seed the remembered port with a free ephemeral loopback port to avoid this VM's real T3 listeners. A direct built-CLI helper driver can use arbitrary fixture files in its run directory and needs no server.

For every launch capture, log in order: fixture ID, baseline/head SHA, runner options, actual CLI versions, pre-connect PID/port/managed/runtime JSON, previous and proposed runner hashes (proving changed or unchanged), the real launch result/stdout/stderr, post-connect ownership/runtime state, and an explicit liveness check of the tracked old PID. Distinguish zombies from live processes. A successful JSON result alone does not prove preservation. Preserve the new server PID and version for replacement paths. For a retained server also compare `startedAt` where available. Include cleanup results.

**Capture format:** every requested capture below is a `recording`: an ordered terminal text log is sufficient under this server-only brief. No still image is sufficient for reconnect, replacement, or readiness. No viewport × theme cross-product is required: minimum is one Linux runtime × no visual theme × each required row. Several rows can share one transcript if each is clearly delimited and has its own assertions.

## Fixture matrix

“Affected” includes the new helper's false/fallback decisions even when the expected outcome matches main. “Control” means adjacent behavior is unchanged and does not require a separate capture for this small fix. All status values are pass-1 inventory, not executed results.

| ID | Named runtime fixture / required data | Affected? | Expected result and how to seed/drive on this VM | Minimum capture / status |
| --- | --- | --- | --- | --- |
| A01 | **Older fixed client, newer healthy managed server**: archive C connects to H; runner changes; managed file, PID file and runtime PID agree; runtime version H; HTTP ready. | Yes | Launch real H first in sandbox via the real launch function, then connect C built from this head. Main kills H and attempts C; head must preserve the H PID, runtime version and startedAt, returning `managed` and the same actual port. Use independent homes for before/after so baseline DB effects do not contaminate head. | **Required recording, before + after. MISSING.** Real launch + real release server + real built C CLI; local-SSH harness labelled. |
| A02 | **Equal version, changed runner**: managed C with matching runtime PID and version C; remembered runner differs bytewise. Include helper variant `serverVersion: "0.1.6+proof"` (build metadata is ignored). | Yes | Launch C; append a harmless newline to the sandbox's remembered `run-t3.sh` before reconnecting with the normal C runner. Confirm `RUNNER_CHANGED=1` by hashes. Main replaces; head keeps the PID. Batch the build-metadata variant through the real helper against matching numeric PID. | **Required recording, before + after launch; helper variant after. MISSING.** Changed sandbox state is a labelled harness fixture. |
| A03 | **Newer fixed client updates older managed server**: archive C connects to real L; matching PID, runtime version L; runner changed; healthy. | Yes, new false decision; outcome retained | Launch real L, then C. Both main and head must stop the tracked L PID, start a distinct C PID, publish C runtime metadata, and return `managed`. This is the required counterexample to “never replace”. | **Required recording, before + after. MISSING.** Real launch + real releases/built C. |
| A04 | **Same-core nightly server below stable client**: server metadata `0.1.6-nightly.20261102.4080`, matching numeric PID; real built stable C helper. | Yes | Run built `t3 __ssh-helper keep-server <fixture> <pid>`; expect exit 1. This is the PR's explicitly documented stable-over-nightly boundary. A helper JSON fixture is enough to distinguish it; a release-labelled end-to-end claim requires a genuine matching nightly runtime. | **Required after helper recording. MISSING.** Label synthetic runtime metadata as helper harness; baseline command is absent. |
| A05 | **Stable server above same-core nightly client**: server metadata `0.1.6`, client embeds `0.1.6-nightly.20261102.4080`; matching numeric PID. | Yes | Use a real built CLI from the same source with only package version changed in an isolated scratch build, or a genuine fixed nightly artifact. Expect exit 0. Record the version-only build delta. Merely setting `archiveVersion` cannot create this fixture: helper compares its embedded package version. | **Required after helper recording. MISSING.** Versioned-build + metadata harness; cannot naturally drive this with the unmodified stable-C build alone. |
| A06 | **PID mismatch, no adoptable foreign runtime**: PID file names live tracked managed server P; runtime names different demonstrably dead tracked fixture PID Q, valid same/newer version. | Yes | Start a real managed server, overwrite only sandbox runtime metadata using Q from a fixture child already spawned and reaped; preserve remembered P/port/managed and force runner change. `runtime-port` fails Q liveness, so no adoption occurs; helper rejects Q != P; old P is replaced. Also record direct helper exit 1. Do not use a live unrelated PID. | **Required before + after launcher recording and after helper status. MISSING.** Label metadata mutation as harness. |
| A07 | **Missing runtime file**: live managed P, remembered port, changed archive runner; file does not exist. | Yes | Keep P running and move the sandbox runtime file to a preserved sibling before reconnect. The helper's `existsSync` false branch rejects; launcher replaces P. Direct helper against a never-created fixture path must exit 1. New server should publish a fresh runtime file. | **Required before + after launch; after helper status. MISSING.** Harness metadata fixture. |
| A08 | **Undecodable runtime JSON/shape**: empty string; malformed `{`; `null`; array; missing PID; string PID; missing serverVersion; non-string serverVersion. | Yes | Batch all listed inputs through the real built helper: each exits 1. For one representative full-launch variant (malformed JSON), leave live P/port/managed intact, mutate only its sandbox metadata and force runner change; P must be replaced. All inputs collapse to the same decode-failure decision; no separate full server run per shape is needed. | **Required after helper batch for all variants + one before/after launcher recording. MISSING.** Label harness. |
| A09 | **Decoded but non-exact version**: numeric matching P, `serverVersion: "latest"` (optionally empty, `v0.1.6`, or leading-zero prerelease). | Yes | Direct helper exits 1. For launcher capture mutate only serverVersion in the sandbox's otherwise valid matching runtime; existing `runtime-port` still recognizes the managed PID, and the new helper rejects exact-version validation. Changed runner replaces P. | **Required before + after launcher and after helper recording; `latest` is the minimum representative. MISSING.** Label metadata harness. |
| A10 | **Runtime read fails after existence check**: an existing directory used as runtime-file. | Yes | Direct real built helper on a directory beneath the run directory reliably gets a read error on Linux, exits nonzero, and emits a diagnostic. Unlike an ordinary rejection it may not be exactly exit 1; the shell treats any nonzero as replace. Do not turn the real running server's runtime path into a directory: that also breaks replacement startup and is a different failure. | **Required after helper recording. MISSING.** Filesystem-error harness; baseline command absent. No separate server launch needed. |
| A11 | **Keep decision succeeds, existing managed server never ready**: changed archive runner, same/newer valid runtime, live P, nonresponsive HTTP. | Yes, newly retained server enters existing readiness path | First make a healthy A01/A02 fixture. SIGSTOP only its tracked server P, then reconnect with a changed runner. Let the real 60 s readiness deadline expire. Expect error containing “server was left running”, no replacement, and same owned PID/runtime. In `finally`, SIGCONT P before graceful termination and await its exit. Log the timeout as a lifecycle recording; do not shorten production constants to claim a real run. | **Required after launcher recording. MISSING.** Controlled process-state harness; full baseline timeout run is not necessary because its replace behavior is already proven in A01/A02. |
| C01 | **Dev node-script runner changed**: `nodeScriptPath` configured; managed live server; same/newer runtime. | No, compatibility control in touched guard | Use a scratch Node entry/wrapper to the real built CLI; change only the path between two wrappers to force changed runner. Archive predicate short-circuits; managed server is still replaced. `nodeScriptPath` wins when both runner options are supplied. Missing/incompatible Node fails earlier. | Optional before/after recording. **NOT REQUIRED / NOT CAPTURED.** Label dev harness. |
| C02 | **Runner unchanged, managed live**: same runner bytes, with runtime older/equal/newer or missing; remembered port valid. | No, short-circuit control | Reconnect twice with identical options and no runner mutation. Helper is skipped regardless of runtime version. Healthy server is reused; if unready, the existing error leaves it running. Can append a healthy unchanged reconnect to A02 at negligible cost. | Optional recording. **NOT REQUIRED / NOT CAPTURED.** |
| C03 | **Foreign live default-home server / external adoption**: runtime names another live ready server Q with valid loopback origin/port; managed state may still name P. | No, preceding branch | Start real Q under sandbox home and record its actual runtime. With no managed state, launch adopts Q as `external`; with stale managed P, adoption stops P first. A currently external and ready server is reused regardless of runner change/version. This does not exercise `keep-server` PID mismatch. Cannot run two real servers simultaneously against the same locked home; if testing P→Q cleanup, use a labelled tracked fixture process as stale P. | Optional recording. **NOT REQUIRED / NOT CAPTURED.** |
| C04 | **Cold or dead managed state**: PID/port absent, tracked PID has exited (including zombie), no valid external runtime. | No | Fresh sandbox launch starts connecting client's release and publishes managed files. Dead/missing state is cleared logically first. Cold older clients on newer-migrated DBs remain outside this fix; this guard cannot protect a server that is not running. | Optional recording. **NOT REQUIRED / NOT CAPTURED.** |
| C05 | **Managed runtime reports a different actual port; failed adoption probe** | No | Valid runtime PID matching managed P corrects remembered port before the version guard (can be piggybacked on A01). A different runtime PID whose loopback HTTP is unready fails adoption, then restores remembered state before the guard; combine with A06 only if intentionally proving that separate branch. | Optional recording. **NOT REQUIRED / NOT CAPTURED.** |
| C06 | **External remembered server gone/unready**: external flag; no newly adopted live PID; remembered port missing or fails readiness. | No | Seed `managed=external` with unused remembered port and no valid runtime. Launch clears external state and starts managed server. An adopted live Q bypasses a second readiness probe. | Optional recording. **NOT REQUIRED / NOT CAPTURED.** |
| C07 | **Prerequisite, port, or startup errors**: missing/invalid runner options, archive install/verification failure, no free port, or newly spawned server never ready. | No | Build-time runner validation or archive preflight fails before guard. No available port emits its existing mode-specific error. New-server readiness failure emits diagnostic/log tail, stops its captured PID and removes ownership files. These paths are unchanged; do not download arbitrary archives or exhaust the VM's ports for this proof. | No additional capture. **NOT REQUIRED.** Existing error branches inventoried. |
| C08 | **Desktop caller variations and cached tunnel**: initial provision vs saved bearer reconnect; healthy vs missing/stale local tunnel; optional pairing after launch. | No separate guard | Both bridge entry paths reach the same manager. Healthy local tunnel reuses cache without running remote launch; stale/missing tunnel invokes it. The harness deliberately calls the launch function directly to avoid that cache. Pairing and explicit disconnect are unchanged and must not be claimed tested by launcher recordings. | No rendered-client capture. **NOT REQUIRED.** Harness scope disclosed. |
| C09 | **Already-shipped connecting CLI, other hosts/surfaces** | No, release/platform boundary | A connecting binary without this PR still replaces on runner change; an old binary used with the new shell may lack `keep-server` and therefore fall back to replace. Do not label it “fixed” by changing archiveVersion. Linux VM proves POSIX/Linux paths only. macOS remote host, real SSH, Windows desktop→Linux SSH, live agent work, web/mobile consumers and tunnel shutdown are not exercised. | No additional capture for this narrow proof. **NOT REQUIRED; explicit coverage limit.** |

## Minimum artifact bundle for the capturer

1. **Before/after launch transcript(s):** A01, A02, A03, A06, A07, A08 (malformed representative), A09. These can be one pair of logs with individually isolated row runs. Use actual base/head launch implementations. Do not hand-delete the new guard to manufacture “before”. If an artifact is a binary/helper shim, disclose exactly what is replaced.
2. **Built-helper after transcript:** A02 build-metadata variant; A04; A05; A06; A07; every A08 variant; A09; A10. Log exact argv, fixture bytes, real `--version`, stdout/stderr and exit code. `keep-server` is new, so the baseline helper command is absent; a full repeated “before” helper matrix has no value. A05 needs the explicit nightly versioned build, not just a runner-option change.
3. **Lifecycle after transcript:** A11, including original PID, real readiness error, post-error PID state and successful SIGCONT/cleanup. A 60-second ordered log is sufficient; no video or terminal screenshot is required.
4. **Provenance/cleanup summary:** artifact hashes/source SHAs, baseline/head generated launch scripts, local-SSH stub source, driver source and exact invocation, per-row sandbox paths, all spawned PIDs and their observed exit. Check free space before and after; stop if under 5 GB. Do not include credentials, startup pairing links, tokens or real user state. Logs from real `serve` may print pairing secrets: redact them before upload and retain unredacted raw logs only inside the private scratch run directory.

Upload final sanitized artifacts with `ghdrop upload -m <file>` and add an Evidence section linking them to the PR when the lead's scope authorizes that edit. This audit does not edit or comment on the PR. Every affected row remains missing until an uploaded artifact names and proves it. Optional control recordings do not substitute for a missing affected row.

## Coverage and friction

Covered: all four changed files, helper CLI registration, runtime producer, existing version comparator, full generated launch decision tree around the guard, direct and desktop caller paths, PR description, linked issue, existing reviews/comments, and reported CI state. Read-only checkout; no builds, tests, servers, browsers or subprocess fixtures started. No processes require cleanup. Available root filesystem space observed: 18 GB.

FRICTION: none

VERDICT: INSUFFICIENT A01, A02, A03, A04, A05, A06, A07, A08, A09, A10, A11 — all affected runtime recordings are missing
