From fff0b307b0c0eb651657795eb2d83459db11916b Mon Sep 17 00:00:00 2001 From: Neil Date: Mon, 19 Oct 2026 13:30:43 -0700 Subject: [PATCH 1/2] test(codex): the stopped-send receipt guarantee lives in the journal since #30403 #28046's dispatch-boundary tests pinned that the Codex adapter drops an echo for a stopped or refused send. #30403 moved that rule to the journal's queued late-accept write. Pin it there through the host's Stop path, and keep the adapter tests asserting what still holds at the adapter: no fallback or second turn/start write, and the earlier send's receipt survives an abandoned wait. --- ...codex-structured-dispatch-boundary.test.ts | 8 ++-- ...ured-agent-session-late-settlement.test.ts | 45 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/main/codex/codex-structured-dispatch-boundary.test.ts b/src/main/codex/codex-structured-dispatch-boundary.test.ts index 7e92abc06318..f0ee43432cd7 100644 --- a/src/main/codex/codex-structured-dispatch-boundary.test.ts +++ b/src/main/codex/codex-structured-dispatch-boundary.test.ts @@ -62,8 +62,9 @@ it('stops a refused steer before its fallback write', async () => { } await expect(sending).rejects.toBe(reason) expect(connection.calls.some((call) => call.method === 'turn/start')).toBe(false) + // An exact echo is passed on; the journal keeps the stopped send rejected (late-settlement test). echoUserMessage(connection, { turnId: 'running', itemId: 'stopped', clientId: 'stopped' }) - expect(settlements).toEqual([]) + expect(settlements.map((entry) => entry.clientMessageId)).toEqual(['stopped']) await adapter.closeAll() }) @@ -89,8 +90,9 @@ it('abandons only the stopped message while waiting for a previous turn to open' await expect(stopped).rejects.toBe(reason) expect(connection.calls.filter((call) => call.method === 'turn/start')).toHaveLength(1) startTurn(connection, 'opening') - echoUserMessage(connection, { turnId: 'opening', itemId: 'ignored', clientId: 'waiting' }) - expect(settlements).toEqual([]) + echoUserMessage(connection, { turnId: 'opening', itemId: 'first', clientId: 'first' }) + // The abandoned wait leaves the earlier send's receipt intact. + expect(settlements.map((entry) => entry.clientMessageId)).toEqual(['first']) await adapter.closeAll() }) diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-late-settlement.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-late-settlement.test.ts index e6538318e2e4..4a6ed4aff8cd 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-late-settlement.test.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-late-settlement.test.ts @@ -281,6 +281,51 @@ describe('settling a send the provider proves it received after the ack window', ]) }) + it('keeps a send Stop withdrew before its write rejected when a receipt names it', async () => { + dispatch.mockImplementationOnce( + (input) => + new Promise((_resolve, reject) => { + input.signal?.addEventListener('abort', () => reject(input.signal?.reason), { + once: true + }) + }) + ) + const params = sendParams('stopped before its write') + await host.send(CALLER, params) + await handedOver(params.envelope.clientOperationId) + expect(dispatch.mock.calls[0]?.[0].signal).toBeDefined() + + const stopped = await host.cancel(CALLER, { + envelope: { + sessionId: SESSION, + clientOperationId: hostTestOperationId(), + expectedRuntimeFence: store.getRecord(SESSION)?.lease.runtimeFence ?? 1, + payloadFingerprint: computeAgentSessionPayloadFingerprint({ + method: 'agentSession.cancel', + sessionId: SESSION, + fields: {} + }) + } + }) + expect(stopped).toMatchObject({ ok: true }) + await vi.waitFor(async () => + expect(await submissions()).toMatchObject([{ dispatchState: 'rejected' }]) + ) + await host.settleLateDispatch({ + sessionId: SESSION, + clientMessageId: params.envelope.clientOperationId, + providerIdentity: { provider: 'codex', threadId: THREAD, turnId: 'turn-1', ordinal: 1 } + }) + + expect(await submissions()).toMatchObject([ + { + clientMessageId: params.envelope.clientOperationId, + dispatchState: 'rejected', + rejection: { kind: 'cancelled' } + } + ]) + }) + it('accepts from the durable echo row when the direct settlement write fails', async () => { dispatch.mockResolvedValueOnce({ state: 'admitted' }) const params = sendParams('settle from provider echo') From 8a68d51a34e97a4ae21e554a4af1bdacd2dac150 Mon Sep 17 00:00:00 2001 From: Neil Date: Mon, 19 Oct 2026 13:35:29 -0700 Subject: [PATCH 2/2] test: pin that a receipt after Stop adds no dispatch row --- src/main/codex/codex-structured-dispatch-boundary.test.ts | 3 ++- .../structured-agent-session-late-settlement.test.ts | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/codex/codex-structured-dispatch-boundary.test.ts b/src/main/codex/codex-structured-dispatch-boundary.test.ts index f0ee43432cd7..8f2d5d566c14 100644 --- a/src/main/codex/codex-structured-dispatch-boundary.test.ts +++ b/src/main/codex/codex-structured-dispatch-boundary.test.ts @@ -62,7 +62,8 @@ it('stops a refused steer before its fallback write', async () => { } await expect(sending).rejects.toBe(reason) expect(connection.calls.some((call) => call.method === 'turn/start')).toBe(false) - // An exact echo is passed on; the journal keeps the stopped send rejected (late-settlement test). + // An exact echo is passed on; the journal keeps the stopped send rejected (see + // structured-agent-session-late-settlement.test.ts). echoUserMessage(connection, { turnId: 'running', itemId: 'stopped', clientId: 'stopped' }) expect(settlements.map((entry) => entry.clientMessageId)).toEqual(['stopped']) await adapter.closeAll() diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-late-settlement.test.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-late-settlement.test.ts index 4a6ed4aff8cd..9598a533fc0b 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-late-settlement.test.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-late-settlement.test.ts @@ -281,7 +281,7 @@ describe('settling a send the provider proves it received after the ack window', ]) }) - it('keeps a send Stop withdrew before its write rejected when a receipt names it', async () => { + it('a receipt after Stop withdrew an unwritten send keeps it cancelled', async () => { dispatch.mockImplementationOnce( (input) => new Promise((_resolve, reject) => { @@ -311,12 +311,16 @@ describe('settling a send the provider proves it received after the ack window', await vi.waitFor(async () => expect(await submissions()).toMatchObject([{ dispatchState: 'rejected' }]) ) + const before = journal().cursor() await host.settleLateDispatch({ sessionId: SESSION, clientMessageId: params.envelope.clientOperationId, providerIdentity: { provider: 'codex', threadId: THREAD, turnId: 'turn-1', ordinal: 1 } }) + const after = journal().readSince(before) + expect(after.ok && after.rows.filter((row) => row.kind === 'dispatch')).toEqual([]) + expect(await submissions()).toMatchObject([ { clientMessageId: params.envelope.clientOperationId,