* fix(claude): single-own turn identity so Stop reaches a provider-opened turn
Stop silently failed on any Claude turn the provider opened on its own — a
background task reporting in wakes the agent — once the session had dispatched
at least once. The transcript read "The provider had already finished this
turn." while the model kept working.
Turn identity was minted twice from the same stream by two components that
never talked. The journal translator writes turnId into the durable turn row,
which is the id every client's Stop carries. settleWaiter separately wrote
session.activeTurnId, only ever on the dispatch-echo path, and nothing cleared
it. Cancel read the adapter's copy; prompt binding, status and both clients
read the journal's. They agreed only when a send echo opened the turn.
Turn identity is now single-owned. The open turn moves out of the translator's
closure into ClaudeOpenTurn, which holds the turn and publishes its lifecycle
row, so the id readers ask for is the id the row carries. activeTurnId and
activeTurnSequence are deleted rather than widened, so the second writer goes
with them instead of a second guard being added beside the first.
activeTurnSequence was never turn identity: it asked whether a send was still
awaiting its echo, which an interrupt would release as an unexpected turn. That
is now derived from the live dispatch waiters. Deriving it also retires a latch
— a retired waiter left the stored sequence permanently behind the dispatch
sequence, refusing every later Stop for the life of the session.
Also fixes the mirror defect the same hazard caused: a stale turn id was
accepted against a newer provider-opened turn, because activeTurnId was never
cleared when a turn ended.
The Claude adapter fixture now acquires with a journal sink, as production
does; without one it modelled a session that never ships.
* fix(claude): reject stale stop after turn settles
* fix(claude): preserve dispatch cancellation fence
* test(claude): cover provider-opened stop integration
* fix(claude): derive dispatch cancellation fence from journal
* fix(claude): honor journal dispatch status before local sends
* fix(native-chat): omit absent dispatch observation
* fix(claude): release unresolved stop fence after deadline
* fix(claude): bound and poll dispatch admission wait
* test(claude): cover dispatch admission fast path