# Attachment proof audit — pass 1

Status: capture pass completed; static/runtime/hash checks pass. Full continuous playback review remains pending. No repository files changed. Replacement diff under capture: `origin/main...8ea57ac19f782aeaa82003c5ddff73419f35bdef`.

Before: `c4fa4629eb645b95aae2beef56c70f705b736049` (current replacement merge-base, confirmed origin/main). After: `8ea57ac19f782aeaa82003c5ddff73419f35bdef`. Do not use the contributor's old merge-base. Replacement scope: five web files covering intake/classification, draft-store compatibility and their focused tests; no sensitive paths changed. The dispatcher reports two fresh full-diff reviews at this after head, both MERGE. This capture pass does not repeat that review; runtime/static results appear below, with continuous playback review pending.

## Derived paths and data

- `ChatComposer.tsx:2724` installs `useComposerAttachmentIntake`. Picker (`ComposerAttachmentStrip.tsx:671`), clipboard (`ChatComposer.tsx:2803`), imperative drop (`:3102`), and dropped thread-file attachment (`:2877`) converge on `addComposerAttachments`. Paste has an additional dispatcher gate, so picker evidence alone is insufficient.
- `composerAttachmentFiles.ts:62`: terminal `.dng`, case insensitive, OR normalized `image/dng`, `image/x-dng`, `image/x-adobe-dng` identify DNG. A supported-looking filename alone is not sufficient: `export.dng.png` remains PNG. No byte sniffing is introduced.
- `useComposerAttachmentIntake.ts:162`: DNG now uses the file branch before HEIC/image handling. It checks server file support, positive byte length, file limit and shared slot capacity. It wraps the original File only to canonicalize MIME to `image/x-adobe-dng`; `new File([file], ...)` preserves bytes. Ordinary JPEG/PNG use `prepareImageForAttachment`, thumbnail/preparing state and image budgets.
- Draft file shape: `{type:'file', id, name, mimeType:'image/x-adobe-dng', sizeBytes, file:File}`. File references are inserted into the real prompt editor via `fileContextReference`; an inline reference can hide its redundant row via `composerOtherFilesForPresentation`. Therefore do not demand both a file-list row AND an inline chip. Capture actual chip, filename, size where displayed, and absence of an image thumbnail for DNG. JPEG/PNG may appear in the media row or inline image chips depending on nonempty prompt; use the same prompt before/after.
- `ComposerAttachmentFileList` handles ordinary file rows, uploads, remove and preview; the prompt editor renders inline file references. No component CSS changed. `ChatComposer.tsx:3370` gates media/file strips on mobile collapse; at width 390 expand/focus composer before still capture. This is responsive web, not native mobile.
- `useComposerSend.ts:760,803,868,1257`: snapshots both attachment arrays, binds context by upload IDs, waits for uploads, and calls `startThreadTurn`. `packages/client-runtime/src/operations/commands.ts:631` persists attachments then sends either launchThread with initialMessage (new draft) or dispatchCommand/message.dispatch (existing thread). Do not incorrectly insist on only one RPC name.
- `types.ts:64` and `packages/shared/src/image.ts`: legacy type:file with PNG/JPEG MIME is treated as image downstream. Canonical DNG MIME prevents that promotion and image-budget charging. A visual chip alone cannot prove this; inspect real outgoing attachment records and persisted originals.

## Minimum visual matrix

Every row is a BEFORE/AFTER pair. Same browser version, device scale 1, viewport height 900, frozen fixture text, sidebar and scroll state. All DNG MIME variants reuse identical 950-byte synthetic CFA DNG bytes and hash from fixture-manifest.json. Two behavior recordings can concatenate labelled chapters; do not create one recording per MIME/theme. Start each chapter with a clean draft. Capture through headed Chromium on Xvfb and ffmpeg x11grab, never the T3 recorder.

| ID | Affected behavior / fixture | Viewport × theme | Type / minimum outputs | Before expectation | After expectation / required observation |
|---|---|---|---|---|---|
| S1 | DNG `audit-original.DNG`, image/tiff; prompt `Inspect this original` | 1440×900 light | static pair | unsupported-image error, no DNG chip | generic file chip/reference, no raw-image decode error |
| S2 | same exact staged state as S1 | 1440×900 dark | static pair | same semantic rejection | readable chip/controls and no clipping |
| S3 | same; expanded web composer, sidebar closed | 390×900 light | static pair | same semantic rejection | filename/reference visible, composer/send controls usable |
| S4 | same as S3 | 390×900 dark | static pair | same semantic rejection | same, with readable dark theme |
| B1 | Native picker small DNG (record actual browser MIME), then deterministic image/tiff override; JPEG image/jpeg + PNG image/png controls in same composer; send | 1440×900 light | chapter in before + after x11grab recordings | DNG rejected if actual MIME TIFF; JPEG/PNG still stage/send as images | DNG generic chip; JPEG/PNG image presentation; outgoing type:file canonical DNG plus two type:image controls; persisted DNG SHA-256 exact |
| B2 | same DNG bytes/name with image/png then image/jpeg, separate clean drafts; picker change dispatch; attach JPEG/PNG controls and send each | 1440×900 light | chapters in same two recordings | raw bytes stage as images with broken thumbnails and can upload; controls survive/send | raw bytes bypass compression; generic DNG chip; sent type:file canonical MIME; SHA exact in both cases |
| B3 | DNG application/octet-stream plus caption `Inspect this original` through real composer paste handler | 1440×900 light | chapter in same two recordings | gate leaves plain-text paste path; no DNG attachment | gate claims transfer; DNG chip inserted and sent as generic file; SHA exact. Caption is not promised to be retained by existing attachment paste handler |
| B5 | Legacy application/octet-stream DNG needs-reattach marker among 100 attachment markers | 1440×900 light | chapter + endpoint pair | generic-MIME marker can reattach without changing MIME | canonical DNG reattachment replaces the old marker at capacity, count remains 100; no capacity error |
| B4 | DNG empty MIME via drop; DNG image/x-adobe-dng named `clipboard` via paste | 1440×900 light | chapters in same two recordings | empty MIME DNG already generic; MIME-only DNG rejected | both generic files, canonical MIME; remove one chip and reattach before send to verify reverse path; SHA exact |

Minimum physical capture files: 8 stills + 2 recordings with B1–B4 labelled chapters. S1 may be a lossless frame from B1 if identical viewport/state; avoid redundant capture. This covers every visually distinct new path at desktop light; themes and responsive layout are static because the changed branches do not depend on either. JPEG/PNG controls distinguish generic originals from supported images. B2 explicitly covers both misleading supported MIME labels, not just the final chip. B3 proves the changed paste dispatcher; B4 covers already-working generic staging and MIME-only recognition. No separate Electron or native mobile captures: no surface-specific implementation changed.

## Nonvisual branch checks (no extra screenshots)

| Branch | Minimal check / outcome |
|---|---|
| terminal extension, MIME aliases | `.DNG` and `.dng`, TIFF/PNG/JPEG/DNG aliases/empty/octet/unknown labels all classify as file; no-name MIME aliases too; trimmed mixed-case MIME with parameters stays DNG |
| ordinary image boundary | real jpg/png remain images; `export.dng.png` remains image; GIF/WebP and HEIC behavior unchanged; ordinary TIFF and CR2 remain unsupported |
| limits and downstream shape | canonical DNG isFileAttachment true/isImageAttachment false, no image-byte budget; ordinary file size/slot/capability gates remain enforced; no new bypass for oversized or empty DNG |
| bytes | compare original fixture hash, actual uploaded body hash, persisted served/downloaded original hash for every sent DNG variant. Names/MIME/size alone are insufficient. Record sanitized wire shape with type, MIME, name, size, attachment ID and bound context; never publish auth headers/upload URL tokens |
| reattach / restore | after upload/reload, persisted DNG remains generic and sendable; before upload, reattach marker matches canonical MIME; removal eliminates reference and attachment |

Existing added tests cover classifier aliases, downstream image budgets, generic captioned paste, ordinary images. Their byte fixture is only 12 bytes and not a decodable DNG; generated CFA fixture here supplies real structural DNG bytes for integration. Tests do not cover actual dispatcher/upload/persistence, which B1–B4 must prove. Browser OS MIME is platform-dependent, so log it and use labelled synthetic File overrides for deterministic TIFF/PNG/JPEG cases.

## Real app setup and instrumentation

Use existing authorized worktree only. Do not run this pass. Later start `T3CODE_DEV_SEED=0 vp run dev --no-seed --home-dir /tmp/ev-16308` and read actual origin/ports from dev-runner output. Inspect CLI help if argument forwarding is required; never silently fall back to default home. No access to ~/.t3/userdata; no migrate-dev-db, live DB copies, secrets/settings copies, or external tunnel origins. Do not publish startup pairing credentials. Retain spawn PIDs and stop only those.

A fresh real app is sufficient: pair the isolated headed browser, Add project pointing to the existing replacement worktree (no new checkout), choose a locally configured provider, New thread in root mode (no worktree setup), select identical model/runtime, enter the fixed prompt, use actual composer picker/paste/drop then Send. Project creation uses `projectEnvironment.createNew`/`createProject` -> project.create mutation (`packages/client-runtime/src/operations/commands.ts:343`). First send uses the launchThread route described above; later sends use message.dispatch. No direct SQLite seeding needed. Request only acknowledgement of fixture receipt and no tool use so the provider doesn't edit the repo. A provider failure after a committed user message does not negate upload/dispatch evidence, but label it explicitly and never claim provider understanding of DNG.

`attachment-driver.js` is a browser-context input helper, not a component harness and not a complete capture runner. It creates deterministic File MIME variants and invokes real DOM change/paste/drop dispatch. Resolve the editor from the actual DOM and require exactly one composer picker; fail rather than accidentally use a reattach input. Use native file selection at least once in B1. Instrument actual browser WebSocket frames and upload responses externally; inspect the isolated server's attachment directory or authenticated asset download read-only after send. A separate pretty fixture page or mocked ChatComposer is insufficient while the real app can run. If no provider is configured in fresh home, report that friction; don't copy credentials or silently replace the transport with a mock. A labelled transport harness can supplement classifier checks but cannot satisfy attach/send rows.

Run before and after sequentially in this same worktree only by coordination with the primary agent; this read-only reviewer does not checkout either revision. Keep the fresh-home run data distinguishable by version, stop the captured server PID between revisions, and never run the same home concurrently. Baseline must run current main commit above. Any missing required fixture means pass 2 is incomplete, not an implicit pass.

## Pass 2 visual and diff acceptance

For each S pair: retain originals, exact unmasked diff0 (`abs(after-before)`, any changed pixel counts), amplified diff0 (8× channel difference clamped to 255), and a JSON report with dimensions, changed-pixel count, percentage and bounding box. State thresholds per pair before judging: S1–S4 are expected to differ in the DNG error/chip/composer reflow region; outside a predeclared union of those affected bounds require <=0.1% pixels with maximum per-channel delta >16. Report full-frame diff0 even when expected dynamic regions are separately assessed. Never use a broad threshold to hide clipping or missing controls. For JPEG/PNG control endpoint comparisons require <=0.1% pixels with delta >16 in aligned attachment regions. Record all masks/regions and reasons in public metadata; no silent resizing/cropping or moving thresholds after seeing results.

Watch both full recordings visually, not just contact sheets: native selection, TIFF rejection, both misleading MIME failures before, generic original success after, image controls, paste/drop dispatcher, remove/reattach and send endpoint. Compare representative aligned frames for each B chapter using the same diff0/amplified/report protocol; allow expected transition differences, but require endpoints and raw-payload/hash assertions. Mark every matrix row pass/fail/blocked with exact artifact links and timestamps. No capture has been reviewed in pass 1.

## FRICTION

- No browser/server launch permitted in this pass: real selector locators, auth availability, native picker MIME, actual upload/send behavior and screenshots are deliberately unverified.
- Fresh state may lack provider config; verify live UI, don't infer it or import live secrets.
- Browser automation helper is intentionally not a runnable navigation/recording driver yet; the primary capture runner must use actual discovered app UI and retain/publicly upload its final driver and sanitized trace.
- Upload URLs and browser auth are secrets. Public traces must keep only method/field/hash evidence, not headers, cookies or signed URLs.
- Generated fixtures and helper must be uploaded, never committed. No screenshots or recordings are claimed here.

## Pass 2 execution results

All rows captured in the real app. S1–S4: PASS for static review. B rows: runtime, byte and endpoint checks PASS; continuous motion review PENDING. No provider understanding is claimed. Before/after staged endpoints are linked below; real sends appear in both chaptered recordings.

| Row | Status | Before | After | Diff0 | DiffT >16 | Video chapter before / after seconds |
|---|---|---|---|---|---|---|
| S1 | PASS static | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/0dff378c51bcaea0/before-S1.png) | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/0e2e2c564f736898/after-S1.png) | [Diff0](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/48a285e19625e090/S1-diff0.png) | [DiffT](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/1d63299387c5624f/S1-diffT.png) | static / static |
| S2 | PASS static | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/a8c6dd1c89b56abe/before-S2.png) | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/fe75b77db2f4e3a9/after-S2.png) | [Diff0](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/9e91ab8d688f3c9a/S2-diff0.png) | [DiffT](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/44ee188a2bc14c75/S2-diffT.png) | static / static |
| S3 | PASS static | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/e5efd14c3e25077e/before-S3.png) | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/960994b9b3f55a40/after-S3.png) | [Diff0](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/4b687df909eb0dc9/S3-diff0.png) | [DiffT](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/af05ad6acb0ada0e/S3-diffT.png) | static / static |
| S4 | PASS static | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/8c8dd9e73345faaf/before-S4.png) | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/d8f77d0082694ff0/after-S4.png) | [Diff0](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/ab39ab3081c47ebe/S4-diff0.png) | [DiffT](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7363e3882f551e71/S4-diffT.png) | static / static |
| B1native-endpoint | PASS runtime; motion PENDING | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/d0d101fe1039e4f3/before-B1native-endpoint.png) | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/d8c07f7c910f03e4/after-B1native-endpoint.png) | [Diff0](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/7ca681a18e6798e3/B1native-endpoint-diff0.png) | [DiffT](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/2e67c2498e69570c/B1native-endpoint-diffT.png) | 0.008 / 0.009 |
| B1-endpoint | PASS runtime; motion PENDING | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/339bdf88cdeb1bf8/before-B1-endpoint.png) | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/bb4b52e828e5370c/after-B1-endpoint.png) | [Diff0](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/72d411dcdeedfe1c/B1-endpoint-diff0.png) | [DiffT](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/ba0ab6306b0a117f/B1-endpoint-diffT.png) | 4.776 / 4.695 |
| B2png-endpoint | PASS runtime; motion PENDING | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/ecda09799b131508/before-B2png-endpoint.png) | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/2e1fa22b89f1c4c0/after-B2png-endpoint.png) | [Diff0](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/d0dcb5797d48210e/B2png-endpoint-diff0.png) | [DiffT](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/43d73278edc24b3f/B2png-endpoint-diffT.png) | 13.65 / 13.645 |
| B2jpg-endpoint | PASS runtime; motion PENDING | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/c9ee4cdb9e7e65d4/before-B2jpg-endpoint.png) | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/e2822e692bc2b1a9/after-B2jpg-endpoint.png) | [Diff0](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/13f411d689cca3f7/B2jpg-endpoint-diff0.png) | [DiffT](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/e5375d468cbcd397/B2jpg-endpoint-diffT.png) | 22.512 / 22.541 |
| B3-endpoint | PASS runtime; motion PENDING | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/711ff27a822f36a2/before-B3-endpoint.png) | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/10e5e86399d3e25a/after-B3-endpoint.png) | [Diff0](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/071e52bcdebb15e4/B3-endpoint-diff0.png) | [DiffT](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/5b6f9bf77c83af96/B3-endpoint-diffT.png) | 30.727 / 31.169 |
| B4empty-endpoint | PASS runtime; motion PENDING | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/a035454e3d6399e7/before-B4empty-endpoint.png) | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/52f2d285b828b179/after-B4empty-endpoint.png) | [Diff0](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/fd9e3b81a75e7dde/B4empty-endpoint-diff0.png) | [DiffT](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/ec5818bae193ae32/B4empty-endpoint-diffT.png) | 36.839 / 38.28 |
| B4mime-endpoint | PASS runtime; motion PENDING | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/f1d173cfb83b94e4/before-B4mime-endpoint.png) | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/bae22de98de9eecc/after-B4mime-endpoint.png) | [Diff0](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/6a2751a86034880b/B4mime-endpoint-diff0.png) | [DiffT](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/b03735d8d8d56822/B4mime-endpoint-diffT.png) | 45.281 / 46.679 |
| B5-marker | PASS runtime; motion PENDING | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/dfec2b729aa71cb6/before-B5-marker.png) | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/a1db34d8c2af46d8/after-B5-marker.png) | [Diff0](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/535b1d5dd3b40934/B5-marker-diff0.png) | [DiffT](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/afc9b3a90ba8664f/B5-marker-diffT.png) | 50.808 / 53.279 |
| B5-endpoint | PASS runtime; motion PENDING | [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/8db9f4d82cb60b7f/before-B5-endpoint.png) | [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/5196ff888d2f3d99/after-B5-endpoint.png) | [Diff0](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/660a9225e377664c/B5-endpoint-diff0.png) | [DiffT](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/c8b35a243712454d/B5-endpoint-diffT.png) | 50.808 / 53.279 |

Recordings: [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/5326267c9ce7b958/before-chaptered.mp4) · [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/742499509dd2a57b/after-chaptered.mp4). Metadata: [Before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/1db16a41ea31ba48/before-capture-metadata.json) · [After](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/36d071e1f21702f1/after-capture-metadata.json).

FRICTION: PNG/JPEG-labelled DNGs in the baseline stage as broken images rather than always rejecting; observed behavior supersedes the original prediction. Native picker MIME is empty on this Linux browser. Full-video visual review is left explicitly pending for the pass-2 auditor. Seeded legacy markers are synthetic metadata in the isolated browser; no live data was used.
