# Final-revision server proof matrix — [#21092](https://git.cafe/pingdotgg/t3code/pulls/21092)

Scope: refreshed runtime evidence; original proof obligations preserved for [#21092](https://git.cafe/pingdotgg/t3code/pulls/21092) ([GitHub mirror](https://github.com/pingdotgg/t3code/pull/21092)). Original: [AnobleSCM/t3code](https://github.com/AnobleSCM/t3code), branch `fix/15542-retry-locked-event-write`. Replacement: `takeover/pr-21092`.

Before: main [3cc31ed27c](https://github.com/pingdotgg/t3code/commit/3cc31ed27c8956b36cee35a50509fb7c6869592b). After: [83ef5cf0b6](https://github.com/pingdotgg/t3code/commit/83ef5cf0b67c915b0db262e8089c84c6eb71a669). Verified local HEAD and origin/main match these full SHAs; clean worktree. Inspected `git diff origin/main HEAD` in `/home/ubuntu/wt/pr-21092`. This refresh executes the unchanged public 112-case driver with only revision and scratch-path substitutions. No code changes, commits, pushes, comments, watches, UI or browser work. The original PASS1 coverage notes below describe source inspection, not refreshed runtime results; the refreshed index and per-row transcripts supply those results.

Spec: retry only EventSink errors whose direct cause is a SqlError with reason LockTimeoutError. Maximum two retries, three total attempts per affected sink write. No explicit retry delay; each attempt can incur SQLite busy_timeout. Non-lock errors fail on their first occurrence, including after an earlier retryable error.

## Evidence contract and fixture protocol

SERVER ONLY. There are no changed render paths. Every interaction/time row below requires a short before/after **runtime transcript (server exception)** instead of UI screenshots or recordings. Evidence is focused tests plus real runtime contention. Injected fault tests alone cannot establish SQLite error classification or actual contention recovery.

Capture agent: use isolated file-backed SQLite databases and the real server persistence, EventSink, projection stores, and ProviderEventIngestor at each pinned revision. Seed through valid domain events and sink writes before contention, using the fixture shapes in ProviderEventIngestor.test.ts. Do not use `:memory:`: independent connections cannot share it. Do not use live T3 state. All temporary harnesses, databases and transcripts stay under `/tmp/ev-21092-capture-final`; no fixture scripts are committed. No browser required.

Use a separate OS process with a second SQLite connection to the SAME canonical database path. Acquire `BEGIN IMMEDIATE`, acknowledge lock acquisition, then invoke the selected ingestion. Production busy_timeout is 5000 ms. A lock shorter than that proves only SQLite's existing wait, not this change. For recovery, release after at least one actual failed BEGIN/LockTimeoutError and before the three-attempt budget expires. For a third-attempt proof, retain through two failures, then release. The lock holder must release independently of the Node process because synchronous node:sqlite waits block that process's timers. Coordinate on IPC/observed milestones, not guessed same-process timers. Keep production timeout for the representative before/after; any shortened-timeout supporting cases must disclose their value and connection.

For every transcript include revision, fixture/route, database identity, effective busy_timeout, holder acquired/released timestamps, ingestion start/end, observed sink-attempt count and error reason chain, returned event IDs/types, persisted event count and projected state. Read-only tracing/decorators may observe real calls but must not fabricate errors, release locks early, or replace EventSink persistence. Count the targeted write separately from fixture setup and secondary writes. Observe persisted events/Deferred milestones; do not poll or sleep for test completion. Query the event log as well as projections to expose duplicate persistence. Subscribe before ingestion if claiming exactly-once publication.

## Minimum affected fixture matrix

All rows marked MIN are minimum proof obligations. Grouped rows explicitly require each listed route; a plain-write result does not cover another route. Automated status below describes inspected source, not tests executed in PASS1.

| ID / minimum | Branch / real SQLite seed | Before main [3cc31ed27c](https://github.com/pingdotgg/t3code/commit/3cc31ed27c8956b36cee35a50509fb7c6869592b) expected | After head [83ef5cf0b6](https://github.com/pingdotgg/t3code/commit/83ef5cf0b67c915b0db262e8089c84c6eb71a669) expected | Evidence / existing automated coverage |
|---|---|---|---|---|
| P / MIN | Plain `write`: seed valid thread/provider session; ingest nonempty `provider_turn.updated`, with neither guard. Hold second-connection lock through first timeout, then release. | One sink attempt; ProviderEventPublishError caused by EventSinkWriteError → SqlError → LockTimeoutError; target event absent. | Retry succeeds within 3 attempts; exactly one target event/projection update. | Runtime transcript (server exception), paired before/after. Existing injected test queues two locks then succeeds on plain write; no real lock. |
| R+ / MIN | `writeIfRunCurrent`: seed thread plus a run whose status equals expectedStatus and payload.activeAttemptId equals supplied attempt; ingest nonempty provider event. Brief real lock as P. | Lock failure; target absent. | Guard matches after retry; one target event persists. | Runtime transcript (server exception), paired. Existing injected guarded test has NO stored matching run and cannot prove this commit path. |
| R0 / MIN | Same run guard with missing run, wrong status, and wrong activeAttemptId as separate seed variants. Also hold lock while the second connection changes a previously matching run to a newer attempt/status and commits before release. | Under timed-out lock, fails before checking guard. Unlocked control is successful no-op. | After retry checks current stored state: returns no events, persists/publishes none; never revives stale attempt. | Runtime transcript (server exception) for contention/change case; focused tests for each predicate. Existing injected test covers missing run only. |
| O+ / MIN | `writeIfProviderThreadOwner`: seed provider-thread row and run in SAME thread; matching run payload.activeAttemptId and provider-thread last_run_ordinal. Supply matching providerThreadId/runId/attempt/ordinal. Prefer a late `provider_thread.updated` event. Brief real lock. | Lock failure; target absent. | One guarded target update after retry. | Runtime transcript (server exception), paired. Existing injected owner test has no joined run/owner, proving no positive commit. |
| O0 / MIN | Owner guard: missing joined row, mismatched attempt, mismatched last_run_ordinal separately. During contention also let holder supersede ownership/ordinal before commit/release. | Timed-out attempt fails; unlocked controls no-op. | Retry rechecks ownership; returns/persists/publishes nothing for stale owner. | Runtime transcript (server exception) for ownership change; focused predicate tests. Existing injected test covers missing joined row only. |
| S+ / MIN | Secondary model sync: seed parent and child threads using existing late-model fixture; child starts with inherited model/options. Ingest provider_native `subagent.updated` with non-null childThreadId and a differing reported model/modelSelection. Acquire real second-connection lock AFTER primary subagent event commits but BEFORE `thread.model-selection-updated` sink write (coordinate observation barrier). | Primary subagent event remains committed; secondary write times out and ingestion fails with ProviderEventPublishError; child model stays old. | Secondary retries succeed; exactly one model-selection event; child has reported selection. Primary event must not be duplicated. | Runtime transcript (server exception), paired. Existing injected test targets model-selection events, queues two locks, and checks later selection/options and repeat behavior; no real lock. |
| S0 / MIN | Same real parent/child SQLite seed; exercise non-provider_native origin, null child ID, no model/selection, missing child row, already-equal selection, and equal selection on locked recheck. | No secondary model write. | Same; no new model-selection event. Repeating successful S+ also adds none. | Focused branch tests; runtime transcript for repeat/recheck interaction. Existing late-model test checks repeats and explicit options; other skip branches not established by the added tests. |
| L / MIN, each P/R+/O+/S+ | Hold real second-connection lock beyond all allowed attempts on each targeted write. For S+, acquire only at secondary barrier. Seed matching guards so guard rejection cannot hide failure. | First attempt fails. | Exactly three targeted attempts, then ProviderEventPublishError; no fourth attempt and no targeted event/projection/publication. S+ retains its already-committed primary event. | Runtime transcript (server exception), elapsed time and counts, paired. Existing injected permanent-lock case covers plain only, with three queued failures and a succeeding hypothetical fourth write. |
| E / MIN, each P/R+/O+/S+ | Seed valid route as above; on isolated real SQLite add a temporary BEFORE INSERT trigger on the target event-store table using `RAISE(ABORT, 'proof non-lock constraint')`, scoped to target event/type. Verify real resulting classification is non-LockTimeoutError. No competing lock. For S+ trigger only the model event. | One targeted attempt, error, no target persistence. | Same one attempt; never retry unrelated SQL errors. S+ primary persists. | Runtime transcript (server exception); focused tests must also cover non-SQL/absent-cause errors without pretending they are real SQLite faults. Existing injected ConstraintError test covers plain only. |
| M / MIN, each P/R+/O+/S+ | Combine real lock then non-lock trigger: create scoped trigger before holder locks; first attempt times out, release holder, second attempt reaches trigger and fails. | Stops at first lock error. | Stops immediately on second attempt's non-lock error; never makes third attempt; no target persistence. | Runtime transcript (server exception), paired, plus deterministic injected sequence test. Not covered by added tests. |
| C / MIN | Unlocked controls for all four sites; empty normalized event (`command_output.delta`); both guards present with owner matching/run mismatching, then inverse. Seed matching/mismatching real rows as above. | Baseline persists once on eligible paths; empty returns []; owner guard has priority. | Same; retries introduce no change absent lock errors and do not fall through to run/plain paths after owner no-op. | Focused tests; short runtime control transcript. Existing added tests do not establish both-guard precedence or empty path. |

## Boundaries and interpretation

Changed write sites are ProviderEventIngestor.ts:446 (secondary model), :753-763 (owner), :766-773 (plain), :775-783 (run). Retry helper is :71-78. Error mapping remains ProviderEventPublishError, including secondary failures. EventSink transactions use BEGIN IMMEDIATE via nodeSqliteClient; EventSink publishes after transaction commit. Actual error wrapping/classification and no duplicate commit/publication still require real runtime proof.

The post-`app_thread.created` worktree-branch metadata recheck at ProviderEventIngestor.ts:813 is an existing separate un-retried write; failures are warning-only. It is outside the four changed sites. Do not claim every ingestor write now retries. If that path is exercised as a control, retain its baseline behavior; it is not a fifth retry obligation for this diff.

Pending-user-input cancellation guarding inside the three primary sink routes remains transactional and is not changed. Representative multi-event input may verify a single retry repeats the batch atomically rather than duplicating each event. No separate browser/provider-CLI matrix is needed: these are shared normalized-event server paths, independent of UI, transport mode, and provider adapter. Do not claim live provider end-to-end verification from service-level fixtures.

Current automated evidence inspected: in-memory SQLite plus injected EventSinkWriteError values; two failures then recovery for plain/missing-run/missing-owner and secondary model, three failures then exhaustion for plain, one constraint failure then stop for plain. These are useful deterministic fault tests, not a real second-connection lock transcript, positive guarded-commit proof, or a test-run result. Capture agent should run the focused ProviderEventIngestor.test.ts suite at head and report its command/result separately from runtime proof. Run any extra proof harness only from temporary material; do not commit fixtures.

## FRICTION category|problem|prevention

EVIDENCE|Injected errors in in-memory SQLite can be mistaken for real lock recovery.|Label injected tests separately; use a file database and independent lock-holder process for runtime proof.
GUARDS|Added guarded tests use nonexistent runs and commit no event.|Seed matching run/owner rows and separately demonstrate stale guard rejection after release.
TIMING|A sub-5000ms lock never reaches the new retries; same-process timers cannot reliably release a synchronous SQLite wait.|Record busy_timeout and real first failure; release from another process using milestones.
SECONDARY|Locking before subagent ingestion blocks the primary write and misses model-sync retry.|Acquire contention at an observed barrier after primary commit and before secondary write.
ERRORS|Plain-only exhaustion/constraint tests do not establish every changed site's limits.|Parameterize all four write sites; include lock-then-unrelated-error sequence and exact attempt counts.
ATOMICITY|Projection success alone can conceal duplicate log entries or publication.|Record target event IDs, log counts, projected state, and subscribed publication counts when claimed.
SCOPE|Generic UI evidence rules or old brief could cause browser capture or committed fixture scripts.|Use the explicit server transcript exception; temporary files only; no UI captures or fixture commits.

## Final binding

All 11 runtime matrix groups and 20 route-expanded obligations bind before to `3cc31ed27c8956b36cee35a50509fb7c6869592b` (current origin/main and merge-base) and after to `83ef5cf0b67c915b0db262e8089c84c6eb71a669` (final HEAD). Every ingestion-start trace records its full source revision. Controls and case definitions are unchanged. Subscribers observe initial ingestion only; repeat calls assert the durable log and model selection, not repeat-call publication. Prior findings/resolutions: none blocking.
