calendar-agent-retrieval-data-integrity.test.mjs
90 lines 4.1 KB
Raw
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