# Issue 17643 — proof audit pass 1

Scope: read-only audit of `origin/t3code/rebuild-mobile-app-swift...f8dadbc5f6` in `/home/ubuntu/wt/issue-17643`. No checkout edits, servers, browsers, simulators, native execution, or public comments. This artifact contains proposed proof, not claimed execution.

## Finding and proof boundary

The two-file diff changes `EnvironmentAPI.makeRequest` to assign an already escaped path through `URLComponents.percentEncodedPath` instead of `path`, and adds a bounded-read request assertion. `threadPath` already escapes `/ ? # %`; the old endpoint escaped its percent signs again. Literal `%3A` in a stored ID must therefore appear as `%253A` on the wire, not `%25253A`. Colons remain allowed path characters. The full read endpoint has **no `/full` suffix**.

No UI code or render implementation changed. There are no newly changed render paths to snapshot. Existing thread-detail loading, pagination, full-snapshot-backed workflows and commands consume these requests; native verification of those flows remains distinct from request proof. No native visual evidence exists from this audit. SwiftUI cannot run on this Linux host. The user's permission for a cannot-verify ios-swift PR targeting the Swift branch does not imply a native pass or authorize this sub-agent to create a PR.

No concrete implementation defect found by source inspection within this bug's scope. Proof remains incomplete. The new test uses `try?`, and its mock has no response route for the synthetic ID: it records the request then throws unsupportedURL. Its assertion can prove URL construction once executed, but cannot prove a 200, matching projection, pagination, command completion, or rendering. Tests were added but not executed because Swift/Xcode is unavailable (reported constraint, not an execution result).

## Minimum fixtures and seeding

Use one isolated disposable fixture store, never live state. For future server integration use a read-only snapshot copied into isolated state and create fixtures through supported orchestration/event paths, not hand-written projection rows. No server was started here. For Swift transport tests adapt the existing Wire fixtures in memory, consistently replacing thread IDs in projection, history, items, turns and command context; return valid data rather than suppressing errors. Keep stable identifiable recent/older messages and a stable snapshot sequence.

Three thread identities cover the distinct encodings:

- U: ordinary UUID, e.g. `17643000-0000-4000-8000-000000000001` (unchanged control).
- P: `thread:delegated-task:command%3Amcp%3Aissue-17643` (actual literal-percent regression class).
- R: `thread:delegated-task:reserved/a?b#c%tail` (combined segment separators and raw percent). Encoded segment must be `thread:delegated-task:reserved%2Fa%3Fb%23c%25tail`; no query/fragment may arise from the ID. Combining characters is enough here because they share the same escaping operation; individual per-character fixtures add no distinct branch.

For each identity seed enough stopped turns/messages to exceed bounded pagination, obtain a real nonempty beforeCursor from the bounded response, and include an old valid checkpoint outside the bounded window. Include the valid run/session/model data needed by command planning. For interrupt/stop/send-now tests use synthetic mocked active run/queue context, never a real provider. Use a fresh client/state per identity; history requires a successful prior bounded read, and repeated opening must not be satisfied only from cache or stream recovery. Seed a second identity with visibly different content to detect wrong-thread results. In an integration fixture, leave the overencoded alias absent so the negative control cannot accidentally succeed.

## Minimum request matrix

Each cell below is a separate required combination (15 cases). Capture the raw percentEncodedPath, method, decoded target identity, status/result, and identity of returned content. All rows are **still** evidence: test output or request/response artifact, not UI recordings. All Swift executions below remain UNVERIFIED.

| Path/branch | U | P | R | Required oracle |
| --- | --- | --- | --- | --- |
| B: direct bounded `threadSnapshot` | B-U | B-P | B-R | GET `/api/orchestration/threads/<segment>/bounded`; correct projection ID and recent content; usable pagination cursor |
| F: direct full `fullThreadSnapshot` (also readModel aggregation) | F-U | F-P | F-R | GET `/api/orchestration/threads/<segment>`; old checkpoint and full content present; displayed paginated state not replaced |
| H: history after B | H-U | H-P | H-R | GET `/api/orchestration/threads/<segment>/history`; cursor comes from B and survives URLQueryItem round trip; older items append, current projection/sequence retained |
| CB: projection-required non-rollback command | CB-U | CB-P | CB-R | Existing-thread `thread.turn.start` without bootstrap: bounded GET succeeds before planned RPC; RPC targets original ID and correct run/session; command acknowledged |
| CF: projection-required rollback command | CF-U | CF-P | CF-R | `checkpoint.rollback` with serverResolvedCommandContext=false: full GET precedes RPC; old checkpoint outside bounded window resolves correctly; correct ID/checkpoint acknowledged |

P segment on all five rows is `thread:delegated-task:command%253Amcp%253Aissue-17643`. U is unchanged. R segment is as specified above. Preserve `/bounded` or `/history` suffixes separately from the segment. Query and fragment assertions matter especially for H-R.

Command equivalence classes: turn-start, interrupt, session-stop, queued-run.send-now and model selection/metadata when projection is required share CB's URL construction. The three rollback aliases share CF. Do not multiply the identity matrix by every alias: one execution per distinct prefetch suffix is enough for this URL bug. Add one branch control with P and fully server-resolved rollback (checkpointId and scopeId present), or bootstrap create-and-start: **no projection GET**, unchanged JSON ID sent through WebSocket. This is an additional still row C0, UNVERIFIED. It prevents misreporting commands that bypass HTTP as repaired by this patch.

Shared authorization requires two additional still controls using P (no full Cartesian product): A1 managed-DPoP bounded request succeeds and signing receives the exact final URL; A2 managed authorization rejects once, refreshes/reloads credentials and retries with the identical percentEncodedPath. Bearer/local construction is covered by the main matrix. Both DPoP attempts call the same modified makeRequest. A1/A2 remain UNVERIFIED; no assertion that remote auth was exercised.

Static-path regression smoke S (still, UNVERIFIED): capture one authenticated request each for shell, websocket-ticket, auth/session, auth/clients, auth/clients/revoke, auth/clients/revoke-others and pull-requests/diff; require unchanged paths, methods, bodies and query behavior. These are all other callers of the changed authorized path builder, not additional ID fixtures. Discovery still uses the original unencoded endpoint overload; signed uploads use their supplied URL directly; WebSocket payload encoding and turn-item RPC are unaffected. No web/Electron/React-Native implementation changed, and no provider-specific expansion is justified for this transport bug.

## Existing external evidence supplied by the lead

Reported real-main-server evidence at commit `8d3b061121`, same isolated database and synthetic delegated ID containing literal `%3A`:

| Row | Result | Evidence type and limit |
| --- | --- | --- |
| E+ correctly escaped bounded request with `command%253Amcp%253Aissue-17643/bounded` | HTTP 200 | still; lead-reported direct RPC/request-level proof, not rerun or independently inspected here |
| E− overescaped bounded request with `command%25253Amcp%25253Aissue-17643/bounded` | HTTP 404 `thread_not_found` | still; same fixture is a useful negative control, not browser/native evidence |

This establishes the server-side distinction needed by B-P; it does not execute the changed Swift builder. It does not cover F/H/CB/CF, ordinary UUID, reserved separators, managed authorization or native UI. Do not label the entire B-P end-to-end row verified from this evidence alone.

## Native consuming-flow matrix (future verification only)

These rows describe existing render consumers, not UI changes in the diff. Reuse P plus U control; R is essential at request level but need not duplicate every native interaction after segment proof. All native rows are UNVERIFIED; no screenshots or recordings fabricated.

| Row | Fixture/action | Minimum evidence | Observable oracle |
| --- | --- | --- | --- |
| N1 | Cold open U and delegated P from thread list | recording (interaction) | tap opens correct transcript, loading ends without not-found error; identify fixture content; avoid cache/stream masking failed HTTP |
| N2 | P open then load earlier history | recording (interaction) | older identifiable turn appears, current transcript retained, loading ends |
| N3 | P action requiring bounded prefetch | recording (interaction) | action completes against intended thread; corresponding request trace links success to HTTP prefetch |
| N4 | P rewind/revert to old checkpoint requiring full read | recording (interaction) | old target available and action completes; isolated disposable workspace only |
| N5 | Settled P detail after successful open | still (static) | visible correct title/messages, no stale failure/loading state; can be a labeled frame from N1, no separate run needed |

Full-snapshot-backed recovery, subagent stopping and thread workflows share F's request shape. Their action semantics are outside this reported URL bug; N4 plus F/CF proof covers the relevant full-read distinction without claiming every workflow was exercised. Native multi-environment routing must pass the wire ID rather than a UI-composite ID; assert that in request fixtures if constructing routes. No need to multiply by every provider or connection mode; DPoP final-URL controls cover the distinct auth construction branch, while native relay/tunnel connectivity remains unverified.

## Minimum feasible evidence and disposition

Available now: read-only source audit; the added but unexecuted bounded-path assertion; lead-reported E+/E− real-server distinction. Minimum follow-up executable proof on a Swift-capable machine: 15 request cases plus C0, A1/A2 and static-path smoke, with valid success responses and explicit URL/content assertions. Native UI proof requires an actual Apple runtime and recordings N1–N4 plus static N5. Request captures or screenshots of logs do not substitute for native evidence. Cannot-verify native status is explicitly permitted by the user; retain that status on any subsequent PR rather than manufacturing evidence. No PR is created in this audit.

## FRICTION

| category | happened | avoidance |
| --- | --- | --- |
| Native tooling | Linux has no Swift/Xcode/iOS runtime; Swift tests/native UI were not executed | Run focused Swift tests and native consuming flows on an Apple host; explicitly retain cannot-verify status until then |
| Proof scope | New test records a request but suppresses a transport failure via try? | Return an ID-matching fixture and assert successful decoded content separately from raw URL construction |
| Evidence provenance | Existing real-server evidence was supplied as a result summary, not independently inspected artifacts | Preserve raw sanitized request/status logs with server SHA and fixture identity; label supplied results accurately |
| Search navigation | Initial broad search included a nonexistent State directory | Locate actual App/NativeFeatureClient callers with rg before choosing paths; no effect on audit conclusions |
