delegation-blob-store-l1c.test.mjs
303 lines 10.6 KB
Raw
sha256:baa800aedf841dbf32081aa7b2befa288ac33dfc7175ac55014c55c4d8c742f9 docs: move durable-auth freeze/evidence to local developmen… Human 10 days ago
1 /**
2 * Phase 7C-L1c — hosted delegation store persistence (Netlify Blobs).
3 *
4 * Tiers: unit, integration, e2e, stress, data-integrity, performance, security.
5 */
6 import { describe, it, beforeEach, afterEach } from 'node:test';
7 import assert from 'node:assert/strict';
8 import fs from 'node:fs';
9 import path from 'node:path';
10 import { fileURLToPath } from 'node:url';
11
12 import {
13 DELEGATION_BLOB_FILES,
14 delegationBlobKey,
15 hydrateDelegationStoresFromBlob,
16 mergeGrantsStoreJson,
17 persistDelegationStoresToBlob,
18 withDelegationBlobSync,
19 } from '../hub/bridge/delegation-blob-store.mjs';
20 import {
21 DELEGATION_CONSENTS_FILE,
22 DELEGATION_GRANTS_FILE,
23 handleDelegationGrantMintRequest,
24 seedDelegationFixtures,
25 } from '../lib/agent/delegation.mjs';
26 import { writeDelegationPolicy, makeAgentIdentity, makeDelegationConsent } from './fixtures/agent/delegation-helpers.mjs';
27
28 const __dirname = path.dirname(fileURLToPath(import.meta.url));
29 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-delegation-l1c');
30
31 /** @returns {Map<string, string> & { get: (k: string, o?: { type?: string }) => Promise<string|null>, set: (k: string, v: string) => Promise<void> }} */
32 function makeMockBlobStore() {
33 const map = new Map();
34 return {
35 async get(key, opts) {
36 const v = map.get(key);
37 if (v == null) return null;
38 return opts?.type === 'text' || typeof v === 'string' ? v : null;
39 },
40 async set(key, value) {
41 map.set(key, value);
42 },
43 map,
44 };
45 }
46
47 describe('7C-L1c delegation blob store — unit', () => {
48 it('delegationBlobKey namespaces filenames', () => {
49 assert.equal(delegationBlobKey('hub_delegation_consents.json'), 'delegation/hub_delegation_consents.json');
50 });
51
52 it('DELEGATION_BLOB_FILES includes core index files', () => {
53 assert.ok(DELEGATION_BLOB_FILES.includes(DELEGATION_CONSENTS_FILE));
54 });
55
56 it('mergeGrantsStoreJson keeps local mint when blob is stale', () => {
57 const local = JSON.stringify({
58 vaults: {
59 default: {
60 grants: [{
61 grant_id: 'dgrnt_new',
62 issued_at: '2026-06-28T12:00:00.000Z',
63 revoked_at: null,
64 }],
65 },
66 },
67 });
68 const blob = JSON.stringify({ vaults: { default: { grants: [] } } });
69 const merged = JSON.parse(mergeGrantsStoreJson(local, blob));
70 assert.equal(merged.vaults.default.grants.length, 1);
71 assert.equal(merged.vaults.default.grants[0].grant_id, 'dgrnt_new');
72 });
73
74 it('mergeGrantsStoreJson prefers revoked grant from blob', () => {
75 const local = JSON.stringify({
76 vaults: {
77 default: {
78 grants: [{
79 grant_id: 'dgrnt_x',
80 issued_at: '2026-06-28T12:00:00.000Z',
81 revoked_at: null,
82 }],
83 },
84 },
85 });
86 const blob = JSON.stringify({
87 vaults: {
88 default: {
89 grants: [{
90 grant_id: 'dgrnt_x',
91 issued_at: '2026-06-28T12:00:00.000Z',
92 revoked_at: '2026-06-28T12:05:00.000Z',
93 }],
94 },
95 },
96 });
97 const merged = JSON.parse(mergeGrantsStoreJson(local, blob));
98 assert.equal(merged.vaults.default.grants[0].revoked_at, '2026-06-28T12:05:00.000Z');
99 });
100
101 it('mergeGrantsStoreJson keeps local revoke when blob is stale', () => {
102 const local = JSON.stringify({
103 vaults: {
104 default: {
105 grants: [{
106 grant_id: 'dgrnt_x',
107 issued_at: '2026-06-28T12:00:00.000Z',
108 revoked_at: '2026-06-28T12:05:00.000Z',
109 }],
110 },
111 },
112 });
113 const blob = JSON.stringify({
114 vaults: {
115 default: {
116 grants: [{
117 grant_id: 'dgrnt_x',
118 issued_at: '2026-06-28T12:00:00.000Z',
119 revoked_at: null,
120 }],
121 },
122 },
123 });
124 const merged = JSON.parse(mergeGrantsStoreJson(local, blob));
125 assert.equal(merged.vaults.default.grants[0].revoked_at, '2026-06-28T12:05:00.000Z');
126 });
127 });
128
129 describe('7C-L1c delegation blob store — integration', () => {
130 const dataDir = path.join(tmpRoot, 'integration', 'data');
131 const vaultId = 'Business';
132
133 beforeEach(() => {
134 fs.rmSync(path.join(tmpRoot, 'integration'), { recursive: true, force: true });
135 fs.mkdirSync(dataDir, { recursive: true });
136 writeDelegationPolicy(dataDir);
137 process.env.DELEGATION_ENABLED = '1';
138 });
139
140 afterEach(() => {
141 delete process.env.DELEGATION_ENABLED;
142 });
143
144 it('hydrate merges grants without dropping warm-lambda mint', async () => {
145 const dataDir = path.join(tmpRoot, 'integration', 'merge-data');
146 fs.rmSync(dataDir, { recursive: true, force: true });
147 fs.mkdirSync(dataDir, { recursive: true });
148 const localGrant = JSON.stringify({
149 vaults: {
150 default: {
151 grants: [{
152 grant_id: 'dgrnt_warm',
153 issued_at: '2026-06-28T12:00:00.000Z',
154 revoked_at: null,
155 }],
156 },
157 },
158 });
159 fs.writeFileSync(path.join(dataDir, DELEGATION_GRANTS_FILE), localGrant, 'utf8');
160 const blob = makeMockBlobStore();
161 await blob.set(delegationBlobKey(DELEGATION_GRANTS_FILE), JSON.stringify({ vaults: { default: { grants: [] } } }));
162 await hydrateDelegationStoresFromBlob(blob, dataDir);
163 const merged = JSON.parse(fs.readFileSync(path.join(dataDir, DELEGATION_GRANTS_FILE), 'utf8'));
164 assert.equal(merged.vaults.default.grants[0].grant_id, 'dgrnt_warm');
165 });
166
167 it('persist then hydrate round-trips consent index across cold start', async () => {
168 const identity = makeAgentIdentity({ agentId: 'agent_l1c_smoke01' });
169 const consent = makeDelegationConsent({ consentId: 'dcons_l1c_smoke01', agentId: 'agent_l1c_smoke01' });
170 seedDelegationFixtures(dataDir, vaultId, identity, consent);
171 const blob = makeMockBlobStore();
172 await persistDelegationStoresToBlob(blob, dataDir);
173 fs.rmSync(path.join(dataDir, DELEGATION_CONSENTS_FILE));
174 await hydrateDelegationStoresFromBlob(blob, dataDir);
175 assert.ok(fs.existsSync(path.join(dataDir, DELEGATION_CONSENTS_FILE)));
176 const mint = handleDelegationGrantMintRequest({
177 dataDir,
178 vaultId,
179 consentId: 'dcons_l1c_smoke01',
180 actorAgentId: 'agent_l1c_smoke01',
181 taskRef: 'task_hw_week3',
182 });
183 assert.equal(mint.ok, true);
184 });
185 });
186
187 describe('7C-L1c delegation blob store — e2e', () => {
188 const dataDir = path.join(tmpRoot, 'e2e', 'data');
189 const vaultId = 'Business';
190
191 beforeEach(() => {
192 fs.rmSync(path.join(tmpRoot, 'e2e'), { recursive: true, force: true });
193 fs.mkdirSync(dataDir, { recursive: true });
194 writeDelegationPolicy(dataDir);
195 process.env.DELEGATION_ENABLED = '1';
196 });
197
198 afterEach(() => {
199 delete process.env.DELEGATION_ENABLED;
200 });
201
202 it('withDelegationBlobSync persists mint result for next lambda', async () => {
203 const identity = makeAgentIdentity({ agentId: 'agent_l1c_smoke01' });
204 const consent = makeDelegationConsent({ consentId: 'dcons_l1c_smoke01', agentId: 'agent_l1c_smoke01' });
205 seedDelegationFixtures(dataDir, vaultId, identity, consent);
206 const blob = makeMockBlobStore();
207 const mint = await withDelegationBlobSync({
208 blobStore: blob,
209 dataDir,
210 run: () =>
211 handleDelegationGrantMintRequest({
212 dataDir,
213 vaultId,
214 consentId: 'dcons_l1c_smoke01',
215 actorAgentId: 'agent_l1c_smoke01',
216 taskRef: 'task_hw_week3',
217 }),
218 });
219 assert.equal(mint.ok, true);
220 fs.rmSync(dataDir, { recursive: true, force: true });
221 fs.mkdirSync(dataDir, { recursive: true });
222 writeDelegationPolicy(dataDir);
223 await hydrateDelegationStoresFromBlob(blob, dataDir);
224 const list = handleDelegationGrantMintRequest({
225 dataDir,
226 vaultId,
227 consentId: 'dcons_l1c_smoke01',
228 actorAgentId: 'agent_l1c_smoke01',
229 taskRef: 'task_hw_week3',
230 });
231 assert.equal(list.ok, true);
232 });
233 });
234
235 describe('7C-L1c delegation blob store — stress', () => {
236 it('hydrate/persist handles empty blob store', async () => {
237 const dataDir = path.join(tmpRoot, 'stress', 'data');
238 fs.rmSync(path.join(tmpRoot, 'stress'), { recursive: true, force: true });
239 fs.mkdirSync(dataDir, { recursive: true });
240 await hydrateDelegationStoresFromBlob(null, dataDir);
241 await persistDelegationStoresToBlob(null, dataDir);
242 assert.ok(true);
243 });
244 });
245
246 describe('7C-L1c delegation blob store — data-integrity', () => {
247 it('blob key matches persisted consent JSON', async () => {
248 const dataDir = path.join(tmpRoot, 'integrity', 'data');
249 fs.rmSync(path.join(tmpRoot, 'integrity'), { recursive: true, force: true });
250 fs.mkdirSync(dataDir, { recursive: true });
251 writeDelegationPolicy(dataDir);
252 const identity = makeAgentIdentity({ agentId: 'agent_l1c_smoke01' });
253 const consent = makeDelegationConsent({ consentId: 'dcons_l1c_smoke01', agentId: 'agent_l1c_smoke01' });
254 seedDelegationFixtures(dataDir, 'Business', identity, consent);
255 const blob = makeMockBlobStore();
256 await persistDelegationStoresToBlob(blob, dataDir);
257 const raw = blob.map.get(delegationBlobKey(DELEGATION_CONSENTS_FILE));
258 assert.ok(typeof raw === 'string' && raw.includes('dcons_l1c_smoke01'));
259 });
260 });
261
262 describe('7C-L1c delegation blob store — performance', () => {
263 it('withDelegationBlobSync completes under 500ms for fixture mint', async () => {
264 const dataDir = path.join(tmpRoot, 'perf', 'data');
265 fs.rmSync(path.join(tmpRoot, 'perf'), { recursive: true, force: true });
266 fs.mkdirSync(dataDir, { recursive: true });
267 writeDelegationPolicy(dataDir);
268 const identity = makeAgentIdentity({ agentId: 'agent_l1c_smoke01' });
269 const consent = makeDelegationConsent({ consentId: 'dcons_l1c_smoke01', agentId: 'agent_l1c_smoke01' });
270 seedDelegationFixtures(dataDir, 'Business', identity, consent);
271 const blob = makeMockBlobStore();
272 const t0 = Date.now();
273 await withDelegationBlobSync({
274 blobStore: blob,
275 dataDir,
276 run: () =>
277 handleDelegationGrantMintRequest({
278 dataDir,
279 vaultId: 'Business',
280 consentId: 'dcons_l1c_smoke01',
281 actorAgentId: 'agent_l1c_smoke01',
282 taskRef: 'task_hw_week3',
283 }),
284 });
285 assert.ok(Date.now() - t0 < 500);
286 });
287 });
288
289 describe('7C-L1c delegation blob store — security', () => {
290 it('hydrate ignores malformed blob entries without throwing', async () => {
291 const dataDir = path.join(tmpRoot, 'security', 'data');
292 fs.rmSync(path.join(tmpRoot, 'security'), { recursive: true, force: true });
293 fs.mkdirSync(dataDir, { recursive: true });
294 const blob = {
295 async get() {
296 throw new Error('blob read denied');
297 },
298 async set() {},
299 };
300 await hydrateDelegationStoresFromBlob(blob, dataDir);
301 assert.ok(true);
302 });
303 });
File History 1 commit
sha256:baa800aedf841dbf32081aa7b2befa288ac33dfc7175ac55014c55c4d8c742f9 docs: move durable-auth freeze/evidence to local developmen… Human 10 days ago