attest-store-dual-write.test.mjs
320 lines 11.0 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 2 days ago
1 /**
2 * Tests for AIR Improvement E — dual-write (Blobs + ICP) and verifyWithIcp.
3 *
4 * Uses _setTestOverrides from icp-attestation-client.mjs to inject mock
5 * anchor/query implementations without fighting ESM module binding rules.
6 */
7
8 import { test, beforeEach, afterEach, describe } from 'node:test';
9 import assert from 'node:assert/strict';
10
11 const TEST_SECRET = 'test-attestation-secret-that-is-at-least-32-characters-long';
12 const TEST_CANISTER_ID = 'ryjl3-tyaaa-aaaaa-aaaba-cai';
13 const TEST_KEY = 'deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef';
14
15 function createStubBlobStore() {
16 const map = new Map();
17 return {
18 _map: map,
19 async get(key, opts) {
20 const raw = map.get(key);
21 if (raw === undefined) return null;
22 if (opts && opts.type === 'json') return JSON.parse(raw);
23 return raw;
24 },
25 async setJSON(key, value) {
26 map.set(key, JSON.stringify(value));
27 },
28 };
29 }
30
31 let savedSecret, savedCanisterId, savedKey, savedEndpoint;
32
33 beforeEach(() => {
34 savedSecret = process.env.ATTESTATION_SECRET;
35 savedCanisterId = process.env.ICP_ATTESTATION_CANISTER_ID;
36 savedKey = process.env.ICP_ATTESTATION_KEY;
37 savedEndpoint = process.env.KNOWTATION_AIR_ENDPOINT;
38
39 process.env.ATTESTATION_SECRET = TEST_SECRET;
40 delete process.env.KNOWTATION_AIR_ENDPOINT;
41 globalThis.__knowtation_attest_blob = createStubBlobStore();
42 });
43
44 afterEach(async () => {
45 if (savedSecret !== undefined) process.env.ATTESTATION_SECRET = savedSecret;
46 else delete process.env.ATTESTATION_SECRET;
47 if (savedCanisterId !== undefined) process.env.ICP_ATTESTATION_CANISTER_ID = savedCanisterId;
48 else delete process.env.ICP_ATTESTATION_CANISTER_ID;
49 if (savedKey !== undefined) process.env.ICP_ATTESTATION_KEY = savedKey;
50 else delete process.env.ICP_ATTESTATION_KEY;
51 if (savedEndpoint !== undefined) process.env.KNOWTATION_AIR_ENDPOINT = savedEndpoint;
52 else delete process.env.KNOWTATION_AIR_ENDPOINT;
53 delete globalThis.__knowtation_attest_blob;
54
55 const { _setTestOverrides, resetClient } = await import(
56 '../hub/gateway/icp-attestation-client.mjs'
57 );
58 _setTestOverrides({});
59 resetClient();
60 });
61
62 function enableIcpEnv() {
63 process.env.ICP_ATTESTATION_CANISTER_ID = TEST_CANISTER_ID;
64 process.env.ICP_ATTESTATION_KEY = TEST_KEY;
65 }
66
67 describe('createAttestation with ICP disabled', () => {
68 test('sets icp_status to "disabled" when ICP not configured', async () => {
69 delete process.env.ICP_ATTESTATION_CANISTER_ID;
70 delete process.env.ICP_ATTESTATION_KEY;
71
72 const { createAttestation } = await import('../hub/gateway/attest-store.mjs');
73 const result = await createAttestation('write', 'notes/test.md');
74
75 assert.match(result.id, /^air-/);
76 assert.equal(result.icp_status, 'disabled');
77
78 const store = globalThis.__knowtation_attest_blob;
79 const raw = await store.get(`attestation/${result.id}`, { type: 'json' });
80 assert.equal(raw.icp_status, 'disabled');
81 });
82 });
83
84 describe('createAttestation with ICP enabled (mocked)', () => {
85 test('sets icp_status to "anchored" when ICP succeeds', async () => {
86 enableIcpEnv();
87 const { _setTestOverrides, resetClient } = await import(
88 '../hub/gateway/icp-attestation-client.mjs'
89 );
90 resetClient();
91 _setTestOverrides({ anchor: async () => ({ seq: 42 }) });
92
93 const { createAttestation } = await import('../hub/gateway/attest-store.mjs');
94 const result = await createAttestation('write', 'notes/icp-test.md');
95
96 assert.equal(result.icp_status, 'anchored');
97
98 const store = globalThis.__knowtation_attest_blob;
99 const raw = await store.get(`attestation/${result.id}`, { type: 'json' });
100 assert.equal(raw.icp_status, 'anchored');
101 assert.equal(raw.icp_seq, 42);
102 assert.equal(raw.canister_id, TEST_CANISTER_ID);
103 });
104
105 test('sets icp_status to "pending" when ICP returns null', async () => {
106 enableIcpEnv();
107 const { _setTestOverrides, resetClient } = await import(
108 '../hub/gateway/icp-attestation-client.mjs'
109 );
110 resetClient();
111 _setTestOverrides({ anchor: async () => null });
112
113 const { createAttestation } = await import('../hub/gateway/attest-store.mjs');
114 const result = await createAttestation('write', 'notes/fail-test.md');
115
116 assert.equal(result.icp_status, 'pending');
117 });
118
119 test('sets icp_status to "pending" when ICP throws', async () => {
120 enableIcpEnv();
121 const { _setTestOverrides, resetClient } = await import(
122 '../hub/gateway/icp-attestation-client.mjs'
123 );
124 resetClient();
125 _setTestOverrides({
126 anchor: async () => {
127 throw new Error('canister unreachable');
128 },
129 });
130
131 const { createAttestation } = await import('../hub/gateway/attest-store.mjs');
132 const result = await createAttestation('write', 'notes/error-test.md');
133
134 assert.equal(result.icp_status, 'pending');
135 });
136
137 test('Blob record is always created even when ICP fails', async () => {
138 enableIcpEnv();
139 const { _setTestOverrides, resetClient } = await import(
140 '../hub/gateway/icp-attestation-client.mjs'
141 );
142 resetClient();
143 _setTestOverrides({
144 anchor: async () => {
145 throw new Error('timeout');
146 },
147 });
148
149 const { createAttestation, verifyAttestation } = await import(
150 '../hub/gateway/attest-store.mjs'
151 );
152 const result = await createAttestation('write', 'notes/blob-always.md');
153
154 const verification = await verifyAttestation(result.id);
155 assert.equal(verification.verified, true);
156 assert.equal(verification.record.id, result.id);
157 });
158 });
159
160 describe('verifyWithIcp', () => {
161 test('returns icp_not_configured when ICP is disabled', async () => {
162 delete process.env.ICP_ATTESTATION_CANISTER_ID;
163 delete process.env.ICP_ATTESTATION_KEY;
164
165 const { createAttestation, verifyWithIcp } = await import(
166 '../hub/gateway/attest-store.mjs'
167 );
168 const { id } = await createAttestation('write', 'notes/verify-test.md');
169
170 const result = await verifyWithIcp(id);
171 assert.equal(result.consensus, 'icp_not_configured');
172 assert.equal(result.verified, true);
173 assert.equal(result.sources.blobs.found, true);
174 assert.equal(result.sources.blobs.hmac_valid, true);
175 assert.equal(result.sources.icp.found, false);
176 });
177
178 test('returns not_found for nonexistent attestation', async () => {
179 delete process.env.ICP_ATTESTATION_CANISTER_ID;
180 const { verifyWithIcp } = await import('../hub/gateway/attest-store.mjs');
181 const result = await verifyWithIcp('air-00000000-0000-0000-0000-000000000000');
182 assert.equal(result.consensus, 'not_found');
183 assert.equal(result.verified, false);
184 });
185
186 test('returns match when Blobs and ICP agree', async () => {
187 enableIcpEnv();
188 const { _setTestOverrides, resetClient } = await import(
189 '../hub/gateway/icp-attestation-client.mjs'
190 );
191 resetClient();
192 _setTestOverrides({ anchor: async () => ({ seq: 99 }) });
193
194 const { createAttestation, verifyWithIcp } = await import(
195 '../hub/gateway/attest-store.mjs'
196 );
197 const { id } = await createAttestation('write', 'notes/match-test.md');
198
199 const store = globalThis.__knowtation_attest_blob;
200 const blobRec = await store.get(`attestation/${id}`, { type: 'json' });
201
202 _setTestOverrides({
203 anchor: async () => ({ seq: 99 }),
204 query: async () => ({
205 id: blobRec.id,
206 action: blobRec.action,
207 path: blobRec.path,
208 timestamp: blobRec.timestamp,
209 content_hash: blobRec.content_hash || '',
210 sig: blobRec.sig,
211 seq: 99,
212 stored_at: new Date().toISOString(),
213 }),
214 });
215
216 const result = await verifyWithIcp(id);
217 assert.equal(result.consensus, 'match');
218 assert.equal(result.verified, true);
219 assert.equal(result.sources.blobs.found, true);
220 assert.equal(result.sources.icp.found, true);
221 assert.equal(result.sources.icp.seq, 99);
222 });
223
224 test('returns mismatch when Blobs and ICP disagree', async () => {
225 enableIcpEnv();
226 const { _setTestOverrides, resetClient } = await import(
227 '../hub/gateway/icp-attestation-client.mjs'
228 );
229 resetClient();
230 _setTestOverrides({ anchor: async () => ({ seq: 1 }) });
231
232 const { createAttestation, verifyWithIcp } = await import(
233 '../hub/gateway/attest-store.mjs'
234 );
235 const { id } = await createAttestation('write', 'notes/mismatch-test.md');
236
237 _setTestOverrides({
238 anchor: async () => ({ seq: 1 }),
239 query: async () => ({
240 id,
241 action: 'TAMPERED',
242 path: 'notes/mismatch-test.md',
243 timestamp: '2026-01-01T00:00:00.000Z',
244 content_hash: '',
245 sig: 'wrong',
246 seq: 1,
247 stored_at: new Date().toISOString(),
248 }),
249 });
250
251 const result = await verifyWithIcp(id);
252 assert.equal(result.consensus, 'mismatch');
253 });
254
255 test('returns icp_pending when Blob is pending and ICP not found', async () => {
256 enableIcpEnv();
257 const { _setTestOverrides, resetClient } = await import(
258 '../hub/gateway/icp-attestation-client.mjs'
259 );
260 resetClient();
261 _setTestOverrides({ anchor: async () => null });
262
263 const { createAttestation, verifyWithIcp } = await import(
264 '../hub/gateway/attest-store.mjs'
265 );
266 const { id } = await createAttestation('write', 'notes/pending-test.md');
267
268 _setTestOverrides({ anchor: async () => null, query: async () => null });
269
270 const result = await verifyWithIcp(id);
271 assert.equal(result.consensus, 'icp_pending');
272 assert.equal(result.verified, true);
273 });
274 });
275
276 describe('anchorPendingAttestations', () => {
277 test('returns early when ICP not configured', async () => {
278 delete process.env.ICP_ATTESTATION_CANISTER_ID;
279 delete process.env.ICP_ATTESTATION_KEY;
280
281 const { anchorPendingAttestations } = await import('../hub/gateway/attest-store.mjs');
282 const result = await anchorPendingAttestations(['air-123']);
283 assert.equal(result.anchored, 0);
284 assert.equal(result.errors.length, 1);
285 assert.match(result.errors[0], /not configured/);
286 });
287
288 test('returns early for empty ids list', async () => {
289 enableIcpEnv();
290 const { anchorPendingAttestations } = await import('../hub/gateway/attest-store.mjs');
291 const result = await anchorPendingAttestations([]);
292 assert.equal(result.anchored, 0);
293 assert.equal(result.failed, 0);
294 });
295
296 test('anchors a pending record successfully', async () => {
297 enableIcpEnv();
298 const { _setTestOverrides, resetClient } = await import(
299 '../hub/gateway/icp-attestation-client.mjs'
300 );
301 resetClient();
302 _setTestOverrides({ anchor: async () => null });
303
304 const { createAttestation, anchorPendingAttestations } = await import(
305 '../hub/gateway/attest-store.mjs'
306 );
307 const { id } = await createAttestation('write', 'notes/reconcile-test.md');
308
309 _setTestOverrides({ anchor: async () => ({ seq: 777 }) });
310
311 const result = await anchorPendingAttestations([id]);
312 assert.equal(result.anchored, 1);
313 assert.equal(result.failed, 0);
314
315 const store = globalThis.__knowtation_attest_blob;
316 const raw = await store.get(`attestation/${id}`, { type: 'json' });
317 assert.equal(raw.icp_status, 'anchored');
318 assert.equal(raw.icp_seq, 777);
319 });
320 });
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 2 days ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 2 days ago