Run each commit in a separate worktree with `vp i`, then `T3CODE_DEV_SEED=0 vp run dev`. Use separate OpenCode config/data/state/cache directories, and OpenCode 2.3.0 authenticated to an available Haiku 4.5 model. The driver pins `anthropic/claude-haiku-4-5-20251001`.

Use seed-permission-proof.py to create one synthetic git repository per side. Add each through `node apps/server/src/bin.ts project add <workspace> --title "Permission proof" --base-dir <isolated T3 home>`. Pair the browser with that app, then evaluate permission-proof-driver.js in its page and call `await installPermissionProof(projectId)`.

For each case, call `await permissionProof.prepare(kind,id)`, navigate to the returned app path, close the thread details panel, start recording, then call `await permissionProof.start(kind,id)`. This drives the shipped client command API through the real WebSocket server and real OpenCode provider. There is no mocked tool or permission response. Use a new thread for each case.

Cases, on both commits:
- allow: Auto, `printf native-allow-proof`. Base waits for approval; head executes. Decline the base request after recording.
- deny: Full access, `rm -f deny-proof.txt nonexistent-disposable-path.txt`. Start with deny-proof.txt present on both sides. Base deletes it; head refuses with Permission denied: shell and leaves it present.
- ask: Auto, `git push --dry-run`. Both ask; decline after recording.

Head controls:
- fullAsk: Full access, `git push --dry-run`: executes without approval and Git reports no configured push destination. No remote is configured.
- defaults: in a second repository without opencode.json, Auto asks for `printf native-allow-proof`. Decline after recording.
- autoDeny: Auto denies the same rm command and keeps deny-proof.txt.
- exception: append shell permission `"rm -f safe-proof.txt": "allow"` after `"rm *": "deny"`; create safe-proof.txt, restart the isolated app so OpenCode loads the new config, then run Full access `rm -f safe-proof.txt` in a new thread.

Capture environment: Linux web client, Chromium, light theme, 1280x800 CSS viewport (2560x1600 screenshots). Uploaded images and recordings crop only the 256px left sidebar; recordings are resized to 1024x800 and shortened to the first 12 seconds without changing playback speed. The ordered-exception recording also crops the right details panel, is 720x800, and retains 24 seconds. Full access stills were captured after two identical snapshots, requested 500ms apart. Interaction evidence is video because live approval state includes changing activity indicators.
