# Proof audit, pass 1: runtime fixture matrix

PR [#29535](https://git.cafe/pingdotgg/t3code/pull/29535), branch `fix/issue-29513`.
Before: [9bcf75335f](https://git.cafe/pingdotgg/t3code/commit/9bcf75335f9b2a8af877e1317c809b72a070b280).
After: [f23933f39b](https://git.cafe/pingdotgg/t3code/commit/f23933f39b4f1635dac2d82c2418fa44bcd89fa7).
Inspected in `/home/ubuntu/wt/pr-29535`, read-only. Pass 1 derives expected evidence; it does not certify captures or test executions. No server, browser, provider turn, or tests were launched.

## Findings first

1. **Evidence is insufficient at these exact revisions.** The PR body quotes older before/after commits, different prompt texts, request-only snippets, and no attached driver/raw logs. Those are useful leads, but are not the paired evidence specified here. In particular, request order alone does not show what the first skill read returned or when the model response completed.
2. **There is no changed UI component or render path.** Production code is now [packages/provider-opencode/src/server/v2/adapter.ts](https://git.cafe/pingdotgg/t3code/blob/f23933f39b4f1635dac2d82c2418fa44bcd89fa7/packages/provider-opencode/src/server/v2/adapter.ts#L3299), rather than the production path in the brief. Its [test](https://git.cafe/pingdotgg/t3code/blob/f23933f39b4f1635dac2d82c2418fa44bcd89fa7/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts#L2256) remains under the server app. Viewport, theme, width, layout, and client platform axes are N/A. Evidence is a chronological runtime recording, not a screenshot.
3. **Failure coverage is narrower than the description's general fallback claims.** The two added tests cover first-read empty -> reread found, and first-read found -> reread empty. They do not cover skill-read errors/timeouts or a failed/timed-out model barrier in this submit path. The existing `ends a turn before re-reading a model list that never answers` test exercises a post-turn refresh, not the new pre-prompt barrier.
4. **The barrier is best effort.** `readModels` logs/ignores errors and has its own five-second timeout; both skill reads separately have five-second timeouts and silently become empty sets on error. Thus model failure/timeout still leads to the second skill read and then a prompt with the IDs known so far. The model barrier's five seconds is not an overall five-second bound for the entire resolution sequence. The patch does not guarantee that a missing skill is recovered after an unsuccessful barrier.
5. **The changed path is fresh-turn submission only.** Active steering posts a prompt directly with `delivery: "steer"` and no skill resolution, unchanged by this diff. Native commands, compaction, and provider continuations also bypass the changed resolver. These are scope boundaries, not newly introduced regressions.

## Branches and callers

- The outer adapter is opened through the provider session manager. `ProviderSessionManager` invokes `runtime.startTurn`; `startTurn` calls the local `submit` exactly once for a normal turn. Queued and restart deliveries eventually use this start path. `compactThread` calls `startTurn` with `/compact`, which exits before mention parsing. Continuations exit through `runWake` before `submit`.
- `submit` trims the original message text. With no attachments, exact `/compact` uses `session.compact`; a recognized leading slash command uses `session.command`. An unknown command, a failed command-list read, or any attachments falls through to ordinary prompting.
- `skillMentions` deduplicates capture group 2 of the unchanged shared skill regex, preserving mention order. Zero matches skips inventory calls. The pattern recognizes currency-symbol mentions followed by whitespace/end; numeric currency and an embedded `foo$bar` do not become skill mentions. Math handling in UI token decoration is not used here. The resolver matches skill **IDs**, not display names, and only examines the original message text, not extracted attachments or appended context.
- `skillsMentioned` is called only from `submit`, only for a nonempty mention list. It calls `listSkills`; if **any** unique mentioned ID is missing, it calls `readModels(directory)`, then `listSkills` again and unions both ID sets. It filters original mention order against that union. It does not replace the first result or retry until every name exists.
- `listSkills` is local to this resolver. Both reads convert errors/timeouts to an empty set. `readModels` is also used during session initialization, `readModelsOnce` in ensure/resume, and post-turn inventory refresh. Only its new call from this resolver changes here. An empty successful model response still proceeds to the reread.
- After resolution, attachment/text preparation is unchanged. The final `sending()` guard suppresses a stopped/replaced turn. Nonempty skills produce `skills: [{ id }]`; empty skills omit that property at the adapter level (the SDK may serialize an absent optional value as `null`). `steerTurn` has its own direct `session.prompt` call and does not call `submit`.

Relevant callers: [session manager](https://git.cafe/pingdotgg/t3code/blob/f23933f39b4f1635dac2d82c2418fa44bcd89fa7/apps/server/src/orchestration-v2/ProviderSessionManager.ts#L1862), [start/submit](https://git.cafe/pingdotgg/t3code/blob/f23933f39b4f1635dac2d82c2418fa44bcd89fa7/packages/provider-opencode/src/server/v2/adapter.ts#L3517), [steer](https://git.cafe/pingdotgg/t3code/blob/f23933f39b4f1635dac2d82c2418fa44bcd89fa7/packages/provider-opencode/src/server/v2/adapter.ts#L3671), [model read](https://git.cafe/pingdotgg/t3code/blob/f23933f39b4f1635dac2d82c2418fa44bcd89fa7/packages/provider-opencode/src/server/v2/adapter.ts#L2717), [mention pattern](https://git.cafe/pingdotgg/t3code/blob/f23933f39b4f1635dac2d82c2418fa44bcd89fa7/packages/shared/src/composerInlineTokens.ts#L26).

## Real-app seeding and recording contract

Use an isolated T3 home and disposable git project, never the live database. Record the exact T3 commit, OpenCode version (target 2.2.5 to match the claim), selected available model, directory, run ID, and whether HTTP fault injection is enabled. A real model call is needed only if completion is part of the evidence; the outgoing prompt already proves skill attachment. Do not substitute a mocked provider for the native scan-race rows.

Seed `<project>/.opencode/skills/greet/SKILL.md` with:

```markdown
---
name: greet
description: A greeting fixture for the proof audit.
---
Reply with exactly HELLO_FROM_GREET.
```

For two-skill fixtures add `other/SKILL.md` with matching `name: other`; use short, unique descriptions. Use the actual `id` returned by OpenCode if it differs from the folder/name. Ensure no project/global skill ID equals `HOME` for the unknown-name fixtures. A cold partial scan may need many harmless extra skills, as in the PR's probes; record the native first response to establish that the intended subset occurred.

Connect T3's OpenCode provider to a logging proxy through its external **Server URL** setting; forward to a real `opencode serve`. Keep OpenCode's persistent session storage across restarts. Log request start, response end/status, location query, relevant skill IDs from response bodies, prompt text/skills, and connection/request errors. Never publish authorization headers, passwords, environment files, hub credentials, or unrelated user data. A proxy that changes a response must explicitly label that segment `HARNESS / injected`, not `native OpenCode`.

Create the project through `projects.mutate` (`project.create`, `commandId`, `projectId`, `title`, `workspaceRoot`). Create a thread through `orchestration.dispatchCommand` with `type: "thread.create"`, IDs, `projectId`, `title`, a valid OpenCode `modelSelection`, `runtimeMode: "full-access"`, `interactionMode: "default"`, and null `branch`/`worktreePath` when using the project root. Send through that same RPC using `type: "message.dispatch"`, IDs, `text`, `attachments: []`, and `dispatchMode: { type: "start_immediately" }`. RPC command acceptance is not provider completion; observe persisted/provider events or the outgoing prompt. Use the actual contract types in a driver; these are payload outlines, not JSON-RPC envelope examples. [Command schemas](https://git.cafe/pingdotgg/t3code/blob/f23933f39b4f1635dac2d82c2418fa44bcd89fa7/packages/contracts/src/orchestrationV2.ts#L2906).

For cold-existing-thread fixtures: complete setup/one warm turn, keep the T3 server/provider runtime and thread alive, then stop only the OpenCode process whose PID the driver captured and restart it on the same port/storage. Do not refresh provider models or rebuild/reopen the T3 provider runtime before the next send: its initialization model read can warm the directory and hide the defect. The adapter may wait for its event-stream reconnect before sending; record that and establish coldness from the first **response**, rather than assuming restart guarantees an empty list. If reconnect/model/background traffic already warmed it, that run is not the cold fixture. Idle location eviction can be an alternative only when the transcript independently proves coldness; no unspecified eviction timeout is assumed here.

Write each capture into a new timestamped directory beneath the authorized scratch directory. Keep a complete turn window and annotate excerpts with real line numbers, e.g. `before.ndjson:L21-L35`. Pass 1 has no such files; all line references below are **required line roles**, never invented observed line numbers.

Notation: `S1` / `S2` = first/second `GET /api/skill?location[directory]=D` with response IDs; `M` = `GET /api/model?location[directory]=D` plus response/end or timeout; `P(ids)` = `POST /api/session/<id>/prompt` whose `skills` array contains those IDs; `P(-)` = absent/null skills, not an unresolved ID. Calls from setup or post-turn refresh must be labelled so they are not counted as resolution calls.

## Fixture matrix

Every row is a **recording**: timestamped HTTP/RPC/runtime transcript or labelled harness transcript. No still is sufficient. All rows have viewport = N/A and theme = N/A. Status `PENDING` means not captured/audited at the pinned revisions. `SOURCE ONLY` means an unchanged boundary has been inspected and is not in the minimum capture set. `TEST SOURCE` means a matching test exists but was not executed in this pass.

| ID / named fixture and data | Diff effect | Reachability / seed | Required before lines | Required after lines | Minimum / status |
|---|---|---|---|---|---|
| R01 No parsed mentions: `hello`; optional parser controls `cost $10` and `foo$greet` | Parser refactor; expected wire unchanged | Native app, warm existing thread, no attachments | `P(-)`, no turn-resolution S/M | Same | Control; PENDING recording |
| R02 All IDs found on first read, warm: `$greet $other $greet` | Refactor only; no extra reads or payload change | Native app, two seeded skills, already scanned directory | `S1=[greet,other]` then `P(greet,other)` once each | Same; no resolution M/S2 | Required; PENDING recording |
| R03 Reporter/new-directory thread: immediate `$greet` | Expected unchanged because initialization warms directory | Native app, newly seeded git project/worktree and a newly created T3 thread; send immediately | Initialization `M` **completion** before `S1` containing greet, then `P(greet)` | Same; no fallback M/S2 if S1 contains greet | Required; PENDING recording |
| R04 Cold existing thread, first read empty: `$greet again please` | Primary fixed branch | Native app with restart sequence above; require actual `S1=[]`. Existing test T1 also models it, but cannot prove native scan/barrier | `S1=[]` then `P(-)`; later independent native scan evidence may establish greet exists | `S1=[]`, `M` returns, `S2` contains greet, `P(greet)` | Required native pair + T1 red/green; PENDING / TEST SOURCE |
| R05 Cold existing thread, first read partial: `$greet $other` | Fixed branch plus union/order behavior | Native app candidate: two skills plus scan load, restart; accept only run whose S1 contains one requested ID. Native timing not guaranteed. Otherwise label as harness; no exact existing test | `S1=[greet]`, `P(greet)` | `S1=[greet]`, `M`, S2 contains other, `P(greet,other)` | Separate native pair if claiming partial-scan reproduction; otherwise covered semantically by R04 + R08, PENDING |
| R06 Unknown word only: `echo $HOME` | New read cost; payload remains unresolved literal | Native app, warm directory, verify HOME absent from actual inventory | `S1` excludes HOME, `P(-)` | `S1` excludes HOME, `M`, `S2` excludes HOME, `P(-)`; response completion timestamps show added cost | Required; PENDING recording |
| R07 Found + permanently unknown: `$greet then echo $HOME` | New reads, same known skill attached | Native app, warm seeded greet, no HOME skill | `S1` has greet, `P(greet)` | `S1` has greet, `M`, `S2` has greet, `P(greet)` and no HOME ID | Can share capture run with R06; required mixed-name control; PENDING recording |
| R08 Reread empty, retains first: `$greet then echo $HOME`; S1 greet, S2 empty | Union prevents dropping first-read skill | Only deterministic replay/harness established here; T2 is the exact existing unit test. A naturally empty native reread is not demonstrated by this patch | `S1=[greet]`, `P(greet)` | `S1=[greet]`, `M`, `S2=[]`, `P(greet)` | Required labelled T2 harness/test recording; TEST SOURCE |
| R09 Reread errors, retains first: same prompt; S2 HTTP/transport failure | New failure branch; union should retain greet | Labelled fault proxy through real T3 + real OpenCode; fail only S2. Not claimed naturally seedable. No existing exact test | `S1=[greet]`, `P(greet)`; S2 fault rule remains unused | `S1=[greet]`, `M`, S2 error, `P(greet)` | Required error harness; PENDING; test gap |
| R10 Reread times out, retains first: same prompt; S2 held open | Same merge fallback, distinct timed path | Labelled gated proxy; hold only S2 through its inventory timeout. No exact existing test | `S1=[greet]`, `P(greet)`; hold rule unused | `S1=[greet]`, `M`, S2 start/cancel or timeout, `P(greet)` afterward | Required timeout harness; PENDING; test gap |
| R11 First read fails, recovery works: `$greet`; S1 error | New recovery opportunity; main sends without skills | Labelled proxy: fail only first S. A real network failure is possible but not a deterministic seed. No exact existing test | S1 error, `P(-)` | S1 error, `M`, `S2=[greet]`, `P(greet)` | Required first-read error harness; PENDING; test gap |
| R12 First read times out, recovery works: `$greet`; S1 held open | Same recovery fallback, distinct timed path | Labelled proxy: gate only S1 until its inventory timeout, allow M/S2. No exact existing test | S1 timeout, `P(-)` | S1 timeout, `M`, `S2=[greet]`, `P(greet)` | Required first-read timeout harness; PENDING; test gap |
| R13 Model barrier fails: `$greet then echo $HOME`; first read greet, reread empty | New model-error path; best effort still rereads and retains first | Labelled proxy: fail the **resolution** M, not startup M. S2 empty may be injected separately and labelled. No exact existing test | `S1=[greet]`, `P(greet)`; no barrier request | `S1=[greet]`, M error and T3 logged cause, `S2=[]`, `P(greet)`; no hard turn failure from M alone | Required model-error harness; PENDING; test gap |
| R14 Model barrier times out: `$greet`; S1 empty | New bounded wait; missing ID can remain missing | Labelled proxy: hold resolution M through its five-second timeout; run **two variants** S2 found and S2 empty. Natural slow scan beyond bound is plausible, not established. No exact existing test; T3 is not this branch | `S1=[]`, `P(-)`; no barrier request | M timeout/log, **then** S2; found variant `P(greet)`, empty variant `P(-)`. This proves bounded best effort rather than guaranteed recovery | Required two timeout outcomes; PENDING; test gap |
| R15 Stop during barrier, then release it | New waiting window; existing final guard must suppress late send | Real app Stop (`thread.stop`) while resolution M is in flight; deterministic timing needs a labelled gated proxy. Release M and drain/observe turn settlement; no exact scan-stop test. T4 covers a different pre-send wait | No corresponding barrier; source-only comparison or a separately gated original S1 | S1 missing, M pending, Stop RPC/interrupt, release M, final interrupted/settled state, **no P for that attempt** through completed submit | Required head-only interaction harness; PENDING; related T4 source only |
| R16 Recognized native command with `$greet` argument | Unchanged bypass | Native app: seed a recognized `.opencode/commands/hello.md`, verify command.list contains hello, send `/hello $greet`, no attachments | command list, native `/command` request; no resolver S/M | Same | Boundary; SOURCE ONLY recording plan |
| R17 Exact `/compact`, no attachments | Unchanged bypass; compact entry point | Native app on a thread with history, or `compactThread` service; no mentions | native `/compact`, no resolver S/M | Same | Boundary; SOURCE ONLY recording plan |
| R18 Active steering: `$greet` sent while a turn is active | Unchanged separate prompt path; fix does not cover it | Native app send with `dispatchMode: { type: "steer_active", targetRunId }`; keep first turn active | `P(-)` with `delivery:"steer"`, no resolver S/M | Same | Boundary; SOURCE ONLY recording plan |
| R19 Attachment or unknown-command fallthrough | Unchanged routing into changed resolver | Native app: attach a harmless text file to `/hello $greet`, or send `/not-a-command $greet` with name absent from command.list. Reuse R02 warm/R04 missing inventories | Normal `/prompt`, expected `files` if native; skills from first read | Normal `/prompt`; same resolver behavior as R02/R04; attachment extraction does not add mentions | No new branch in changed code beyond R02/R04; SOURCE ONLY recording plan |

Model success with an empty model list collapses into the successful M rows: the resolver does not inspect model contents. Other failure combinations also collapse by the set-union rule: a failed S1 contributes nothing, and a failed/empty S2 contributes nothing; R06/R08-R14 cover the distinct resulting payloads. Zero, one, and multiple/repeated mentions are represented in R01/R03/R02; unrelated inventory skill counts and name lengths do not create another behavior once IDs are established. All-found first read must skip the new barrier even if later inventory would change.

## Minimum capture set and test mapping

- Axis product is **N/A viewport x N/A theme x row**, on one isolated server OS with the real OpenCode 2.2.5. Browser/client duplication is unnecessary: web, Electron, mobile, local, remote, and tunnel clients feed the same server resolver; no contract or transport changes occur. Other providers and OpenCode 1.x use other adapters. Older supported OpenCode 2.x versions remain outside the empirically claimed 2.2.5 barrier proof.
- Minimum native paired evidence: R01-R04, R06-R07. Keep identical fixture inputs and skill contents on base/head. R05 needs its own native pair only if claiming a native partial-first-list reproduction; otherwise the union behavior is covered by R04 plus R08 and should be labelled accordingly. Do not use a warm R02 recording as R04 proof.
- Minimum labelled failure/interaction evidence: R08-R14 (R14 both second-read outcomes), plus head-only R15. Error and timeout variants must remain separately identified. These can share a driver run/log with distinct attempt IDs; a separate process/server is not needed per row. Runtime fault-injected captures exercise the app and adapter, but cannot establish native OpenCode scan ordering. If unit replays are used instead, report their names and actual outputs; missing tests are not existing coverage. Unchanged R16-R19 are documented boundaries, not extra mandatory captures for this small patch.
- All missing-read rows must show S1 **response IDs**, M **response completion/error/timeout**, S2 response/error, and the final prompt payload for the same directory/session/attempt. Store full response IDs if needed; publish only fixture data. A line saying `GET /api/model` with a start timestamp cannot prove the scan finished before S2.
- Recovery proof does not require the model to obey the skill. The claim at this boundary is that T3 sends `skills:[{id:...}]`; an assistant's greeting alone is insufficient. If the report claims skill content was used or the turn completed, also attach OpenCode/T3 completion events and the skill-specific output.

Existing tests, all in [OpenCode2AdapterV2.test.ts](https://git.cafe/pingdotgg/t3code/blob/f23933f39b4f1635dac2d82c2418fa44bcd89fa7/apps/server/src/orchestration-v2/Adapters/OpenCode2AdapterV2.test.ts):

| Alias | Exact test name | What its source proves / limit |
|---|---|---|
| T1 | `sends a $skill that appears once OpenCode finishes scanning the directory` | R04 simulated response sequence and attached ID; capture actual red-on-base/green-on-head output. The test does not run real OpenCode or demonstrate that model.list waits for scanning. |
| T2 | `keeps a $skill found on the first read when the reread after the scan comes back empty` | R08; `$HOME` triggers retry while greet is already known. Does not exercise a failed/timed-out reread. This compatibility assertion can pass on the base's single-read behavior, so do not require it to fail on base. |
| T3 | `ends a turn before re-reading a model list that never answers` | A post-turn model refresh must not delay terminal events; **not** R14's pre-prompt barrier timeout. |
| T4 | `sends nothing for a turn stopped while extracting document text` | Existing final sending guard during attachment work; **not** a scan/model-barrier stop fixture. |

T1 does not exist in the base tree. Running its name on base and selecting zero tests is not red proof; apply the head's focused test delta to an approved disposable test checkout/harness, retaining the base production adapter. This audit does not modify the worktree or create that checkout. Preserve the exact test invocation, selected count, assertion failure, and matching green invocation. The PR body reports earlier runs, but this pass has not verified them.

## Status and scope

Covered: full pinned two-file diff; changed helpers and submit routing; initialization/model cache; start/compact/steer/stop boundaries; both added tests and nearby timeout/stop tests; RPC payload shapes; current PR body, review discussion, and head metadata. No captures or test executions were audited. No edits, pushes, comments, process restarts, or live-state writes were made.

FRICTION:
brief | The production file named in the brief has moved to packages/provider-opencode/src/server/v2/adapter.ts; the diff stat exposed the correct location. | Use the current two changed paths in the capture brief.

VERDICT: INSUFFICIENT no pinned before/after runtime recordings; no executed red/green T1 evidence; R08-R15 failure/interaction recordings pending
