|
1 | 1 | import { loggerMock } from '@sim/testing' |
2 | 2 | import { beforeEach, describe, expect, it, vi } from 'vitest' |
3 | 3 |
|
4 | | -const { mockDecryptSecret, mockRedactObjectStrings } = vi.hoisted(() => ({ |
5 | | - mockDecryptSecret: vi.fn(), |
6 | | - mockRedactObjectStrings: vi.fn(async (value: unknown) => value), |
| 4 | +const { mockDecryptSecret, mockRedactObjectStrings, mockIsEnforced, mockReportUnrecorded } = |
| 5 | + vi.hoisted(() => ({ |
| 6 | + mockDecryptSecret: vi.fn(), |
| 7 | + mockRedactObjectStrings: vi.fn(async (value: unknown) => value), |
| 8 | + mockIsEnforced: vi.fn(() => false), |
| 9 | + mockReportUnrecorded: vi.fn(), |
| 10 | + })) |
| 11 | + |
| 12 | +vi.mock('@/lib/execution/durable-secret-provenance-enforcement', () => ({ |
| 13 | + DURABLE_SECRET_PROVENANCE_SURFACES: ['memory', 'table-row', 'knowledge'], |
| 14 | + isDurableSecretProvenanceEnforced: mockIsEnforced, |
| 15 | + reportUnrecordedDurableProvenance: mockReportUnrecorded, |
7 | 16 | })) |
8 | 17 |
|
9 | 18 | vi.mock('@/lib/core/security/encryption', () => ({ |
@@ -35,6 +44,7 @@ describe('Memory', () => { |
35 | 44 |
|
36 | 45 | beforeEach(() => { |
37 | 46 | vi.clearAllMocks() |
| 47 | + mockIsEnforced.mockReturnValue(false) |
38 | 48 | mockDecryptSecret.mockImplementation(async (encryptedValue: string) => ({ |
39 | 49 | decrypted: `decrypted:${encryptedValue}`, |
40 | 50 | })) |
@@ -503,6 +513,48 @@ describe('Memory', () => { |
503 | 513 | expect(messages).toEqual([retainedPublicMessage]) |
504 | 514 | expect(mockDecryptSecret).not.toHaveBeenCalled() |
505 | 515 | }) |
| 516 | + |
| 517 | + /** Trace 2's shape: a stored memory a previous run could not vouch for. */ |
| 518 | + it('reads a memory with unrecorded provenance while the surface stays open', async () => { |
| 519 | + const registry = new ResolvedSecretTraceRegistry([], { |
| 520 | + userId: 'user-1', |
| 521 | + workspaceId: 'workspace-1', |
| 522 | + }) |
| 523 | + vi.spyOn(memoryService as any, 'fetchMemory').mockResolvedValueOnce({ |
| 524 | + messages: [{ role: 'user', content: 'how do i see my tickets?' }], |
| 525 | + provenance: { status: 'unknown' }, |
| 526 | + }) |
| 527 | + |
| 528 | + const messages = await memoryService.fetchMemoryMessages( |
| 529 | + createContext(registry) as never, |
| 530 | + inputs |
| 531 | + ) |
| 532 | + |
| 533 | + expect(messages).toEqual([{ role: 'user', content: 'how do i see my tickets?' }]) |
| 534 | + expect(registry.isPermanentlyIncomplete()).toBe(false) |
| 535 | + expect(mockReportUnrecorded).toHaveBeenCalledWith({ |
| 536 | + surface: 'memory', |
| 537 | + cause: 'stored-memory-provenance-unknown', |
| 538 | + workspaceId: 'workspace-1', |
| 539 | + }) |
| 540 | + }) |
| 541 | + |
| 542 | + it('refuses that same memory once the memory surface is closed', async () => { |
| 543 | + mockIsEnforced.mockReturnValue(true) |
| 544 | + const registry = new ResolvedSecretTraceRegistry([], { |
| 545 | + userId: 'user-1', |
| 546 | + workspaceId: 'workspace-1', |
| 547 | + }) |
| 548 | + vi.spyOn(memoryService as any, 'fetchMemory').mockResolvedValueOnce({ |
| 549 | + messages: [{ role: 'user', content: 'how do i see my tickets?' }], |
| 550 | + provenance: { status: 'unknown' }, |
| 551 | + }) |
| 552 | + |
| 553 | + await expect( |
| 554 | + memoryService.fetchMemoryMessages(createContext(registry) as never, inputs) |
| 555 | + ).rejects.toThrow() |
| 556 | + expect(mockReportUnrecorded).not.toHaveBeenCalled() |
| 557 | + }) |
506 | 558 | }) |
507 | 559 |
|
508 | 560 | describe('secret-safe diagnostics', () => { |
|
0 commit comments