calendar-agent-retrieval-data-integrity.test.mjs
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠ breaking
1 day ago
| 1 | /** |
| 2 | * Tier 5 — DATA INTEGRITY: retrieval is read-only and counts are faithful. |
| 3 | * |
| 4 | * Verifies that agent retrieval never mutates the store, that event_count |
| 5 | * matches returned items, and that agent visibility is independent of the |
| 6 | * display toggle (security checklist #7). |
| 7 | * @see lib/calendar/agent-retrieval.mjs |
| 8 | */ |
| 9 | import { describe, it, beforeEach, afterEach } from 'node:test'; |
| 10 | import assert from 'node:assert/strict'; |
| 11 | import fs from 'node:fs'; |
| 12 | import path from 'node:path'; |
| 13 | import { fileURLToPath } from 'node:url'; |
| 14 | import { |
| 15 | importIcsIntoVault, |
| 16 | getCalendarStorePath, |
| 17 | } from '../lib/calendar/event-store.mjs'; |
| 18 | import { patchSourceCalendar } from '../lib/calendar/source-calendar-patch.mjs'; |
| 19 | import { retrieveAgentCalendarContext } from '../lib/calendar/agent-retrieval.mjs'; |
| 20 | |
| 21 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
| 22 | const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-calendar-agent-integrity'); |
| 23 | const ics = fs.readFileSync(path.join(__dirname, 'fixtures', 'calendar', 'multi-event.ics'), 'utf8'); |
| 24 | |
| 25 | const RANGE = { from: '2026-06-01', to: '2026-06-30' }; |
| 26 | |
| 27 | describe('Data integrity — agent retrieval', () => { |
| 28 | const dataDir = path.join(tmpRoot, 'data'); |
| 29 | const vaultId = 'default'; |
| 30 | /** @type {string} */ |
| 31 | let calendarId; |
| 32 | |
| 33 | beforeEach(() => { |
| 34 | fs.rmSync(tmpRoot, { recursive: true, force: true }); |
| 35 | fs.mkdirSync(dataDir, { recursive: true }); |
| 36 | calendarId = importIcsIntoVault(dataDir, vaultId, { icsText: ics, displayName: 'Personal' }).source_calendar_id; |
| 37 | patchSourceCalendar(dataDir, vaultId, calendarId, { enabled_for_agents: true, agent_context_tier_max: 2 }); |
| 38 | }); |
| 39 | |
| 40 | afterEach(() => { |
| 41 | fs.rmSync(tmpRoot, { recursive: true, force: true }); |
| 42 | }); |
| 43 | |
| 44 | it('does not mutate the store file when retrieving', () => { |
| 45 | const storePath = getCalendarStorePath(dataDir); |
| 46 | const before = fs.readFileSync(storePath, 'utf8'); |
| 47 | retrieveAgentCalendarContext(dataDir, vaultId, { ...RANGE, agentContextTier: 2 }); |
| 48 | const after = fs.readFileSync(storePath, 'utf8'); |
| 49 | assert.equal(after, before); |
| 50 | }); |
| 51 | |
| 52 | it('summary returned to the agent matches the stored summary verbatim', () => { |
| 53 | const stored = JSON.parse(fs.readFileSync(getCalendarStorePath(dataDir), 'utf8')); |
| 54 | const storedSummaries = new Set( |
| 55 | stored.vaults[vaultId].events.filter((e) => !e.deleted_at).map((e) => e.summary), |
| 56 | ); |
| 57 | const result = retrieveAgentCalendarContext(dataDir, vaultId, { ...RANGE, agentContextTier: 2 }); |
| 58 | for (const item of result.items) { |
| 59 | assert.ok(storedSummaries.has(item.summary), `unexpected summary: ${item.summary}`); |
| 60 | } |
| 61 | }); |
| 62 | |
| 63 | it('event_count in the summary equals the number of returned items per calendar', () => { |
| 64 | const result = retrieveAgentCalendarContext(dataDir, vaultId, { ...RANGE, agentContextTier: 2 }); |
| 65 | const counted = result.items.filter((i) => i.source_calendar_id === calendarId).length; |
| 66 | assert.equal(result.source_calendars[0].event_count, counted); |
| 67 | }); |
| 68 | |
| 69 | it('agent visibility is independent of enabled_for_display', () => { |
| 70 | patchSourceCalendar(dataDir, vaultId, calendarId, { enabled_for_display: false }); |
| 71 | const result = retrieveAgentCalendarContext(dataDir, vaultId, { ...RANGE, agentContextTier: 2 }); |
| 72 | assert.ok(result.items.length > 0, 'agents still see events when display is off but agents on'); |
| 73 | }); |
| 74 | |
| 75 | it('returns exactly the non-tombstoned events and excludes deleted ones', () => { |
| 76 | const storePath = getCalendarStorePath(dataDir); |
| 77 | const stored = JSON.parse(fs.readFileSync(storePath, 'utf8')); |
| 78 | const liveCount = stored.vaults[vaultId].events.filter((e) => !e.deleted_at).length; |
| 79 | |
| 80 | const before = retrieveAgentCalendarContext(dataDir, vaultId, { ...RANGE, agentContextTier: 2 }); |
| 81 | assert.equal(before.items.length, liveCount); |
| 82 | |
| 83 | // Tombstone one event (provider delete / revoke) and confirm it disappears. |
| 84 | stored.vaults[vaultId].events[0].deleted_at = '2026-06-25T00:00:00.000Z'; |
| 85 | fs.writeFileSync(storePath, JSON.stringify(stored)); |
| 86 | |
| 87 | const after = retrieveAgentCalendarContext(dataDir, vaultId, { ...RANGE, agentContextTier: 2 }); |
| 88 | assert.equal(after.items.length, liveCount - 1); |
| 89 | }); |
| 90 | }); |
File History
1 commit
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠
1 day ago