memory-verify.test.mjs
245 lines 9.4 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 /**
2 * Tests for skeptical memory verification: verifyMemoryEvent() and MEMORY_CONFIDENCE_LEVELS.
3 */
4 import { describe, it, before, after, beforeEach } from 'node:test';
5 import assert from 'node:assert';
6 import fs from 'fs';
7 import path from 'path';
8 import os from 'os';
9
10 import { createMemoryEvent } from '../lib/memory-event.mjs';
11 import { verifyMemoryEvent, MEMORY_CONFIDENCE_LEVELS } from '../lib/memory.mjs';
12
13 let tmpDir;
14 let vaultDir;
15
16 before(() => {
17 tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'knowtation-verify-test-'));
18 vaultDir = path.join(tmpDir, 'vault');
19 fs.mkdirSync(vaultDir, { recursive: true });
20 });
21
22 after(() => {
23 fs.rmSync(tmpDir, { recursive: true, force: true });
24 });
25
26 describe('MEMORY_CONFIDENCE_LEVELS', () => {
27 it('is a frozen array with the three levels', () => {
28 assert(Array.isArray(MEMORY_CONFIDENCE_LEVELS));
29 assert(Object.isFrozen(MEMORY_CONFIDENCE_LEVELS));
30 assert(MEMORY_CONFIDENCE_LEVELS.includes('verified'));
31 assert(MEMORY_CONFIDENCE_LEVELS.includes('hint'));
32 assert(MEMORY_CONFIDENCE_LEVELS.includes('stale'));
33 assert.strictEqual(MEMORY_CONFIDENCE_LEVELS.length, 3);
34 });
35 });
36
37 describe('verifyMemoryEvent — no path reference', () => {
38 it('returns hint when event data has no path reference', () => {
39 const config = { vault_path: vaultDir };
40 const event = createMemoryEvent('search', { query: 'test' });
41 const result = verifyMemoryEvent(config, event);
42 assert.strictEqual(result.confidence, 'hint');
43 assert(result.reason.includes('no verifiable path'));
44 });
45
46 it('returns hint when event is null', () => {
47 const result = verifyMemoryEvent({ vault_path: vaultDir }, null);
48 assert.strictEqual(result.confidence, 'hint');
49 });
50
51 it('returns hint when event is not an object', () => {
52 const result = verifyMemoryEvent({ vault_path: vaultDir }, 'not-an-event');
53 assert.strictEqual(result.confidence, 'hint');
54 });
55
56 it('returns hint when vault_path is not configured', () => {
57 const event = createMemoryEvent('write', { path: 'notes/test.md' });
58 const result = verifyMemoryEvent({}, event);
59 assert.strictEqual(result.confidence, 'hint');
60 assert(result.reason.includes('vault_path not configured'));
61 });
62
63 it('returns hint for user events with no path', () => {
64 const event = createMemoryEvent('user', { key: 'preference', theme: 'dark' });
65 const result = verifyMemoryEvent({ vault_path: vaultDir }, event);
66 assert.strictEqual(result.confidence, 'hint');
67 });
68 });
69
70 describe('verifyMemoryEvent — failed status', () => {
71 it('returns stale for events with status=failed', () => {
72 const event = createMemoryEvent('write', { path: 'notes/test.md' }, { status: 'failed' });
73 const result = verifyMemoryEvent({ vault_path: vaultDir }, event);
74 assert.strictEqual(result.confidence, 'stale');
75 assert(result.reason.includes('failed operation'));
76 });
77 });
78
79 describe('verifyMemoryEvent — path verification', () => {
80 let noteDir;
81
82 beforeEach(() => {
83 noteDir = path.join(tmpDir, 'notes-' + Date.now());
84 fs.mkdirSync(noteDir, { recursive: true });
85 });
86
87 it('returns stale when referenced path does not exist', () => {
88 const config = { vault_path: noteDir };
89 const event = createMemoryEvent('write', { path: 'notes/nonexistent.md' });
90 const result = verifyMemoryEvent(config, event);
91 assert.strictEqual(result.confidence, 'stale');
92 assert(result.reason.includes('no longer exists'));
93 });
94
95 it('returns verified when referenced path exists and is unchanged', () => {
96 const notePath = 'notes/exists.md';
97 const absPath = path.join(noteDir, notePath);
98 fs.mkdirSync(path.dirname(absPath), { recursive: true });
99 // Set mtime explicitly to the past so the event ts (now) is always after the file mtime
100 fs.writeFileSync(absPath, '# Test Note\n', 'utf8');
101 const pastMtime = new Date(Date.now() - 10000);
102 fs.utimesSync(absPath, pastMtime, pastMtime);
103
104 const config = { vault_path: noteDir };
105 const event = createMemoryEvent('write', { path: notePath });
106 // event.ts is now — file mtime is 10s ago, so file has not changed since event
107
108 const result = verifyMemoryEvent(config, event);
109 assert.strictEqual(result.confidence, 'verified');
110 assert(result.reason.includes('exists and unchanged'));
111 });
112
113 it('returns stale when referenced path was modified after event ts', () => {
114 const notePath = 'notes/modified.md';
115 const absPath = path.join(noteDir, notePath);
116 fs.mkdirSync(path.dirname(absPath), { recursive: true });
117
118 const oldTs = new Date(Date.now() - 10000).toISOString();
119 const event = createMemoryEvent('write', { path: notePath });
120 event.ts = oldTs;
121
122 fs.writeFileSync(absPath, '# Modified Note\n', 'utf8');
123
124 const config = { vault_path: noteDir };
125 const result = verifyMemoryEvent(config, event);
126 assert.strictEqual(result.confidence, 'stale');
127 assert(result.reason.includes('modified after event'));
128 });
129
130 it('extracts path from data.path field (write events)', () => {
131 const notePath = 'inbox/test.md';
132 const absPath = path.join(noteDir, notePath);
133 fs.mkdirSync(path.dirname(absPath), { recursive: true });
134 fs.writeFileSync(absPath, '# Inbox note\n', 'utf8');
135 const pastMtime = new Date(Date.now() - 10000);
136 fs.utimesSync(absPath, pastMtime, pastMtime);
137
138 const config = { vault_path: noteDir };
139 const event = createMemoryEvent('write', { path: notePath });
140
141 const result = verifyMemoryEvent(config, event);
142 assert.strictEqual(result.confidence, 'verified');
143 });
144
145 it('extracts first path from data.paths array (search events)', () => {
146 const notePath = 'projects/alpha.md';
147 const absPath = path.join(noteDir, notePath);
148 fs.mkdirSync(path.dirname(absPath), { recursive: true });
149 fs.writeFileSync(absPath, '# Alpha\n', 'utf8');
150 const pastMtime = new Date(Date.now() - 10000);
151 fs.utimesSync(absPath, pastMtime, pastMtime);
152
153 const config = { vault_path: noteDir };
154 const event = createMemoryEvent('search', {
155 query: 'alpha project',
156 paths: [notePath, 'projects/beta.md'],
157 });
158
159 const result = verifyMemoryEvent(config, event);
160 assert.strictEqual(result.confidence, 'verified');
161 });
162
163 it('returns stale when first path in paths array is missing', () => {
164 const config = { vault_path: noteDir };
165 const event = createMemoryEvent('search', {
166 query: 'something',
167 paths: ['missing/note.md', 'other.md'],
168 });
169 const result = verifyMemoryEvent(config, event);
170 assert.strictEqual(result.confidence, 'stale');
171 });
172
173 it('extracts path from data.exported array (export events)', () => {
174 const notePath = 'exports/report.md';
175 const absPath = path.join(noteDir, notePath);
176 fs.mkdirSync(path.dirname(absPath), { recursive: true });
177 fs.writeFileSync(absPath, '# Report\n', 'utf8');
178 const pastMtime = new Date(Date.now() - 10000);
179 fs.utimesSync(absPath, pastMtime, pastMtime);
180
181 const config = { vault_path: noteDir };
182 const event = createMemoryEvent('export', {
183 format: 'md',
184 exported: [{ path: notePath }],
185 });
186
187 const result = verifyMemoryEvent(config, event);
188 assert.strictEqual(result.confidence, 'verified');
189 });
190
191 it('accepts absolute paths in event data', () => {
192 const absNotePath = path.join(noteDir, 'absolute.md');
193 fs.writeFileSync(absNotePath, '# Absolute\n', 'utf8');
194 const pastMtime = new Date(Date.now() - 10000);
195 fs.utimesSync(absNotePath, pastMtime, pastMtime);
196
197 const config = { vault_path: noteDir };
198 const event = createMemoryEvent('write', { path: absNotePath });
199
200 const result = verifyMemoryEvent(config, event);
201 assert.strictEqual(result.confidence, 'verified');
202 });
203 });
204
205 describe('verifyMemoryEvent — edge cases', () => {
206 it('handles missing config gracefully', () => {
207 const event = createMemoryEvent('write', { path: 'test.md' });
208 const result = verifyMemoryEvent(null, event);
209 assert.strictEqual(result.confidence, 'hint');
210 });
211
212 it('handles events with no ts gracefully', () => {
213 const notePath = 'no-ts.md';
214 const absPath = path.join(vaultDir, notePath);
215 fs.writeFileSync(absPath, '# No TS\n', 'utf8');
216 const pastMtime = new Date(Date.now() - 10000);
217 fs.utimesSync(absPath, pastMtime, pastMtime);
218
219 const event = createMemoryEvent('write', { path: notePath });
220 delete event.ts;
221
222 const config = { vault_path: vaultDir };
223 const result = verifyMemoryEvent(config, event);
224 // No ts means no "modified after event" check — file exists so verified
225 assert.strictEqual(result.confidence, 'verified');
226 });
227
228 it('all three confidence levels are returned correctly', () => {
229 const config = { vault_path: vaultDir };
230
231 const hintEvent = createMemoryEvent('search', { query: 'no paths here' });
232 assert.strictEqual(verifyMemoryEvent(config, hintEvent).confidence, 'hint');
233
234 const staleEvent = createMemoryEvent('write', { path: 'definitely/does/not/exist.md' });
235 assert.strictEqual(verifyMemoryEvent(config, staleEvent).confidence, 'stale');
236
237 const verifiedPath = 'verified-note.md';
238 const verifiedAbsPath = path.join(vaultDir, verifiedPath);
239 fs.writeFileSync(verifiedAbsPath, '# Verified\n', 'utf8');
240 const pastMtime = new Date(Date.now() - 10000);
241 fs.utimesSync(verifiedAbsPath, pastMtime, pastMtime);
242 const verifiedEvent = createMemoryEvent('write', { path: verifiedPath });
243 assert.strictEqual(verifyMemoryEvent(config, verifiedEvent).confidence, 'verified');
244 });
245 });
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 1 day ago