import { describe, expect, it, vi } from 'vitest'; import type { SlashCommandPayload } from '@mosaicstack/types'; import { ChatGateway } from './chat.gateway.js'; const payload: SlashCommandPayload = { command: 'gc', conversationId: 'conversation-1', approvalId: 'approval-1', }; /** * Task 5 fence (F, existing control): gateway-owned command authorization/approval must * cause ZERO chat-runtime dispatch. Placed in the gateway's chat-runtime-router slot (the * former direct `AgentService` slot) so any accidental chat-runtime resolution throws * loudly instead of silently passing. Because execute/approval run entirely through the * command executor dependency and never resolve a chat runtime, this fixture is never * triggered and the ingress stays a GREEN control. */ function failIfUsedChatRuntimeRouter() { return { onModuleInit: () => { throw new Error('chat runtime router must not initialise on the command approval path'); }, get active(): never { throw new Error('chat runtime must not be resolved on the command approval path'); }, }; } function buildGateway(commandExecutor: { execute: ReturnType; createApproval: ReturnType; }): ChatGateway { return new ChatGateway( failIfUsedChatRuntimeRouter() as never, {} as never, {} as never, {} as never, commandExecutor as never, {} as never, ); } describe('ChatGateway command approval ingress', () => { it('passes the client approval ID through to command execution while deriving the actor server-side', async (): Promise => { const commandExecutor = { execute: vi.fn().mockResolvedValue({ ...payload, success: true }), createApproval: vi.fn(), }; const gateway = buildGateway(commandExecutor); const client = { data: { user: { id: 'admin-1' } }, emit: vi.fn() }; await gateway.handleCommandExecute(client as never, payload); expect(commandExecutor.execute).toHaveBeenCalledWith(payload, { userId: 'admin-1', tenantId: 'admin-1', }); expect(client.emit).toHaveBeenCalledWith( 'command:result', expect.objectContaining({ success: true }), ); }); it('issues a durable approval only for the authenticated actor', async (): Promise => { const commandExecutor = { execute: vi.fn(), createApproval: vi.fn().mockResolvedValue({ approvalId: 'approval-1', expiresAt: '2026-07-12T00:05:00.000Z', }), }; const gateway = buildGateway(commandExecutor); const client = { data: { user: { id: 'admin-1' } }, emit: vi.fn() }; await gateway.handleCommandApproval(client as never, { command: 'gc', conversationId: 'conversation-1', }); expect(commandExecutor.createApproval).toHaveBeenCalledWith( { command: 'gc', conversationId: 'conversation-1' }, { userId: 'admin-1', tenantId: 'admin-1' }, ); expect(client.emit).toHaveBeenCalledWith('command:approval', { command: 'gc', conversationId: 'conversation-1', success: true, approvalId: 'approval-1', expiresAt: '2026-07-12T00:05:00.000Z', }); }); }); /** * Task 5 (G3) command runtime fence. Under pi-rpc there is no embedded chat session, so * embedded slash-commands (/model, /agent, and every other non-audited command) are fixed * "unsupported" and MUST fail closed BEFORE reaching the command executor — never a silent * fall-through to embedded execution. Only runtime-independent audited system commands * (/reload) pass through as a positive control, and the approval path stays runtime-independent. * The router stub here carries `runtimeMode: 'pi-rpc'` and throws if any runtime is resolved, so * a fence bypass surfaces as a thrown error rather than a silent embedded dispatch. */ function buildPiRpcGateway(commandExecutor: { execute: ReturnType; createApproval: ReturnType; }): ChatGateway { const piRpcRouter = { runtimeMode: 'pi-rpc' as const, onModuleInit: () => { throw new Error('chat runtime router must not initialise on the pi-rpc command path'); }, get active(): never { throw new Error('chat runtime must not be resolved on the pi-rpc command path'); }, }; return new ChatGateway( piRpcRouter as never, {} as never, {} as never, {} as never, commandExecutor as never, {} as never, ); } describe('ChatGateway command runtime fence (Task 5 G3, pi-rpc)', () => { const UNSUPPORTED = 'Slash commands are not available on this deployment.'; it.each(['model', 'agent', 'gc'])( 'fails /%s closed before the executor under pi-rpc (execute never called)', async (command): Promise => { const commandExecutor = { execute: vi .fn() .mockResolvedValue({ command, conversationId: 'conversation-1', success: true }), createApproval: vi.fn(), }; const gateway = buildPiRpcGateway(commandExecutor); const client = { data: { user: { id: 'admin-1' } }, emit: vi.fn() }; await gateway.handleCommandExecute(client as never, { command, conversationId: 'conversation-1', }); expect(commandExecutor.execute).toHaveBeenCalledTimes(0); expect(client.emit).toHaveBeenCalledWith('command:result', { command, conversationId: 'conversation-1', success: false, message: UNSUPPORTED, }); }, ); it('passes the audited /reload system command through as a positive control under pi-rpc', async (): Promise => { const reloadResult = { command: 'reload', conversationId: 'conversation-1', success: true }; const commandExecutor = { execute: vi.fn().mockResolvedValue(reloadResult), createApproval: vi.fn(), }; const gateway = buildPiRpcGateway(commandExecutor); const client = { data: { user: { id: 'admin-1' } }, emit: vi.fn() }; await gateway.handleCommandExecute(client as never, { command: 'reload', conversationId: 'conversation-1', }); expect(commandExecutor.execute).toHaveBeenCalledTimes(1); expect(commandExecutor.execute).toHaveBeenCalledWith( { command: 'reload', conversationId: 'conversation-1' }, { userId: 'admin-1', tenantId: 'admin-1' }, ); expect(client.emit).toHaveBeenCalledWith('command:result', reloadResult); }); it('keeps command approval runtime-independent under pi-rpc (createApproval still runs)', async (): Promise => { const commandExecutor = { execute: vi.fn(), createApproval: vi.fn().mockResolvedValue({ approvalId: 'approval-1', expiresAt: '2026-07-12T00:05:00.000Z', }), }; const gateway = buildPiRpcGateway(commandExecutor); const client = { data: { user: { id: 'admin-1' } }, emit: vi.fn() }; await gateway.handleCommandApproval(client as never, { command: 'gc', conversationId: 'conversation-1', }); expect(commandExecutor.createApproval).toHaveBeenCalledWith( { command: 'gc', conversationId: 'conversation-1' }, { userId: 'admin-1', tenantId: 'admin-1' }, ); expect(client.emit).toHaveBeenCalledWith( 'command:approval', expect.objectContaining({ success: true, approvalId: 'approval-1' }), ); }); });