# Proof audit, pass 1 — server update download diagnostics

PR [#33239](https://git.cafe/pingdotgg/t3code/pull/33239) ([GitHub mirror](https://github.com/pingdotgg/t3code/pull/33239)). Read-only inspection in `/home/user/wt/pr-33239`, at the requested local merge of published [d49e8ee0f1](https://github.com/pingdotgg/t3code/commit/d49e8ee0f13114789f7c7a43480f1d0b51626259) and main [4c361199f4](https://github.com/pingdotgg/t3code/commit/4c361199f47f6ea32a2c14835df392640d0f7c63). Original merge-base [c6ca605630](https://github.com/pingdotgg/t3code/commit/c6ca6056309904f89b90b6055d797973be0a0939). The three audited files are identical between the published PR head and the local merged head. Baseline for new evidence: `origin/main`, not the original merge-base.

## Findings

1. **Proof incomplete, not a confirmed implementation bug.** The PR description contains one inline, explicitly labelled service-harness transcript: checksums HTTP 504 from github.com, no staging entries, launcher not invoked, then successful retry. It has no linked captures or raw logs. Its verification dates from the original merge-base. The description states the old generic message, but does not contain a paired baseline runtime run. Existing assertions cover checksums 504/403 and archive 503 with a synthetic CDN request containing dummy credentials, path, query, and private body; the exact expected reason excludes those details. This audit read the tests; it did not rerun them or independently reproduce the transcript.
2. **The reason has visible surfaces.** The server-only/no-visible-surface exemption is not the complete proof rule here. The existing contract makes `message` equal to `Server update failed: ${reason}`. Shared client state retains that message; web, desktop and mobile render it. A single targeted client recording is warranted to show the added diagnostic suffix can actually be read. No new client tests, full theme sweep, or separate desktop/mobile capture is warranted for this server-only diff.
3. **The diff changes more than HTTP failures.** Every ordinary pinned-runtime install error now includes `while ${step}`, even when no HTTP metadata exists. Downloads can produce status+host, host-only transport failure, or host-only timeout (with an additional timed-out marker). Preserve these distinct textual shapes in the transcript matrix. Preflight-blocked and policy-mirror-missing errors take earlier branches and keep their existing reason.
4. **Do not confuse a desktop client with a desktop-managed target.** A desktop client connected to a service-managed remote target shows the changed reason. A desktop-managed server takes `DesktopAppUpdate.run` and never enters the changed pinned-runtime mapping. A normal `vp run dev` target is unmanaged; pointing its release URL at a failing HTTP server alone does not reach this bug.

## Fixture matrix

Status legend: **PARTIAL** = existing description evidence covers only part; **MISSING** = required proof absent; **TEST-SOURCE** = relevant assertions inspected, PR reports a run, not independently rerun here; **TRACED** = code inspected, no capture claimed. “No extra capture” means intentionally outside the minimum proof set, not visually verified. All interaction/time-dependent rows are marked **recording**; server rows use a sequential runtime transcript rather than still images.

| ID / named fixture | Input and expected result | Diff affected? | Proof type / minimum capture | How to seed | Current status |
| --- | --- | --- | --- | --- | --- |
| S1 Checksums HTTP 504 | Fresh version 1.1.0; checksums response 504. Before: `Could not prepare t3@1.1.0.` After: `Could not prepare t3@1.1.0 while downloading the t3 release checksums (HTTP 504 from github.com).` Client adds the standard `Server update failed:` prefix. | Yes: step + status + host | **Required before/after runtime transcript**. No viewport/theme. | H1 below, fail `/v1.1.0/SHA256SUMS`; capture reason and message on both revisions. | **PARTIAL**: after-only inline harness output; no paired baseline/current-merge run. |
| S2 Archive HTTP 503 / checksums HTTP 403 | For archive: valid SHA256SUMS listing `t3-1.1.0-linux-x64.tar.gz`, archive response 503. For 403: deny checksums. Expected asset-specific step and HTTP status, host only. Include dummy URL credentials/query/body in the harness response request to prove omission. | Yes; same visible shape as S1 | Existing focused test is sufficient for these substitutions; no extra image/transcript required beyond S1. | H1; select failure asset/status exactly as the new parameterized test does. A real mirror must serve valid checksums before failing the archive. | **TEST-SOURCE**: all three HTTP cases assert exact reason, empty staging, no extraction/preflight/handoff, successful retry. |
| S3 Transport failure, host only | Request fails without a response, e.g. connection refusal; expected `while downloading the t3 release checksums (from host[:port]).` No fabricated HTTP status. | Yes | Sequential runtime transcript; can collapse with S4 for optional-metadata shape, **no separate capture required**. | H1 using HttpClient transport error; alternatively isolated target with release base at a closed loopback port. | **TRACED**: `isHttpClientError` true, non-StatusCodeError; no focused assertion found. |
| S4 Download timeout | Headers never arrive, or response body stalls for the existing one-minute limit. Expected `while downloading the t3 release checksums (timed out) (from host[:port]).` No HTTP status for TimeoutError, even if headers arrived before a body stall. | Yes | **Required sequential runtime transcript (recording/time)**; record request-start, timeout result, and cleanup/no handoff. A still cannot establish timeout. | H1 with Deferred/never-completing HTTP operation; use TestClock adjustment for deterministic harness output. Header/body variants share the formatter; one representative transcript suffices. Real isolated HTTP mirror can accept and never complete headers/body. | **MISSING**: not in inline transcript or focused assertions. |
| S5 Non-download error, step only | Valid downloads with wrong archive digest; expected `Could not prepare t3@1.1.0 while verifying the t3 release archive checksum.` No empty `()` or invented HTTP metadata. | Yes | **Required runtime transcript** for the third reachable metadata shape. No client capture. | H1; list a deliberately wrong SHA-256 for the supplied archive. Stops before extracting, so no real archive/launcher is needed. | **MISSING** at service-reason layer; existing pinnedRuntime test checks only the lower-level checksum step/cleanup. |
| S6 Managed mirror missing, unchanged | Managed policy organization Example Corp, feed URL, updates mode auto; checksums or archive HTTP 404, or checksums omit this machine's archive. Expected `Example Corp's update mirror has no t3@1.1.0 for this machine.` A public/default feed 404 without managed policy instead uses S1 formatting with HTTP 404. | Earlier branch preserved | No new capture required. | H1 with policy; real isolated target needs managed policy, not just T3CODE_RELEASE_BASE_URL. Cached runtime still rechecks publication when a policy mirror is configured. | **TEST-SOURCE**: service checks absent archive listing; pinnedRuntime checks archive 404 versus 500 and cached publication. |
| S7 Preflight blocked, unchanged | Validated archive; runner returns JSON `{status:"blocked",version:"1.1.0",reason:"local update required"}` with exit 0. Expected reason exactly `local update required`, not the new preparation wrapper. | Earlier branch preserved | No new capture required. | H1 runner mock using existing harness option `preflight:"blocked"`. Do not run a real update to force a database migration refusal. | **TRACED**: preserved branch and harness option; no test invokes that option in this file. |
| S8 Other gates / success / cached / handoff, unchanged | Policy off/nightly restriction, unmanaged server, desktop-managed server, invalid version, unsafe track crossing, concurrent update, launcher rejection; idle, downloading/installing, reconnecting, success. | No new reason formatting on these branches | No new capture required. Any actual update/restart interaction is **recording** if captured. | Existing service harness options; cached runtime via sentinel plus matching version; ordinary gated app state. | **TRACED / TEST-SOURCE**: existing cases exercise several gates, desktop delegation, concurrency, progress and success. Not all listed guards independently executed here. |
| S9 Failed update → retry | Failed asset fetch leaves no staging runtime and never calls launcher; switch harness HTTP to success, retry same target, extraction → preflight → launcher acceptance. | Cleanup/retry behavior unchanged, asserted by new tests | **recording** if shown in app; existing sequential harness transcript/test suffices. No live successful update required. | H1 reset failure flag; mock extraction/preflight/launcher as existing test does. | **PARTIAL + TEST-SOURCE**: inline transcript and each new HTTP test cover this; not a real restart/reconnect recording. |
| C1 Web/desktop ordinary reason | `state={status:"failed",stage:"downloading",fromVersion:<current>,targetVersion:"1.1.0",message:<S1 error.message>}`. Settings progress truncates, tooltip contains full message; single-update toast wraps, has Copy error. Ordinary S1 description is below the 180-character clamp threshold. | Text only | **recording** for triggering update/revealing tooltip/copy; no separate capture beyond C2 required. | H2 at `/settings/connections` saved environment row or `/settings/access` primary Version row. Normal app path: select an eligible boot-service target with version mismatch and maintain permission, click Update. | **TRACED**: no client capture in PR. |
| C2 Web long host, constrained width | Same state, but use a plausible long multi-label mirror host so full error.message is ≥180 characters. Tooltip must expose step, status and complete host. Error toast takes `line-clamp-4`; Copy error retains full text. Version and environment label may lengthen title too. | Yes, longer input reaches existing overflow/clamp branches | **Required one client recording**, web **390×844, dark theme, pointer capable**. Show long error toast, Copy error and its feedback, then hover/focus truncated Settings row to show full tooltip. Include a paused final frame with complete reason legible. No light-theme or wide duplicate. | Prefer H2 actual isolated service-managed target + failing local mirror under a long resolvable test hostname. Alternatively H3 explicitly labelled client-state harness. Use ordinary S1 as semantic baseline in transcript. | **MISSING**: the runtime transcript cannot prove truncation/reveal/copy behavior. |
| C3 Active thread composer | One selected environment, not automatic balancing; failed state, not dismissed. Banner description holds message. It truncates and shows a details popover; description becomes sr-only below the existing 400px container breakpoint, so details reveal matters. Retry exists only if mismatch/capability allows; dismissal removes the notice. | Same longer string | **recording** for details/retry/dismiss; no extra capture required for this unchanged renderer. | H2/H3 then open a thread on that environment. Use Show details / notice information affordance. | **TRACED**: `useSystemComposerBanners` → `ComposerBannerStack.NoticeText`. |
| C4 Auto-balanced composer | 0 machines → no notice; 1 or many eligible environments, running takes title precedence, then failed, then idle. Per-machine reason is only inside the machines popover, via ServerUpdateProgress; that reason may then require tooltip reveal. Retry/Update all and dismissal depend on running/eligible counts. | Per-machine text only | **recording** for popover/tooltip/retry/dismiss; no extra capture required. | H2/H3 on environment(s) participating in automatic balancing; open View machines, then failure tooltip. | **TRACED**: shared progress renderer, no distinct new count/layout branch. |
| C5 Batch/sidebar/track switch | Update everything uses quiet updates, stores each failed reason as machine.failure, renders wrapping `break-words` text inside SidebarUpdateCapsule popover, and joins labelled failure lines into result toast. 0 failures gives success/no toast; 1/many failures change count/lines; mixed successes alter title. Track switch uses same update-everything path. Retry/Dismiss and active/countdown/elapsed states are existing. | Failure text only | **recording** for update, popover, retry, countdown or dismissal; no extra capture required. | H2 update-everything UI; fail one eligible remote boot-service target. Multiple targets only needed to inspect batching, outside this change's minimum. | **TRACED**: `useUpdateEverything`, `updateEverythingRun`, `updateEverything.logic`, `SidebarUpdateCapsule`; no batch capture. |
| C6 Mobile iOS/Android | SettingsEnvironmentDetail: connected+config branch shows wrapping selectable failure Text; local action error can also repeat the same message above the section. OutdatedServerUpdateSection shows an alert Text when failed, unless a non-dismissed check error takes precedence. Checking/running disables actions; retry label differs; disconnected/no permission/manual/desktop-managed branches gate access. | Shared longer string, no native component change | **recording** for confirm → update → fail → retry/check transitions; static layout can use a still if separately desired. No native capture required in this minimum. | Settings → Environments → selected environment (`SettingsEnvironmentDetail` route, environmentId). H2 with reachable isolated service-managed target; outdated target additionally needs version incompatibility. H1 text alone cannot claim native visual proof. | **TRACED**: two native render paths; no mobile capture in PR. |

There is no naturally reachable status-only/no-host fetch case for a valid HTTP(S) URL: URL.parse(...).host exists. The optional formatter permits `(HTTP N)` if supplied a synthetic install error without downloadHost; label that harness-only if exercised, and do not add a production fixture just for the type's optionality. No-metadata errors in S5 are reachable. Empty error reason is rejected by the existing contract; empty/idle UI states are unchanged. User theme colors, platform typography, viewport widths and target counts do not change the server mapping; the one constrained-width client recording samples the meaningful new length risk.

## Complete source walk and collapse decisions

- Changed [pinnedRuntime.ts](https://github.com/pingdotgg/t3code/blob/d49e8ee0f13114789f7c7a43480f1d0b51626259/apps/server/src/cloud/pinnedRuntime.ts): `fetchReleaseAsset` has exactly two callers: `publishedArchive` for SHA256SUMS (no progress callback) and `installFromArchive` for archive bytes (with download progress). Request/header timeout, HTTP status rejection, stream stall, and stream transport/decode errors meet the same error mapper. StatusCodeError yields status; every parseable request/fallback URL yields host including port; only TimeoutError adds `(timed out)`. The optional fields do not alter PinnedRuntimeInstallError.message. `ensurePinnedRuntimeInstalled` is called by selfUpdate, CLI update, and bootService installation. CLI/bootService keep their existing error formatting; their cause gains metadata, with no new client render path.
- Changed [selfUpdate.ts](https://github.com/pingdotgg/t3code/blob/d49e8ee0f13114789f7c7a43480f1d0b51626259/apps/server/src/cloud/selfUpdate.ts): unary ws handler calls `.update`; progress handler calls `updateProgressStream`, which calls the same service and fails its queue with ServerSelfUpdateError. In the changed mapper: blocked-preflight first; managed mirror missing second; otherwise step plus optional status/host. All other generated install-step values collapse to S5's no-metadata shape: checking cached state; selecting platform/architecture archive; removing incomplete runtime; preparing versions/staging directory; finding archive in checksums (except managed-mirror override); digest verification; writing/extracting archive; running/decoding/verifying staged preflight; recording sentinel; concurrent-publication check; publishing runtime. Exit code and stdout/stderr lengths are retained in causes, not appended to reason. Cached valid runtime skips downloads, except managed mirrors revalidate publication, and still runs preflight. These paths introduce different words, not new layouts or formatting branches.
- Changed [selfUpdate.test.ts](https://github.com/pingdotgg/t3code/blob/d49e8ee0f13114789f7c7a43480f1d0b51626259/apps/server/src/cloud/selfUpdate.test.ts): parameterized HTTP regression uses injected HttpClient and returns baseDir to inspect cleanup; it has no rendered component. Synthetic response request is deliberately on a different host from the original download URL. Exact message assertions prove host selection/omission of dummy URL secrets for these cases.
- Existing [contract](https://github.com/pingdotgg/t3code/blob/4c361199f47f6ea32a2c14835df392640d0f7c63/packages/contracts/src/server.ts#L951) and [shared state](https://github.com/pingdotgg/t3code/blob/4c361199f47f6ea32a2c14835df392640d0f7c63/packages/client-runtime/src/state/server.ts#L832): reason becomes the prefixed Error.message; normal and outdated update commands save it in failed state. Both unary and streaming RPCs carry the same existing error union. No schema/client prop changes, no provider-adapter differences, no new scheduled-task or agent-specific render path.
- Existing [ServerUpdateAction.tsx](https://github.com/pingdotgg/t3code/blob/4c361199f47f6ea32a2c14835df392640d0f7c63/apps/web/src/components/ServerUpdateAction.tsx#L111): useServerUpdate callers are ServerUpdateAction, ServerUpdatesAction, useUpdateEverything and SidebarUpdateCapsule retry. ServerUpdateAction is used by ConnectionsSettings primary version, SavedEnvironmentRows, active composer retry, and auto-balanced composer manual fallback. ServerUpdatesAction is used by saved-environment Update all and auto-balanced banner. OutdatedServerUpdateAction is used by saved-environment unsupported/version-skew row. Shared ServerUpdateProgress appears in primary version, saved environment rows and auto-balanced per-machine popover. Single/batch/outdated button appearance, label, variant and size affect the trigger, not reason formatting. Eligibility branches: authenticated maintain grant, duplicate/in-flight guard, managed-policy denial, desktop support, cross-track refusal, manual copy-command fallback, confirmation cancellation, interruption. Only requests that actually reach a boot-service install produce this diff's reason.
- Existing [toast](https://github.com/pingdotgg/t3code/blob/4c361199f47f6ea32a2c14835df392640d0f7c63/apps/web/src/components/ui/toast.tsx#L115) has a meaningful length boundary: string error descriptions shorter than 180 characters wrap normally; at/above 180 get four-line clamp. Copy error appears for string descriptions. Batched result description is a React span of newline-separated failures, so the string-length clamp and string Copy error affordance do not apply to it. Stacked/collapsed toasts and dismiss timing are existing controls; isolate one toast for the minimum C2 recording. Progress tooltip wraps with a max width and anywhere breaks, unlike its truncated trigger.
- Composer NoticeText supports title/description truncation, width-driven details affordance, and narrow-container description hiding; auto-balance uses count-based titles plus nested progress rows. Sidebar batch popover uses wrapping failure text. These are distinct existing renderers, explicitly traced in C3–C5 without claiming capture coverage. No direct server-update command-palette or keybinding entry point was found; settings navigation leads to the same controls. Settings About track-switch confirmation uses the shared update-everything path.
- Existing mobile SettingsEnvironmentDetail and OutdatedServerUpdateSection are distinct components; no line limit is set on their reason Text. iOS/Android share this code. Normal detail can show the action's local error and the shared failed-state reason concurrently; outdated section suppresses check error after starting update. These existing branches are C6. Desktop Electron wraps the web components; its own desktop-app updater is not this server download path.

## Capturer seeding recipes

**H1 — labelled service harness, not live server update.** Use `ServerSelfUpdate.make` with the same dependencies as `apps/server/src/cloud/selfUpdate.test.ts`: isolated baseDir; web mode; launcher `managed:true` and a mocked requestUpdate; linux/x64; mock ProcessRunner; injected HttpClient; normal policy unless the fixture says otherwise. Call `.update({targetVersion:"1.1.0"})` and record both error.reason and error.message. Do not fabricate the final string in the driver. Record revision, injected asset/status or timeout milestone, staging listing, and whether launcher was called. Run equivalent driver at origin/main and requested local merged head for S1. Record S4/S5 at head; their baseline generic message is shared with S1. Keep every driver/output/temp baseDir inside a new timestamped directory under `/tmp/lead-33239-r1/proof-audit-1/`; no temp files elsewhere. For deterministic time use TestClock and Deferred, not sleeps/polling. Real HTTP transport can be substituted for S1/S5; this does not make the mocked launcher a live server update. Never publish dummy credential-bearing request URLs or raw causes; publish the sanitized error message and explicit injection label.

**H2 — real app, isolated target.** A real target must advertise boot-service and permit environment:maintain; a regular dev server is unmanaged. Set `T3CODE_RELEASE_BASE_URL=http://<fixture-host>:<port>/releases` before target service initialization. Serve `/releases/v<target>/SHA256SUMS` as 504. For archive errors, first serve a syntactically valid SHA256SUMS listing the correct OS/arch archive, then fail that archive URL. Force a target version absent from the isolated target's pinned cache, otherwise downloads can be bypassed. Managed-policy mirror feedUrl takes precedence over the environment variable, and changes 404 handling. Pair the isolated target into an existing isolated dev client; do not change any live service or live T3 home. `/settings/connections` displays saved-environment failures; `/settings/access` displays primary Version failure when canManageLocalBackend. A UI Update button needs mismatch/eligibility; otherwise drive shared updateServer command or RPC with targetVersion. RPC names are `server.updateServer` and `server.updateServerWithProgress` (target service update and progress stream); wire reason flows through both. Do not issue a successful update to a real target to collect retry proof; use H1's mocked handoff. Remote/relay/tunnel carry the same error contract and require no separate captures for this diff.

**H3 — labelled client-state harness fallback.** If no safe service-managed target is available, seed the existing real dev web app's in-memory atom via a development-browser driver: import `/src/rpc/atomRegistry.ts` and `/src/state/server.ts`, identify a real isolated connected environment, set `appAtomRegistry.set(serverEnvironment.updateStateAtom(environmentId), {status:"failed",stage:"downloading",fromVersion:<actual server version>,targetVersion:"1.1.0",message:<error.message from H1>})`. Match fromVersion to prevent config projection clearing a stale failure. Optionally import toastManager from `/src/components/ui/toast.tsx` and add the same error toast (`type:error`, title `<label> update failed`, description the message). Use the H1 captured message, substituting only a declared fixture host for the long-host variant. This proves actual renderer handling, not RPC propagation, real download, or service management; label it “client-state harness, no live update”. Restore original in-memory state afterwards. Long example host: `downloads.eu-west-1.enterprise-updates.production.internal-artifacts.example.test`; full fixture error.message is 190 characters; verify message.length ≥180 before seeding. Below 180 and at/above 180 are the toast's branch boundary. Keep added client fixtures out of source control.

## Minimum handoff

One public, labelled transcript file can contain S1 before/after, S4 timeout, S5 checksum failure, plus the already-covered S9 cleanup/retry sequence. One short public client recording C2 covers the long-message reading interaction; include the tooltip and copied-message feedback. Tests supply S2 substitutions and unchanged controls. A client-state harness recording is acceptable if labelled as such and paired with the actual service-harness transcript. No still proves hover/copy/timeout/retry transitions. Do not expand this into captures for every unchanged client, theme, count, provider, loading state, or installation platform.

No repository files were edited; no tests, servers, agents, browser sessions or long-running processes were started by this audit. Space check: 109 GB available. All source/fixture descriptions are code inspection unless explicitly attributed to the existing PR description.

FRICTION: none

VERDICT: INSUFFICIENT S1 paired baseline/current runtime transcript; S4 timeout transcript; S5 step-only failure transcript; C2 long-reason client recording
