delegation-blob-store-l1c.test.mjs
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