import { describe, expect, it } from 'vitest'; import type { AttachConversation, ConversationSnapshot, DetachConversation, HarnessActorContext, HarnessConversationService, HarnessEventEnvelope, HarnessSelection, SendHarnessTurn, TurnReceipt, } from '@mosaicstack/types'; import { HarnessChatRuntime } from './harness-chat.runtime.js'; /** * Task Five, Step One (harness runtime). Proves the `pi-rpc` runtime executes * exclusively through the {@link HarnessConversationService} RPC boundary and * forwards the caller's exact selection tuple and idempotency key without * substitution. Red-first: the runtime is an unimplemented stub, so every * delegation assertion fails until Step Three. */ const context: HarnessActorContext = { actorId: 'actor-1', tenantId: 'tenant-1', seatId: 'seat-1', correlationId: 'corr-1', }; const selection: HarnessSelection = { harnessId: 'pi', providerId: 'anthropic', modelId: 'claude-opus-4-8', }; const conversationId = '11111111-1111-4111-8111-111111111111'; const idempotencyKey = '22222222-2222-4222-8222-222222222222'; const sendInput: SendHarnessTurn & { idempotencyKey: string } = { context, conversationId, selection, turnId: 'turn-abc', correlationId: 'corr-1', content: 'hello', idempotencyKey, }; const attachInput: AttachConversation & { afterSequence?: number } = { context, conversationId, clientId: 'client-1', selection, afterSequence: 0, }; const detachInput: DetachConversation = { context, conversationId, clientId: 'client-1', }; interface RecordedCalls { attach: (AttachConversation & { afterSequence?: number })[]; detach: DetachConversation[]; send: (SendHarnessTurn & { idempotencyKey: string })[]; subscribeFrom: { conversationId: string; afterSequence: number }[]; } const snapshot: ConversationSnapshot = { session: { conversationId, nativeSessionId: 'native-1', seatId: 'seat-1', selection, state: 'idle', attachedClientIds: ['client-1'], }, lastSequence: 0, replay: [], }; function build(): { runtime: HarnessChatRuntime; calls: RecordedCalls } { const calls: RecordedCalls = { attach: [], detach: [], send: [], subscribeFrom: [] }; const service: HarnessConversationService = { attach: (input) => { calls.attach.push(input); return Promise.resolve(snapshot); }, detach: (input) => { calls.detach.push(input); return Promise.resolve(); }, send: (input) => { calls.send.push(input); // The service echoes only the requested tuple; there is no representable substitute. const receipt: TurnReceipt = { conversationId: input.conversationId, turnId: 'turn-server', correlationId: input.correlationId, state: 'accepted', selection: input.selection, }; return Promise.resolve(receipt); }, subscribeFrom: (id, afterSequence) => { calls.subscribeFrom.push({ conversationId: id, afterSequence }); return (async function* (): AsyncIterable { return; })(); }, }; return { runtime: new HarnessChatRuntime(service), calls }; } describe('HarnessChatRuntime', () => { it('is the harness runtime kind and needs only a HarnessConversationService', () => { const { runtime } = build(); expect(runtime.kind).toBe('harness'); }); it('delegates send to the conversation service with the exact tuple and idempotency key', async () => { const { runtime, calls } = build(); const receipt = await runtime.send(sendInput); expect(calls.send).toHaveLength(1); const firstSend = calls.send[0]!; expect(firstSend).toEqual(sendInput); expect(firstSend.idempotencyKey).toBe(idempotencyKey); expect(firstSend.selection).toEqual(selection); // The runtime must not substitute an effective tuple onto the receipt. expect(receipt.selection).toEqual(selection); }); it('delegates attach to the conversation service and returns its snapshot', async () => { const { runtime, calls } = build(); const result = await runtime.attach(attachInput); expect(calls.attach).toHaveLength(1); expect(calls.attach[0]).toEqual(attachInput); expect(result).toBe(snapshot); }); it('delegates detach to the conversation service', async () => { const { runtime, calls } = build(); await runtime.detach(detachInput); expect(calls.detach).toHaveLength(1); expect(calls.detach[0]).toEqual(detachInput); }); it('delegates subscribeFrom to the conversation service journal replay', async () => { const { runtime, calls } = build(); const iterable = runtime.subscribeFrom(conversationId, 7); // Drain to prove it is the service-backed async iterable, not a fabricated one. const drained: unknown[] = []; for await (const event of iterable) { drained.push(event); } expect(drained).toHaveLength(0); expect(calls.subscribeFrom).toHaveLength(1); expect(calls.subscribeFrom[0]).toEqual({ conversationId, afterSequence: 7 }); }); });