From c26b3b775279575276c6ebe8955a9146bfc61413 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Mon, 13 Jul 2026 08:32:30 -0500 Subject: [PATCH] test(memory): cover server-derived operator scope --- .../__tests__/agent-service-ownership.test.ts | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/apps/gateway/src/agent/__tests__/agent-service-ownership.test.ts b/apps/gateway/src/agent/__tests__/agent-service-ownership.test.ts index b015155..83e0368 100644 --- a/apps/gateway/src/agent/__tests__/agent-service-ownership.test.ts +++ b/apps/gateway/src/agent/__tests__/agent-service-ownership.test.ts @@ -12,18 +12,24 @@ type AgentServiceInternals = { creating: Map>; }; -function makeService(): AgentService { +function makeService(operatorMemory: unknown = null): AgentService { return new AgentService( - {} as never, + { + getDefaultModel: vi.fn(() => null), + getRegistry: vi.fn(() => ({})), + findModel: vi.fn(), + listAvailableModels: vi.fn(() => []), + } as never, {} as never, {} as never, { available: false } as never, {} as never, - {} as never, - {} as never, + { getToolDefinitions: vi.fn(() => []) } as never, + { loadForSession: vi.fn(async () => ({ metaTools: [], promptAdditions: [] })) } as never, null, null, { collect: vi.fn().mockResolvedValue(undefined) } as never, + operatorMemory as never, ); } @@ -120,6 +126,37 @@ describe('AgentService owner/tenant scope enforcement', () => { expect(internals(service).sessions.has(CONVERSATION_ID)).toBe(false); }); + it('derives the operator-memory scope on the createSession production path', async () => { + const plugin = { capture: vi.fn(), search: vi.fn() }; + const service = makeService(plugin); + const buildTools = vi.spyOn(service as never, 'buildToolsForSandbox').mockReturnValue([]); + + // Session construction reaches the real scope derivation before the intentionally incomplete + // Pi test double rejects later in createAgentSession. + await service.createSession(CONVERSATION_ID, OWNER_SCOPE).catch(() => undefined); + + expect(buildTools).toHaveBeenCalledWith(expect.any(String), OWNER_SCOPE.userId, { + tenantId: OWNER_SCOPE.tenantId, + ownerId: OWNER_SCOPE.userId, + sessionId: CONVERSATION_ID, + }); + }); + + it('denies a foreign actor before it can obtain another session operator-memory scope', async () => { + const plugin = { capture: vi.fn(), search: vi.fn() }; + const service = makeService(plugin); + internals(service).sessions.set(CONVERSATION_ID, makeSession()); + const buildTools = vi.spyOn(service as never, 'buildToolsForSandbox'); + + await expect(service.createSession(CONVERSATION_ID, FOREIGN_SCOPE)).rejects.toBeInstanceOf( + ForbiddenException, + ); + + expect(buildTools).not.toHaveBeenCalled(); + expect(plugin.capture).not.toHaveBeenCalled(); + expect(plugin.search).not.toHaveBeenCalled(); + }); + it('checks owner/tenant scope before returning an in-flight session creation', async () => { const service = makeService(); const session = makeSession();