# Proof audit, pass 1: Codex permission approval data

Issue [#26092](https://git.cafe/pingdotgg/t3code/issues/26092). No PR exists. Reviewed local branch `fix/issue-26092` in `/home/ubuntu/wt/issue-26092`, local head `1990668fc6f13818bc4913087d127856d43cb891`, merge-base `76d5ec7a22a312c3b9cb6a64102de1933321e665`. The head is not pushed, so there is no published commit permalink. The diff contains the adapter and two focused adapter tests; no client component changes.

This is a source-derived capture specification, not a capture review or an executed UI test. Every row is uncaptured in this pass. Ten raw protocol fixtures were decoded successfully with the checkout's actual `PermissionsRequestApprovalParams` schema. Expected before/after props were derived from source, not measured through the adapter.

## Findings first

1. The important comparison is requested rights versus explanation: `Network access`, access modes, and paths must appear as detail, with a different nonblank explanation below. A generic kind heading still says Command / File read / File change approval; that heading alone is insufficient proof.
2. The cap is twenty summary entries, including the network line, after exact-string deduplication of filesystem grants. W09 has network plus twenty-five unique paths: the visible summary ends at `read /w/p19` and then `+6 more`. Those six paths are absent from the prompt; scrolling cannot reveal them. This is a presentation limitation to show honestly, not a requirement to invent an expand control. Newline-containing paths can produce more physical text lines than the entry cap.
3. There is a second affected render path: expanded web history uses `V2ItemInspector` and renders `item.prompt` only. Moving the explanation to `item.reason` means it disappears from this view, although the pending card displays it and the turn item retains it. W11 explicitly records that consequence for the lead's scope decision; do not claim the reason remains visible in every historical surface.
4. Structured `entries` alone still classify as `command`; only legacy `write` and `read` arrays affect the heading, with write taking precedence. W04 therefore legitimately shows Command approval above filesystem grants in this head. This pre-existing classification is unchanged by the diff.
5. Mobile receives both fields through the same client-runtime mapping, so a duplicate native fixture matrix is not required for this server-only data change. Its renderer differs: proportional text, a warning-colored reason, and no detail height cap. M01 is an optional native layout smoke capture, not evidence already obtained. A narrow web capture is not a native capture.

Source anchors: [adapter formatting](/home/ubuntu/wt/issue-26092/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts:154), [permission request handler](/home/ubuntu/wt/issue-26092/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts:5232), [shared request mapping](/home/ubuntu/wt/issue-26092/packages/client-runtime/src/state/threadRequests.ts:225), [web panel](/home/ubuntu/wt/issue-26092/apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx:18), [web caller](/home/ubuntu/wt/issue-26092/apps/web/src/components/chat/ChatComposer.tsx:3372), [web history](/home/ubuntu/wt/issue-26092/apps/web/src/components/chat/V2ItemInspector.tsx:399), [native card](/home/ubuntu/wt/issue-26092/apps/mobile/src/features/threads/PendingApprovalCard.tsx:33), [typed path schema](/home/ubuntu/wt/issue-26092/packages/effect-codex-app-server/src/_generated/schema.gen.ts:29469).

## Data and capture conventions

Download [the complete fixture payloads and before/after PendingApproval props](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/4b5f5b6866a093a6/fixtures.json). No credentials or live user data are included. The file contains exact path strings, all special-path union variants, generated twenty/twenty-six-entry arrays, multiline and Unicode data, and source-derived expected output. Preserve the literal newline in W10's third path. Use fixture ids in filenames and captions.

- D = web 1440 × 1000 CSS px, dark theme, 100% zoom. Ensure the approval banner container is wider than 560 px so buttons sit beside the content.
- L = the same viewport, light theme.
- N = web 390 × 844 CSS px, dark theme, 100% zoom. Ensure the banner container is at most 560 px so buttons move below the content.
- P = optional native phone, approximately 390 × 844 logical points, dark theme, normal text scale; record the actual device/platform and dimensions.
- Match main and head viewport, theme, chrome/sidebar state, and input data. Capture the full approval banner with its action controls and surrounding composer; isolated text is insufficient.
- `recording` means a short video, including the initial and final states. Exporting still frames is useful but does not replace the video. Do not fabricate waits or claim response/loading behavior from static props.
- READY/R means a known real-app route, not that this pass ran it. READY/H means a fully specified, labelled component-harness fixture; it is not real Codex evidence.

## Fixture matrix

| Row | Named fixture and exact necessary data | Expected head / visible distinction | Diff effect | Minimum capture tuple and medium | Seed / capture status |
|---|---|---|---|---|---|
| W01 | Network only; `network.enabled=true`; reason `Run the formatter` | Command approval; detail `Network access`; separate reason `Run the formatter` | Yes: summary replaces reason in detail, reason moves below | D × W01; before/after still pair | READY/R; use real-app recipe R. Main reference supplied in brief; verify matching capture metadata before reusing. Uncaptured here. |
| W02 | Network plus `fileSystem.read=["/w/docs"]`; reason `Need to check the docs` | File read approval; two detail lines `Network access` and `read /w/docs`; reason below | Yes: mixed rights and reason separation | D × W02 and L × W02; before/after still pairs. W11 can supply D endpoint frames if all content is legible. | READY/R; recipe R. Uncaptured here. |
| W03 | Network false; typed entries read `/w/docs`, write `/w/out`, deny `/w/secrets`; duplicate legacy read/write paths; reason null | File change approval; three unique lines in entry order: read docs, write out, deny secrets; no network line or reason | Yes: every access mode, false network, legacy arrays, duplicate collapse, write heading | D × W03; before/after still pair | READY/H for exact mixed schema and null reason. R may supply legacy-only paths with omitted reason, but that does not cover typed deny/dedup. Uncaptured here. |
| W04 | Ten typed entries: literal path; glob; root; minimal; project_roots without/with subpath; tmpdir; slash_tmp; unknown without/with subpath. Full data in JSON. No legacy arrays; no reason. | Command heading; `write /w/out`, `deny /w/private/**`, `read entire filesystem (/)`, `read minimal system paths`, `read project roots`, `read project roots/docs`, `write $TMPDIR`, `write /tmp`, `read /opaque`, `read /opaque/cache` | Yes: all new path-label branches; all access modes share one text renderer | D × W04; before/after still pair; if actual font metrics cause scrolling, upgrade to recording | READY/H. Codex 0.163 tool exposure for this newer typed union is not established by the brief. Do not label as real Codex. Uncaptured here. |
| W05 | Network true; reason `  Network access  ` | Exactly one detail line; trimmed reason equals prompt, so no second reason line | Yes: equality suppression; whitespace-only/null/absent reason with a summary belongs to the same no-reason layout class | D × W05; before/after still pair | READY/H for deterministic exact reason. R can attempt it; inspect actual payload before relabelling. Uncaptured here. |
| W06 | Empty arrays, network false; reason `Need to inspect configuration` | Command approval; explanation remains the sole detail; no duplicated reason or invented rights | Fallback branch exercised; visible output unchanged | Control: no additional minimum capture. Optional D still, can be colocated with W05 in a labelled harness contact sheet. | READY/H. Exact protocol shape is schema-valid, but real Codex may skip an empty request. Uncaptured here. |
| W07 | `permissions={}`, reason null | Command approval fallback in detail; no reason. Empty string reason is equivalent. | Empty branch exercised; visible output unchanged | Control: no additional minimum capture. Optional D still | READY/H. No real provider empty-request guarantee. Uncaptured here. |
| W08 | Network true + nineteen unique legacy read paths `/w/p01`…`/w/p19`; reason `Inspect nineteen folders` | Exactly twenty summary entries; no `+N more`; separate reason. Detail scrolls at max-h-40. | Yes: exact cap boundary | D × W08; before/after recording pair, focus detail and scroll to bottom; show no suffix | READY/R via R; use H if real provider alters requested arrays. Uncaptured here. |
| W09 | Network true + twenty-five unique legacy read paths `/w/p01`…`/w/p25`; reason null | Network + first nineteen read lines + `+6 more`; no reason. Twenty-one rendered summary lines including suffix; omitted paths unavailable. | Yes: over-cap count, scroll, no reason | D × W09; before/after recording pair from top through bottom of detail | READY/H for exact no-reason payload; R can attempt the same path count. Uncaptured here. |
| W10 | Network true; 360-character unbroken path segment; Unicode/spaces/brackets/ampersand/angle brackets in another path; newline in a third path; long explanation. Exact strings in JSON. | Plain literal path text, wrapping without page overflow, reason wrapping, narrow-layout actions below. Newline path is not escaped by formatter. | Yes: newly longer detail and explanation geometry | N × W10; before/after recording pair, scroll long detail and keep reason/actions visible | READY/H for deterministic strings; R can supply long ordinary paths but provider normalization of exotic/newline paths is unverified. Uncaptured here. |
| W11 | Reuse W02's pending approval; refresh while live; open More approval options; dismiss menu; Decline; expand corresponding approval history row | Pending detail/reason survive client refresh while provider stays live; default session option is visible in menu; card disappears on response; expanded history shows summary but does not render reason | Prompt/reason data affects card and history; menu/action implementation unchanged | D × W11; before/after recording pair. Record interactions, not merely endpoints. | READY/R via R; no second provider run is needed beyond W02. Do not grant permissions solely for capture. Uncaptured here. |
| M01 | Optional: W02 on native client connected to the same dev environment | Both detail lines and separate reason via shared mapping; native typography and reason color differ | Data affected; native rendering code unchanged | Optional P × W02 still pair. Not part of minimum web proof and no native visual claim without it. | OPTIONAL/R when authorized native environment is available; not run here. |

Minimum set: **W01, W02, W03, W04, W05, W08, W09, W10, W11**, with W02 also in light theme. No full theme × width × fixture Cartesian product is needed because CSS and renderer branches are unchanged. W06/W07 are fallback controls, W08 is the one exact-boundary check, and M01 is explicitly optional. W03/W04/W05/W09/W10 can share a labelled harness session; each fixture must remain individually identifiable and show the real card and action layout. For strict real-provider-only evidence, W01/W02/W08/W11 provide it; the remaining rows must stay labelled harness until their exact live payload is observed.

## Real-app seeding recipe R

The lead, after browser authorization, starts its existing isolated dev setup with `T3CODE_DEV_SEED=0` and `T3CODE_CODEX_LAUNCH_ARGS="--enable request_permissions_tool"`. No VITE_HTTP_URL or VITE_WS_URL. Keep dev state out of the live T3 home. Select real Codex 0.163 and Supervised runtime mode; create a fresh thread per fixture or attempt, so prior grants cannot affect it.

Open the selected thread at `/<environmentId>/<threadId>` (the TanStack route is `/_chat/$environmentId/$threadId`). Ask the agent: “Call request_permissions exactly once with network enabled, read access to /w/docs, and reason Need to check the docs. Do not run a command, read a file, or request a different grant; stop at the approval request.” Substitute each row's requested rights and reason. The JSON fixture uses the app-server's camelCase field names; do not assume the model-facing tool uses those exact names. The condition for accepting a real capture is the resulting app-server request/persisted approval matching the row, not the agent saying it complied.

The brief establishes real reproduction for W01 and W02. W08 is the same path-array interface at a higher count, not already executed evidence. For exact-null reasons, duplicate legacy/typed entries, special union paths, empty permissions, and exotic strings, use H unless the actual request proves that the real CLI emitted exactly the intended shape. Do not silently simplify a fixture and claim coverage.

Leave the editor unfocused and idle before snapshotting so the existing composer typing guard does not hold a newly arriving request. Capturing typing-guard timing itself is outside this diff; if included in a recording, label it as an unchanged control.

Refresh only the client in W11; do not restart the provider. A provider restart makes pending approvals not resumable and is a different state. Use Decline or Cancel to clean up captures. Grant-lifetime behavior is unchanged: the web More menu contains “Always allow this session,” while the primary action is “Approve”; native defaults are “Allow once,” “Allow session,” “Decline.” A video of opening the menu proves the option exists, not that its grant lifetime works. Backend scope/decision correctness belongs to focused tests/runtime transcripts.

## Labelled harness seeding recipe H

There is no app RPC that publicly creates an arbitrary provider approval request; `respondToApproval` only answers an existing one. Do not invent an injection RPC or mutate the live database. Exact raw protocol frames are provided for a future replay driver: replace native thread/turn ids with the active ids and feed `item/permissions/requestApproval` through a Codex app-server peer. The existing adapter test's `requestPermissions` helper (near line 6748) demonstrates the replay preamble, request, and response, but it is test-local and is not a ready-to-run UI driver.

For pass-1 rows marked H, the immediately specified capture is a **component harness**: mount the checkout's unmodified `ComposerPendingApprovalPanel` and `ComposerPendingApprovalActions` inside the same `ComposerBanner` warning/spacious/approval layout used by `ChatComposer`. Supply `expectedBeforePendingApproval` or `expectedAfterPendingApproval` from fixtures.json, `pendingCount=1`, `canRespond=true`, `isResponding=false`, `disabled=false`, and default options. Use the checkout's real CSS/theme/fonts. A small temporary Vite entry under the capturer's scratch directory with filesystem access to the checkout can import these components; no tracked file or production RPC change is needed. Use the browser/test workflow the lead has authorized. Mark every frame “COMPONENT HARNESS — source-derived approval props — Wxx — main/head”; it proves text layout, not an executed adapter or durable persistence path. The main and head card source are identical, but the two props objects differ.

W11 must use the real app and actual pending approval; a direct-props component harness does not prove refresh, resolution, or history behavior. If a capturer elects to build a mock app-server driver instead, label that evidence “PROTOCOL HARNESS,” preserve the request/turn-item transcript, and do not call it real Codex. Every run gets a new timestamped scratch directory; keep raw evidence until uploaded.

## Exhaustive render-path inventory and proportional exclusions

The changed TypeScript module is an adapter, not a component. Its affected inputs and all callers are:

| Function / branch | Callers and values | Coverage decision |
|---|---|---|
| `buildApprovalRequestArtifacts` | Six call sites: commandExecution/requestApproval; fileChange/requestApproval; permissions/requestApproval; MCP server elicitation; legacy execCommandApproval; legacy applyPatchApproval | Only permissions supplies new `reason`; other callers omit it and retain output. New helper extraction also serves both patch callers, with identical order/limit behavior. Existing focused patch tests cover them; no new patch screenshot required. |
| `joinApprovalLines` | zero entries → undefined; 1–20 → join; >20 → first20 plus suffix | W06/W07; W01–W05/W08/W10; W09. Empty file-change arrays fall back to grantRoot as before. |
| `codexPermissionsApprovalPrompt` | network only when enabled===true; filesystem absent/null/empty equivalent; entries then legacy write then read; Set dedup; no sorting | W01/W02/W03/W06/W07; W03 covers all access modes and dedup, W08/W09 count unique summary entries. Null/false/absent network share the no-network shape; no extra screenshots. |
| `codexFileSystemPathLabel` | literal path; glob; six special kinds, including both optional-subpath branches for project_roots and unknown | Every branch is in W04. Null/empty/absent subpath has the same label. Access mode is a literal prefix, not a UI badge. `globScanMaxDepth` is not displayed; it introduces no render branch. |
| reason trimming/fallback | nonblank different reason; null/absent/empty/whitespace reason; reason equals prompt.trim; summary absent with reason; both absent | W01/W02/W10; W03/W04/W09; W05; W06; W07. With no summary, a whitespace-only reason remains a truthy whitespace prompt (pre-existing blank detail), so it is an unchanged control, not evidence of a new empty-state fix. |

Client rendering and interactions:

| Surface / caller | Every layout or conditional path relevant to inspection | Decision |
|---|---|---|
| Shared mapping | `derivePendingThreadRequests` finds last matching approval item for each pending runtime request; truthy prompt → detail, truthy reason → reason; resolved requests excluded; live versus not_resumable | New fields pass through unchanged. W01–W10 cover pending data; W11 covers removal and client rehydration. Missing approval item/empty fields, not_resumable, and resolved-only projection are unchanged controls. |
| Web pending panel: sole JSX caller `ChatComposer` → `ChatView` | `approval`, `pendingCount`, optional className. First pending request is selected; web sorts by createdAt. Count >1 shows 1/N. `Detail` is span/sans/max-h-20 for MCP, otherwise code/mono/max-h-40. Detail fallback depends on kind. not_resumable overrides detail with provider-gone message; reason still renders if present. appName truncates if present; fullAccessOverride exposes hover/click popover. detail is focusable and scrollable, with focus ring. Reason is an uncapped muted span with word wrapping. | W01/W02/W03 cover command/read/change kind headings. New permissions artifacts have no appName, fullAccessOverride, custom options, or supportsFeedback, so those branches cannot be caused by this change. Generic permission/MCP branches belong to other request kinds and need no new row. Count, focus, disabled/error, provider-gone controls are unchanged; no Cartesian product. Scroll is recording in W08/W09/W10. |
| Web composer wrapper | Warning surface, spacious density, approval grid; container width <=560 moves actions below content; normal/dark/custom theme tokens; typing guard can suppress arrival temporarily; no pending approval removes panel or gives precedence to user input. | D and N cover both action-placement branches; W02 light covers newly visible reason contrast. Custom palettes, sidebar arrangements, split pane, desktop Electron, hosted/remote/relay/tunnel reuse this markup and wire fields; no new transport/client branch. Typing guard timing, user-input precedence, and composer collapse are unchanged controls. |
| Web actions: sole caller adjacent to pending panel | Defaults: Approve/Decline primary, Cancel/session in More menu. Custom options can alter count, warnings/tooltips, labels, menu presence. disabled for connection permission/responding; primary also disabled if provider not live. Response dispatch, menu open/close, optional warning hover. Errors are reported through thread error UI, outside panel. | New permissions handler supplies default options and no warnings. Menu and Decline recording in W11 is representative only. Loading/inflight, failed RPC, read-only connection, stale provider, warning/custom-label/empty-option states are unchanged and do not need duplicate new-data rows. No new spinner/error branch in the panel. |
| Web expanded history: `TimelineWorkEntryRow` → `V2ItemInspector` | Collapsed versus expanded; `turnItemHasDetail` tests prompt.trim; approval branch passes prompt into `StructuredValue` (max-h-80 pre/overflow). Empty prompt means no expandable approval detail. Plain approval detail has no syntax-highlighting/loading branch of its own. History fetch support infrastructure is unchanged. | W11 recording covers expansion and changed history text. It must reveal the missing historical reason rather than claiming both fields render here. No separate settings, palette, keybinding or MCP approval-card entry point found. |
| Mobile pending: sole caller `ThreadDetailScreen`; requests from `useSelectedThreadRequests` and shared pendingRequestsAtom | Approval container conditional; native enter/exit animation; title appName ?? requestKind; optional detail (sans), reason (warning color); fullAccessOverride expand/collapse; provider-gone message; first option warning; option-button wrapping; canOperate/responding/live disable buttons; read-only explanation. No detail max-height. `onRespond` receives request id/decision. | No new native branch or mapping. M01 optional because this pass's requested matrix is web; no native regression-free claim. A long native-card height issue would require native verification if reported or if broader native visual coverage is claimed. Native animation/action timing would require recording, not stills. |
| Mobile historical activity | `threadActivity.itemPreview`/`toolItemForDisplay` use prompt; collapsed work log preview versus expanded `thread-work-log`; expanded detail formats the projected item as JSON with a max-h-60 ScrollView. | Data reaches it; no new native capture minimum for unchanged history renderer. Do not imply it has web's dedicated reason line. |

No test suite, browser, dev server, emulator, or lease was started by this audit. No worktree changes, pushes, comments, PR creation, or deletion of the lead's checkout. Only this step's scratch artifacts were created; public matrix/fixture uploads survive scratch cleanup. There was more than 5 GB free throughout checked points (19 GB initially, 18 GB later).

FRICTION: none

VERDICT: SUFFICIENT
