import { describe, expect, it } from 'vitest'; import type { HarnessActorContext, HarnessCapability, HarnessSelection } from '@mosaicstack/types'; import { HARNESS_CAPABILITIES } from '@mosaicstack/types'; import { HarnessOperationError, HarnessRegistry } from './harness.registry.js'; import { HarnessScopeViolationError, HarnessService, type TrustedGatewayScope, } from './harness.service.js'; import { FakeHarnessAdapter } from './testing/fake-harness.adapter.js'; const SCOPE: TrustedGatewayScope = { actorId: 'actor-trusted', tenantId: 'tenant-trusted', seatId: 'seat-trusted', correlationId: 'correlation-trusted', }; const READ_CONTEXT: HarnessActorContext = { actorId: SCOPE.actorId, tenantId: SCOPE.tenantId, seatId: SCOPE.seatId, correlationId: SCOPE.correlationId, }; function setup(capabilities?: readonly HarnessCapability[]) { const registry = new HarnessRegistry(); const adapter = new FakeHarnessAdapter({ id: 'fake', capabilities }); registry.register(adapter); const service = new HarnessService(registry); return { registry, adapter, service }; } async function availableSelection(adapter: FakeHarnessAdapter): Promise { const catalog = await adapter.catalog(READ_CONTEXT); const entry = catalog.models.find((model) => model.availability === 'available'); if (!entry) { throw new Error('fixture requires an available model'); } return { harnessId: entry.harnessId, providerId: entry.providerId, modelId: entry.modelId }; } describe('HarnessService', () => { it('derives the actor context from trusted scope on create', async () => { const { service, adapter } = setup(); const selection = await availableSelection(adapter); const snapshot = await service.createSession(SCOPE, { conversationId: 'conversation-1', selection, }); expect(snapshot.seatId).toBe(SCOPE.seatId); expect(snapshot.state).toBe('idle'); expect(snapshot.selection).toEqual(selection); expect(snapshot.nativeSessionId).toBeTruthy(); }); it('rejects server-authority fields supplied by an external caller', async () => { const { service, adapter } = setup(); const selection = await availableSelection(adapter); const hostile = { conversationId: 'conversation-1', selection, seatId: 'attacker-seat', executablePath: '/usr/bin/evil', home: '/home/attacker', cwd: '/tmp/attacker', nativeSessionPath: '/var/native/attacker.jsonl', } as unknown as Parameters[1]; let error: unknown; try { await service.createSession(SCOPE, hostile); } catch (caught) { error = caught; } expect(error).toBeInstanceOf(HarnessScopeViolationError); expect((error as HarnessScopeViolationError).field).toBe('seatId'); }); it('returns adapter_unavailable for an unknown harness id, echoing the requested tuple', async () => { const { service } = setup(); const selection: HarnessSelection = { harnessId: 'ghost-harness', providerId: 'p', modelId: 'm', }; let error: unknown; try { await service.createSession(SCOPE, { conversationId: 'conversation-1', selection }); } catch (caught) { error = caught; } expect(error).toBeInstanceOf(HarnessOperationError); const dto = (error as HarnessOperationError).dto; expect(dto.code).toBe('adapter_unavailable'); expect(dto.selection).toEqual(selection); expect(dto.correlationId).toBe(SCOPE.correlationId); }); it('returns selection_invalid for an unknown provider/model tuple, unchanged', async () => { const { service } = setup(); const selection: HarnessSelection = { harnessId: 'fake', providerId: 'ghost-provider', modelId: 'ghost-model', }; let error: unknown; try { await service.createSession(SCOPE, { conversationId: 'conversation-1', selection }); } catch (caught) { error = caught; } expect(error).toBeInstanceOf(HarnessOperationError); const dto = (error as HarnessOperationError).dto; expect(dto.code).toBe('selection_invalid'); expect(dto.selection).toEqual(selection); }); it('returns model_unavailable without falling back for a known unavailable model', async () => { const { service, adapter } = setup(); const catalog = await adapter.catalog(READ_CONTEXT); const unavailable = catalog.models.find((entry) => entry.availability === 'unavailable'); expect(unavailable).toBeDefined(); const selection: HarnessSelection = { harnessId: unavailable!.harnessId, providerId: unavailable!.providerId, modelId: unavailable!.modelId, }; let error: unknown; try { await service.createSession(SCOPE, { conversationId: 'conversation-1', selection }); } catch (caught) { error = caught; } expect(error).toBeInstanceOf(HarnessOperationError); const dto = (error as HarnessOperationError).dto; expect(dto.code).toBe('model_unavailable'); // No substitution: the DTO tuple is exactly what was requested. expect(dto.selection).toEqual(selection); }); it('gives create, resume, detach, evict, and end distinct observable effects', async () => { const { service, adapter } = setup(); const selection = await availableSelection(adapter); const created = await service.createSession(SCOPE, { conversationId: 'conversation-create', selection, }); expect(created.state).toBe('idle'); expect(created.processId).toBeTruthy(); expect(created.attachedClientIds).toEqual([]); const resumed = await service.resumeSession(SCOPE, { conversationId: 'conversation-resume', nativeSessionId: 'native-preexisting-123', selection, }); // Resume binds the supplied native session; create mints a fresh one. expect(resumed.nativeSessionId).toBe('native-preexisting-123'); expect(resumed.nativeSessionId).not.toBe(created.nativeSessionId); await service.attach(SCOPE, { conversationId: 'conversation-create', clientId: 'browser-1', }); const afterAttach = await service.snapshot(SCOPE, 'conversation-create'); expect(afterAttach.attachedClientIds).toEqual(['browser-1']); const afterDetach = await service.detach(SCOPE, { conversationId: 'conversation-create', clientId: 'browser-1', }); // Detach removes the browser attachment only; the process stays alive. expect(afterDetach.attachedClientIds).toEqual([]); expect(afterDetach.state).toBe('idle'); expect(afterDetach.processId).toBeTruthy(); const afterEvict = await service.evict(SCOPE, { conversationId: 'conversation-create', reason: 'idle_timeout', }); // Evict stops the process but retains the resumable native session. expect(afterEvict.state).toBe('evicted'); expect(afterEvict.processId).toBeUndefined(); expect(afterEvict.nativeSessionId).toBe(created.nativeSessionId); const afterEnd = await service.end(SCOPE, { conversationId: 'conversation-create', reason: 'session_ended', }); // End destructively terminates the native session. expect(afterEnd.state).toBe('ended'); }); it('fails typed when an unsupported capability is exercised', async () => { const withoutExtensionUi = HARNESS_CAPABILITIES.filter( (capability) => capability !== 'extensionUi', ); const { service, adapter } = setup(withoutExtensionUi); const selection = await availableSelection(adapter); await service.createSession(SCOPE, { conversationId: 'conversation-1', selection }); let error: unknown; try { await service.respondInteraction(SCOPE, { conversationId: 'conversation-1', response: { requestId: 'interaction-1', type: 'confirm', accepted: true }, }); } catch (caught) { error = caught; } expect(error).toBeInstanceOf(HarnessOperationError); expect((error as HarnessOperationError).dto.code).toBe('interaction_unsupported'); }); });