# PR #23213 proof matrix (pass 1, code-derived, head f7082eceb)
Keys: web atom key = JSON [envId, input] (packages/client-runtime/src/state/runtime.ts:467-472, :583). Query: swr staleTime 5 min, revalidateOnMount (only when stale), idleTTL 60 min, refresh every 30 min (client-runtime assets.ts:29-31,178-186; runtime.ts:671-677). Server mints a new signed URL per call: token embeds ms expiresAt + dev/inode (server assets/AssetAccess.ts:520,413-419,843-846). Route sends `Cache-Control: private, max-age=3600` for images (server http.ts:107; no-store only audio/video :197-203). Mobile <Image cachePolicy="memory-disk"> (ThreadMarkdownImage.tsx:178), URL-keyed.
Head key: web input `{resource, cacheRevision: message.id}` (TimelineAssistantRows.tsx:46 → ChatMarkdown.tsx:1470 → ChatMarkdownMedia.tsx:338 → assetUrls.ts:100-102); mobile via MarkdownMediaRevisionContext (ThreadFeed.tsx:1558, ThreadMarkdownImage.tsx:254-259, media-file only).

| Row | Scenario | BEFORE (base) | AFTER (head) | Evidence |
|---|---|---|---|---|
| M1 | Reply 1 shows P (content A) | A | A (key [env,{resource,cacheRevision:r1}]) | still |
| M2 | Overwrite P→B, new reply 2 refs P, no reload | A *if* reply 2 mounts <5 min after reply 1's mint: same atom key → same URL → browser/expo cache hit (max-age 3600). If >5 min, swr revalidates on mount → new URL → B (ambiguous, timing-dependent) | B: new key r2 → fresh RPC → new token URL → uncached fetch | RECORD (show timestamps; overwrite→reply2 must be <5 min for base to reproduce) |
| M3 | Reply 1 after overwrite, no reload | A (shares atom with reply 2; same URL, cached bytes). If >5 min stale and re-read, could flip to B together with reply 2 | A (r1 atom untouched; its URL still in HTTP cache ≤1 h). Becomes B only on r1 refresh: stale+remount (>5 min), 30-min interval, or HTTP cache expiry/eviction after which the old URL is re-fetched (same inode → server serves B; replaced inode → 404 → broken img; MediaFile.ts:55-60) | still (same frame as M2) |
| M4 | Full page reload | Both B (atoms reset, fresh mint, new URL; ms expiresAt differs) | Both B (same reason) | still each; mobile app restart same (new URL, disk cache miss) |
| M5 | Switch thread & back, no reload | Atom kept alive by idleTTL 60 min, key unchanged → within 5 min: both A (same cached URL); >5 min: revalidate on mount → both B | Keys unchanged by unmount (message ids stable) → within 5 min: r1 A, r2 B; >5 min: both re-minted → both B | RECORD (state time elapsed) |
| M6 | RPC payload | WS text frame, JSON (RpcSerialization.layerJson: client rpc/session.ts:224, server ws.ts:3218): `"tag":"assets.createUrl"`, payload `{"resource":{"_tag":"media-file","threadId":…,"path":"P"}}` | Identical payload, NO `cacheRevision` (destructured out client-runtime assets.ts:146-150; local fallback reuses stripped `input` :174). Contract is `Struct({resource})` (contracts/assets.ts:60-62; rpc.ts:1295) | still: DevTools → Network → WS → Messages, filter `assets.createUrl`; show a head frame lacking `cacheRevision`, and two distinct frames (r1, r2) for the same path on head vs one on base |

## Ambiguities the code shows
1. 5-minute swr staleTime: base can look "fixed" (B in reply 2) if >5 min elapse between first mint and reply 2 mount; proof must keep it <5 min and show the clock.
2. Browser HTTP cache keyed by URL (private, max-age=3600); every mint yields a distinct URL (ms expiresAt), so "fresh" = new URL, not revalidation. Disabling cache in DevTools invalidates the base repro. Two mints in the same ms would collide (negligible).
3. Overwrite method matters: in-place write keeps inode (old URL serves B if refetched); temp+rename changes inode → old token 404s (MediaFile.ts:55-60,73-77). On head, reply 1 could then go broken rather than A once its cached bytes expire.
4. Chat timeline passes no workspaceMutationId, so the mutation-refresh in ChatMarkdownMedia.tsx:346-372 does not fire there; its refreshToken also ignores mediaRevision.
5. Within one reply (streaming chunks, same message.id) the key is shared → overwrite mid-reply is NOT refreshed (PR body states this). Mobile applies revision only to `media-file` resources; web GitHub-media branch (ChatMarkdown.tsx:1396) gets no revision.
6. Mobile expo-image disk cache persists across app restarts but is URL-keyed, so M4 holds; still unverified on device (prior audit: desktop/iOS/Android cannot-verify).
7. useAssetUrls/createUrls (client-runtime assets.ts:187-196) and FilePreviewPanel callers have no revision → unchanged behavior; not covered by this fix.
