Spaces:
Running
Running
| import type { AgentImageResponse } from './agent-api-contracts'; | |
| import { SQLITE_SCHEMA, SqliteAgentStateStore } from './agent-state-sqlite'; | |
| import { hashAgentPayload, type AgentArtifactRecord } from './agent-state-store'; | |
| import Database from 'better-sqlite3'; | |
| import assert from 'node:assert/strict'; | |
| import { access, mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises'; | |
| import os from 'node:os'; | |
| import path from 'node:path'; | |
| import { after, before, describe, it } from 'node:test'; | |
| describe('SqliteAgentStateStore', () => { | |
| let tempDir = ''; | |
| let store: SqliteAgentStateStore; | |
| before(async () => { | |
| tempDir = await mkdtemp(path.join(os.tmpdir(), 'agent-state-sqlite-')); | |
| store = new SqliteAgentStateStore(path.join(tempDir, 'agent.sqlite')); | |
| await store.init(); | |
| }); | |
| after(async () => { | |
| await store.close(); | |
| await rm(tempDir, { recursive: true, force: true }); | |
| }); | |
| it('acquires, completes, and replays idempotent requests', async () => { | |
| const requestJson = { prompt: 'test' }; | |
| const requestHash = hashAgentPayload(requestJson); | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-1', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 60, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| const artifact: AgentArtifactRecord = { | |
| id: 'artifact-1', | |
| requestId: begin.record.requestId, | |
| filename: '1715400000000-abcdef1234567890-0.png', | |
| filepath: '/tmp/image.png', | |
| contentUrl: '/api/agent/artifacts/artifact-1/content', | |
| metadataUrl: '/api/agent/artifacts/artifact-1', | |
| outputFormat: 'png', | |
| mimeType: 'image/png', | |
| sizeBytes: 4, | |
| width: 1, | |
| height: 1, | |
| model: 'gpt-image-2', | |
| promptHash: 'hash', | |
| createdAt: '2026-05-12T00:00:01.000Z' | |
| }; | |
| const response: AgentImageResponse = { | |
| request_id: begin.record.requestId, | |
| idempotency_key: 'idem-1', | |
| cached: false, | |
| images: [], | |
| created_at: '2026-05-12T00:00:01.000Z' | |
| }; | |
| await store.completeRequest({ requestId: begin.record.requestId, response, artifacts: [artifact] }); | |
| const replay = await store.beginRequest({ | |
| idempotencyKey: 'idem-1', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 60, | |
| now: new Date('2026-05-12T00:00:02.000Z') | |
| }); | |
| assert.equal(replay.type, 'replay'); | |
| if (replay.type !== 'replay') throw new Error('expected replay'); | |
| assert.equal(replay.response.request_id, begin.record.requestId); | |
| assert.deepEqual(await store.getArtifact('artifact-1'), artifact); | |
| }); | |
| it('rejects the same idempotency key with a different request hash', async () => { | |
| const result = await store.beginRequest({ | |
| idempotencyKey: 'idem-1', | |
| requestHash: hashAgentPayload({ prompt: 'different' }), | |
| mode: 'generate', | |
| requestJson: { prompt: 'different' }, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| assert.equal(result.type, 'conflict'); | |
| }); | |
| it('rejects artifact metadata for unknown requests', async () => { | |
| await assert.rejects( | |
| () => | |
| store.saveArtifacts([ | |
| { | |
| id: 'artifact-missing-request', | |
| requestId: 'missing-request', | |
| filename: '1715400000000-missing-0.png', | |
| filepath: '/tmp/missing.png', | |
| contentUrl: '/api/agent/artifacts/artifact-missing-request/content', | |
| metadataUrl: '/api/agent/artifacts/artifact-missing-request', | |
| outputFormat: 'png', | |
| mimeType: 'image/png', | |
| sizeBytes: 1, | |
| width: 1, | |
| height: 1, | |
| model: 'gpt-image-2', | |
| promptHash: 'hash', | |
| createdAt: '2026-05-12T00:00:00.000Z' | |
| } | |
| ]), | |
| /FOREIGN KEY/ | |
| ); | |
| }); | |
| it('lists sorted artifact filepaths for cleanup protection', async () => { | |
| const requestJson = { prompt: 'sqlite cleanup protection' }; | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-sqlite-cleanup-protection', | |
| requestHash: hashAgentPayload(requestJson), | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| const firstPath = path.join(tempDir, 'generated-images', 'cleanup-a.png'); | |
| const secondPath = path.join(tempDir, 'generated-images', 'cleanup-b.png'); | |
| await store.saveArtifacts([ | |
| { | |
| ...buildArtifact({ | |
| id: 'artifact-sqlite-cleanup-b', | |
| requestId: begin.record.requestId, | |
| filename: 'cleanup-b.png' | |
| }), | |
| filepath: secondPath | |
| }, | |
| { | |
| ...buildArtifact({ | |
| id: 'artifact-sqlite-cleanup-a', | |
| requestId: begin.record.requestId, | |
| filename: 'cleanup-a.png' | |
| }), | |
| filepath: firstPath | |
| } | |
| ]); | |
| const protectedPaths = (await store.listArtifactFilepaths()).filter( | |
| (filepath) => filepath === firstPath || filepath === secondPath | |
| ); | |
| assert.deepEqual(protectedPaths, [firstPath, secondPath]); | |
| }); | |
| it('upserts feedback rows by target type and id', async () => { | |
| const first = { | |
| targetType: 'page_request' as const, | |
| targetId: 'web-sqlite-feedback', | |
| value: 'usable' as const, | |
| note: 'works for review', | |
| source: 'webui' as const, | |
| updatedAt: '2026-05-12T00:00:00.000Z' | |
| }; | |
| const second = { | |
| ...first, | |
| value: 'needs_revision' as const, | |
| note: 'needs crop fix', | |
| updatedAt: '2026-05-12T00:02:00.000Z' | |
| }; | |
| await store.upsertFeedbackBatch([first, second, { ...first, note: 'stale retry' }]); | |
| assert.deepEqual(await store.readFeedback('page_request', 'web-sqlite-feedback'), second); | |
| assert.equal( | |
| await store.deleteFeedbackByTargets([{ targetType: 'page_request', targetId: 'web-sqlite-feedback' }], { | |
| deletedAt: '2026-05-12T00:01:00.000Z' | |
| }), | |
| 0 | |
| ); | |
| assert.deepEqual(await store.readFeedback('page_request', 'web-sqlite-feedback'), second); | |
| assert.equal( | |
| await store.deleteFeedbackByTargets([{ targetType: 'page_request', targetId: 'web-sqlite-feedback' }]), | |
| 1 | |
| ); | |
| assert.equal(await store.readFeedback('page_request', 'web-sqlite-feedback'), undefined); | |
| }); | |
| it('replays stored failures without reacquiring the same idempotency key', async () => { | |
| const requestJson = { prompt: 'stored failure' }; | |
| const requestHash = hashAgentPayload(requestJson); | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-stored-failure', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| await store.failRequest({ | |
| requestId: begin.record.requestId, | |
| error: { | |
| error: { | |
| code: 'unexpected_error', | |
| message: 'stored failure', | |
| retryable: true, | |
| request_id: begin.record.requestId | |
| } | |
| } | |
| }); | |
| const replay = await store.beginRequest({ | |
| idempotencyKey: 'idem-stored-failure', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| assert.equal(replay.type, 'failed'); | |
| if (replay.type !== 'failed') throw new Error('expected failed'); | |
| assert.equal(replay.error.error.message, 'stored failure'); | |
| }); | |
| it('marks expired running requests as orphaned', async () => { | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-expired', | |
| requestHash: hashAgentPayload({ prompt: 'expired' }), | |
| mode: 'generate', | |
| requestJson: { prompt: 'expired' }, | |
| leaseMs: 1000, | |
| ttlSeconds: 60, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| const recovered = await store.recoverExpiredRequests(new Date('2026-05-12T00:00:02.000Z')); | |
| assert.ok(recovered >= 1); | |
| const reacquired = await store.beginRequest({ | |
| idempotencyKey: 'idem-expired', | |
| requestHash: hashAgentPayload({ prompt: 'expired' }), | |
| mode: 'generate', | |
| requestJson: { prompt: 'expired' }, | |
| leaseMs: 1000, | |
| ttlSeconds: 60, | |
| now: new Date('2026-05-12T00:00:03.000Z') | |
| }); | |
| assert.equal(reacquired.type, 'acquired'); | |
| }); | |
| it('keeps refreshed running requests out of recovery', async () => { | |
| const requestJson = { prompt: 'sqlite lease refresh' }; | |
| const requestHash = hashAgentPayload(requestJson); | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-sqlite-refresh', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 100, | |
| ttlSeconds: 60, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| assert.equal( | |
| await store.refreshRequestLease({ | |
| requestId: begin.record.requestId, | |
| leaseMs: 1000, | |
| now: new Date('2026-05-12T00:00:00.050Z') | |
| }), | |
| true | |
| ); | |
| await store.recoverExpiredRequests(new Date('2026-05-12T00:00:00.200Z')); | |
| const retry = await store.beginRequest({ | |
| idempotencyKey: 'idem-sqlite-refresh', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 100, | |
| ttlSeconds: 60, | |
| now: new Date('2026-05-12T00:00:00.200Z') | |
| }); | |
| assert.equal(retry.type, 'in_progress'); | |
| }); | |
| it('recovers expired running requests with artifact metadata as succeeded', async () => { | |
| const requestJson = { prompt: 'recover artifact' }; | |
| const requestHash = hashAgentPayload(requestJson); | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-recover-artifact', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 60, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| await store.saveArtifacts([ | |
| { | |
| id: 'artifact-recovered', | |
| requestId: begin.record.requestId, | |
| filename: '1715400000000-recovered-0.png', | |
| filepath: '/tmp/recovered.png', | |
| contentUrl: '/api/agent/artifacts/artifact-recovered/content', | |
| metadataUrl: '/api/agent/artifacts/artifact-recovered', | |
| outputFormat: 'png', | |
| mimeType: 'image/png', | |
| sizeBytes: 8, | |
| width: 2, | |
| height: 2, | |
| model: 'gpt-image-2', | |
| promptHash: 'hash', | |
| createdAt: '2026-05-12T00:00:01.000Z' | |
| } | |
| ]); | |
| const recovered = await store.recoverExpiredRequests(new Date('2026-05-12T00:00:02.000Z')); | |
| assert.ok(recovered >= 1); | |
| const replay = await store.beginRequest({ | |
| idempotencyKey: 'idem-recover-artifact', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 60, | |
| now: new Date('2026-05-12T00:00:03.000Z') | |
| }); | |
| assert.equal(replay.type, 'replay'); | |
| if (replay.type !== 'replay') throw new Error('expected replay'); | |
| assert.equal(replay.response.request_id, begin.record.requestId); | |
| assert.equal(replay.response.images[0].id, 'artifact-recovered'); | |
| }); | |
| it('purges expired terminal requests and their artifact metadata', async () => { | |
| const requestJson = { prompt: 'purge expired' }; | |
| const requestHash = hashAgentPayload(requestJson); | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-purge-expired', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 1, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| await store.completeRequest({ | |
| requestId: begin.record.requestId, | |
| response: { | |
| request_id: begin.record.requestId, | |
| idempotency_key: 'idem-purge-expired', | |
| cached: false, | |
| images: [], | |
| created_at: '2026-05-12T00:00:00.500Z' | |
| }, | |
| artifacts: [ | |
| { | |
| id: 'artifact-purge-expired', | |
| requestId: begin.record.requestId, | |
| filename: '1715400000000-purge-0.png', | |
| filepath: '/tmp/purge.png', | |
| contentUrl: '/api/agent/artifacts/artifact-purge-expired/content', | |
| metadataUrl: '/api/agent/artifacts/artifact-purge-expired', | |
| outputFormat: 'png', | |
| mimeType: 'image/png', | |
| sizeBytes: 1, | |
| width: 1, | |
| height: 1, | |
| model: 'gpt-image-2', | |
| promptHash: 'hash', | |
| createdAt: '2026-05-12T00:00:00.500Z' | |
| } | |
| ], | |
| now: new Date('2026-05-12T00:00:00.500Z') | |
| }); | |
| const purged = await store.purgeExpiredRequests(new Date('2026-05-12T00:00:02.000Z')); | |
| assert.equal(purged, 1); | |
| assert.equal(await store.getArtifact('artifact-purge-expired'), undefined); | |
| }); | |
| it('purges feedback rows for expired agent requests and artifacts', async () => { | |
| const requestJson = { prompt: 'sqlite feedback purge' }; | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-sqlite-feedback-purge', | |
| requestHash: hashAgentPayload(requestJson), | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 1, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| const artifact = buildArtifact({ | |
| id: 'artifact-sqlite-feedback-purge', | |
| requestId: begin.record.requestId, | |
| filename: 'sqlite-feedback-purge.png' | |
| }); | |
| await store.completeRequest({ | |
| requestId: begin.record.requestId, | |
| response: { | |
| request_id: begin.record.requestId, | |
| idempotency_key: 'idem-sqlite-feedback-purge', | |
| cached: false, | |
| images: [], | |
| created_at: '2026-05-12T00:00:00.500Z' | |
| }, | |
| artifacts: [artifact], | |
| now: new Date('2026-05-12T00:00:00.500Z') | |
| }); | |
| await store.upsertFeedback({ | |
| targetType: 'agent_request', | |
| targetId: begin.record.requestId, | |
| value: 'usable', | |
| source: 'agent', | |
| updatedAt: '2026-05-12T00:00:00.600Z' | |
| }); | |
| await store.upsertFeedback({ | |
| targetType: 'agent_artifact', | |
| targetId: artifact.id, | |
| value: 'needs_revision', | |
| source: 'agent', | |
| updatedAt: '2026-05-12T00:00:00.700Z' | |
| }); | |
| assert.equal(await store.purgeExpiredRequests(new Date('2026-05-12T00:00:02.000Z')), 1); | |
| assert.equal(await store.readFeedback('agent_request', begin.record.requestId), undefined); | |
| assert.equal(await store.readFeedback('agent_artifact', artifact.id), undefined); | |
| }); | |
| it('deletes artifact feedback when deleting artifact metadata', async () => { | |
| const requestJson = { prompt: 'sqlite artifact feedback delete' }; | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-sqlite-artifact-feedback-delete', | |
| requestHash: hashAgentPayload(requestJson), | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| const artifact = buildArtifact({ | |
| id: 'artifact-sqlite-feedback-delete', | |
| requestId: begin.record.requestId, | |
| filename: 'sqlite-feedback-delete.png' | |
| }); | |
| await store.saveArtifacts([artifact]); | |
| await store.upsertFeedback({ | |
| targetType: 'agent_artifact', | |
| targetId: artifact.id, | |
| value: 'usable', | |
| source: 'agent', | |
| updatedAt: '2026-05-12T00:00:00.000Z' | |
| }); | |
| assert.equal(await store.deleteArtifact(artifact.id), true); | |
| assert.equal(await store.readFeedback('agent_artifact', artifact.id), undefined); | |
| }); | |
| it('deletes artifact files when purging expired terminal requests', async () => { | |
| const requestJson = { prompt: 'purge file' }; | |
| const requestHash = hashAgentPayload(requestJson); | |
| const artifactPath = path.join(process.cwd(), 'generated-images', 'purge-file.png'); | |
| await mkdir(path.dirname(artifactPath), { recursive: true }); | |
| await writeFile(artifactPath, 'stale image'); | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-purge-file', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 1, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| await store.completeRequest({ | |
| requestId: begin.record.requestId, | |
| response: { | |
| request_id: begin.record.requestId, | |
| idempotency_key: 'idem-purge-file', | |
| cached: false, | |
| images: [], | |
| created_at: '2026-05-12T00:00:00.500Z' | |
| }, | |
| artifacts: [ | |
| { | |
| id: 'artifact-purge-file', | |
| requestId: begin.record.requestId, | |
| filename: 'purge-file.png', | |
| filepath: artifactPath, | |
| contentUrl: '/api/agent/artifacts/artifact-purge-file/content', | |
| metadataUrl: '/api/agent/artifacts/artifact-purge-file', | |
| outputFormat: 'png', | |
| mimeType: 'image/png', | |
| sizeBytes: 10, | |
| width: 1, | |
| height: 1, | |
| model: 'gpt-image-2', | |
| promptHash: 'hash', | |
| createdAt: '2026-05-12T00:00:00.500Z' | |
| } | |
| ], | |
| now: new Date('2026-05-12T00:00:00.500Z') | |
| }); | |
| const purged = await store.purgeExpiredRequests(new Date('2026-05-12T00:00:02.000Z')); | |
| assert.equal(purged, 1); | |
| await assert.rejects(() => access(artifactPath)); | |
| }); | |
| it('purges directory artifact paths through the same relocation flow', async () => { | |
| const requestJson = { prompt: 'purge directory artifact' }; | |
| const requestHash = hashAgentPayload(requestJson); | |
| const artifactBaseName = `purge-directory-artifact-${crypto.randomUUID()}`; | |
| const artifactParentPath = path.join(process.cwd(), 'generated-images', '.sqlite-purge-dir-test'); | |
| const artifactPath = path.join(artifactParentPath, artifactBaseName); | |
| await mkdir(artifactPath, { recursive: true }); | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-purge-directory-artifact', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 1, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| await store.completeRequest({ | |
| requestId: begin.record.requestId, | |
| response: { | |
| request_id: begin.record.requestId, | |
| idempotency_key: 'idem-purge-directory-artifact', | |
| cached: false, | |
| images: [], | |
| created_at: '2026-05-12T00:00:00.500Z' | |
| }, | |
| artifacts: [ | |
| { | |
| id: 'artifact-purge-directory-artifact', | |
| requestId: begin.record.requestId, | |
| filename: 'purge-directory-artifact', | |
| filepath: artifactPath, | |
| contentUrl: '/api/agent/artifacts/artifact-purge-directory-artifact/content', | |
| metadataUrl: '/api/agent/artifacts/artifact-purge-directory-artifact', | |
| outputFormat: 'png', | |
| mimeType: 'image/png', | |
| sizeBytes: 10, | |
| width: 1, | |
| height: 1, | |
| model: 'gpt-image-2', | |
| promptHash: 'hash', | |
| createdAt: '2026-05-12T00:00:00.500Z' | |
| } | |
| ], | |
| now: new Date('2026-05-12T00:00:00.500Z') | |
| }); | |
| try { | |
| const purged = await store.purgeExpiredRequests(new Date('2026-05-12T00:00:02.000Z')); | |
| assert.equal(purged, 1); | |
| await assert.rejects(() => access(artifactPath)); | |
| assert.equal(await store.getArtifact('artifact-purge-directory-artifact'), undefined); | |
| const entries = await readdir(artifactParentPath); | |
| assert.deepEqual( | |
| entries.filter((entry) => entry.startsWith(`${artifactBaseName}.purge-`)), | |
| [] | |
| ); | |
| } finally { | |
| await rm(artifactParentPath, { recursive: true, force: true }); | |
| } | |
| }); | |
| it('does not replace a different artifact row when filename collides', async () => { | |
| const requestJsonA = { prompt: 'file collision A' }; | |
| const requestJsonB = { prompt: 'file collision B' }; | |
| const beginA = await store.beginRequest({ | |
| idempotencyKey: 'idem-collision-a', | |
| requestHash: hashAgentPayload(requestJsonA), | |
| mode: 'generate', | |
| requestJson: requestJsonA, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| const beginB = await store.beginRequest({ | |
| idempotencyKey: 'idem-collision-b', | |
| requestHash: hashAgentPayload(requestJsonB), | |
| mode: 'generate', | |
| requestJson: requestJsonB, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| assert.equal(beginA.type, 'acquired'); | |
| assert.equal(beginB.type, 'acquired'); | |
| if (beginA.type !== 'acquired' || beginB.type !== 'acquired') throw new Error('expected acquired'); | |
| const sharedFilename = 'shared-name.png'; | |
| await store.completeRequest({ | |
| requestId: beginA.record.requestId, | |
| response: { | |
| request_id: beginA.record.requestId, | |
| idempotency_key: 'idem-collision-a', | |
| cached: false, | |
| images: [], | |
| created_at: '2026-05-12T00:00:00.500Z' | |
| }, | |
| artifacts: [ | |
| buildArtifact({ | |
| id: 'artifact-collision-a', | |
| requestId: beginA.record.requestId, | |
| filename: sharedFilename | |
| }) | |
| ] | |
| }); | |
| await assert.rejects( | |
| () => | |
| store.completeRequest({ | |
| requestId: beginB.record.requestId, | |
| response: { | |
| request_id: beginB.record.requestId, | |
| idempotency_key: 'idem-collision-b', | |
| cached: false, | |
| images: [], | |
| created_at: '2026-05-12T00:00:00.600Z' | |
| }, | |
| artifacts: [ | |
| buildArtifact({ | |
| id: 'artifact-collision-b', | |
| requestId: beginB.record.requestId, | |
| filename: sharedFilename | |
| }) | |
| ] | |
| }), | |
| /UNIQUE/ | |
| ); | |
| assert.ok(await store.getArtifact('artifact-collision-a')); | |
| assert.equal(await store.getArtifact('artifact-collision-b'), undefined); | |
| }); | |
| it('restores moved artifact files when purge fails after file relocation', async () => { | |
| const requestJson = { prompt: 'purge restore on failure' }; | |
| const requestHash = hashAgentPayload(requestJson); | |
| const artifactPath = path.join( | |
| process.cwd(), | |
| 'generated-images', | |
| '.sqlite-purge-test', | |
| `${crypto.randomUUID()}.png` | |
| ); | |
| await mkdir(path.dirname(artifactPath), { recursive: true }); | |
| await writeFile(artifactPath, 'stale image'); | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-purge-restore-file', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 1, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| await store.completeRequest({ | |
| requestId: begin.record.requestId, | |
| response: { | |
| request_id: begin.record.requestId, | |
| idempotency_key: 'idem-purge-restore-file', | |
| cached: false, | |
| images: [], | |
| created_at: '2026-05-12T00:00:00.500Z' | |
| }, | |
| artifacts: [ | |
| { | |
| id: 'artifact-purge-restore-file', | |
| requestId: begin.record.requestId, | |
| filename: 'sqlite-purge-restore.png', | |
| filepath: artifactPath, | |
| contentUrl: '/api/agent/artifacts/artifact-purge-restore-file/content', | |
| metadataUrl: '/api/agent/artifacts/artifact-purge-restore-file', | |
| outputFormat: 'png', | |
| mimeType: 'image/png', | |
| sizeBytes: 1, | |
| width: 1, | |
| height: 1, | |
| model: 'gpt-image-2', | |
| promptHash: 'hash', | |
| createdAt: '2026-05-12T00:00:00.500Z' | |
| } | |
| ], | |
| now: new Date('2026-05-12T00:00:00.500Z') | |
| }); | |
| try { | |
| const db = ( | |
| store as unknown as { | |
| db: { exec(sql: string): void; prepare(sql: string): { run(...args: unknown[]): void } }; | |
| } | |
| ).db; | |
| db.exec( | |
| "CREATE TRIGGER purge_fail_guard BEFORE DELETE ON agent_requests BEGIN SELECT RAISE(ABORT, 'purge failed'); END;" | |
| ); | |
| await assert.rejects( | |
| () => store.purgeExpiredRequests(new Date('2026-05-12T00:00:02.000Z')), | |
| /purge failed/ | |
| ); | |
| await assert.doesNotReject(() => access(artifactPath)); | |
| assert.ok(await store.getArtifact('artifact-purge-restore-file')); | |
| } finally { | |
| const db = (store as unknown as { db: { exec(sql: string): void } }).db; | |
| db.exec('DROP TRIGGER IF EXISTS purge_fail_guard'); | |
| await rm(path.dirname(artifactPath), { recursive: true, force: true }); | |
| } | |
| }); | |
| it('continues purging expired requests after a guarded purge failure', async () => { | |
| const requestJson = { prompt: 'purge after guarded failure' }; | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-purge-after-guarded-failure', | |
| requestHash: hashAgentPayload(requestJson), | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 1, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| await store.completeRequest({ | |
| requestId: begin.record.requestId, | |
| response: { | |
| request_id: begin.record.requestId, | |
| idempotency_key: 'idem-purge-after-guarded-failure', | |
| cached: false, | |
| images: [], | |
| created_at: '2026-05-12T00:00:00.500Z' | |
| }, | |
| artifacts: [], | |
| now: new Date('2026-05-12T00:00:00.500Z') | |
| }); | |
| await assert.doesNotReject(() => store.purgeExpiredRequests(new Date('2026-05-12T00:00:02.000Z'))); | |
| }); | |
| it('stores and reads image share metadata', async () => { | |
| await store.createImageShareRecord({ | |
| token: 'b'.repeat(24), | |
| sourceFilename: 'source.png', | |
| contentFilename: `${'b'.repeat(24)}.png`, | |
| mimeType: 'image/png', | |
| sizeBytes: 12, | |
| createdAt: '2026-05-14T08:00:00.000Z', | |
| accessCodeRequired: true, | |
| expiresAt: '2026-05-14T09:00:00.000Z', | |
| accessCodeSalt: 'salt', | |
| accessCodeHash: 'hash' | |
| }); | |
| const record = await store.readImageShareRecord('b'.repeat(24)); | |
| assert.ok(record); | |
| assert.equal(record.sourceFilename, 'source.png'); | |
| assert.equal(record.accessCodeRequired, true); | |
| assert.equal(record.expiresAt, '2026-05-14T09:00:00.000Z'); | |
| }); | |
| it('rejects invalid protected image share metadata at the schema boundary', async () => { | |
| const db = new Database(path.join(tempDir, 'invalid-share-schema.sqlite')); | |
| db.exec(SQLITE_SCHEMA); | |
| try { | |
| assert.throws( | |
| () => | |
| db | |
| .prepare( | |
| `INSERT INTO image_shares | |
| (token, source_filename, content_filename, mime_type, size_bytes, created_at, access_code_required) | |
| VALUES (?, ?, ?, ?, ?, ?, ?)` | |
| ) | |
| .run( | |
| 'f'.repeat(24), | |
| 'source.png', | |
| `${'f'.repeat(24)}.png`, | |
| 'image/png', | |
| 12, | |
| '2026-05-14T08:00:00.000Z', | |
| 1 | |
| ), | |
| /CHECK/ | |
| ); | |
| } finally { | |
| db.close(); | |
| } | |
| }); | |
| it('rejects applied migration checksum drift', async () => { | |
| const dbPath = path.join(tempDir, 'migration-checksum-drift.sqlite'); | |
| const db = new Database(dbPath); | |
| db.exec(` | |
| CREATE TABLE state_schema_migrations ( | |
| id TEXT PRIMARY KEY, | |
| checksum TEXT NOT NULL, | |
| applied_at TEXT NOT NULL | |
| ); | |
| INSERT INTO state_schema_migrations (id, checksum, applied_at) | |
| VALUES ('001_agent_state_core', 'bad-checksum', '2026-05-14T08:00:00.000Z'); | |
| `); | |
| db.close(); | |
| const drifted = new SqliteAgentStateStore(dbPath); | |
| await assert.rejects(() => drifted.init(), /checksum/); | |
| }); | |
| it('rejects attempts to rewrite an existing artifact id with different metadata', async () => { | |
| const requestJsonA = { prompt: 'stable artifact A' }; | |
| const requestJsonB = { prompt: 'stable artifact B' }; | |
| const beginA = await store.beginRequest({ | |
| idempotencyKey: 'idem-stable-artifact-a', | |
| requestHash: hashAgentPayload(requestJsonA), | |
| mode: 'generate', | |
| requestJson: requestJsonA, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| const beginB = await store.beginRequest({ | |
| idempotencyKey: 'idem-stable-artifact-b', | |
| requestHash: hashAgentPayload(requestJsonB), | |
| mode: 'generate', | |
| requestJson: requestJsonB, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| assert.equal(beginA.type, 'acquired'); | |
| assert.equal(beginB.type, 'acquired'); | |
| if (beginA.type !== 'acquired' || beginB.type !== 'acquired') throw new Error('expected acquired'); | |
| const first = buildArtifact({ | |
| id: 'artifact-stable-sqlite', | |
| requestId: beginA.record.requestId, | |
| filename: 'stable-sqlite-a.png' | |
| }); | |
| const rewritten = buildArtifact({ | |
| id: 'artifact-stable-sqlite', | |
| requestId: beginB.record.requestId, | |
| filename: 'stable-sqlite-b.png' | |
| }); | |
| await store.saveArtifacts([first]); | |
| await assert.rejects(() => store.saveArtifacts([rewritten]), /artifact metadata conflict/); | |
| assert.deepEqual(await store.getArtifact('artifact-stable-sqlite'), first); | |
| }); | |
| it('deletes expired image share records and lists active share records', async () => { | |
| await store.createImageShareRecord({ | |
| token: 'd'.repeat(24), | |
| sourceFilename: 'expired.png', | |
| contentFilename: `${'d'.repeat(24)}.png`, | |
| mimeType: 'image/png', | |
| sizeBytes: 12, | |
| createdAt: '2026-05-14T08:00:00.000Z', | |
| accessCodeRequired: false, | |
| expiresAt: '2026-05-14T09:00:00.000Z' | |
| }); | |
| await store.createImageShareRecord({ | |
| token: 'e'.repeat(24), | |
| sourceFilename: 'active.png', | |
| contentFilename: `${'e'.repeat(24)}.png`, | |
| mimeType: 'image/png', | |
| sizeBytes: 12, | |
| createdAt: '2026-05-14T08:00:00.000Z', | |
| accessCodeRequired: false, | |
| expiresAt: '2026-05-14T10:00:00.000Z' | |
| }); | |
| const expired = await store.deleteExpiredImageShareRecords('2026-05-14T09:00:01.000Z'); | |
| const active = await store.listImageShareRecords(); | |
| assert.equal( | |
| expired.some((record) => record.token === 'd'.repeat(24)), | |
| true | |
| ); | |
| assert.equal( | |
| active.some((record) => record.token === 'e'.repeat(24)), | |
| true | |
| ); | |
| }); | |
| it('records schema migrations and keeps repeated init idempotent', async () => { | |
| const dbPath = path.join(tempDir, 'migration-idempotent.sqlite'); | |
| const first = new SqliteAgentStateStore(dbPath); | |
| await first.init(); | |
| const second = new SqliteAgentStateStore(dbPath); | |
| await second.init(); | |
| const db = new Database(dbPath, { readonly: true }); | |
| try { | |
| const rows = db.prepare('SELECT id FROM state_schema_migrations ORDER BY id ASC').all() as Array<{ | |
| id: string; | |
| }>; | |
| assert.deepEqual( | |
| rows.map((row) => row.id), | |
| ['001_agent_state_core', '002_image_shares', '003_result_feedback'] | |
| ); | |
| } finally { | |
| db.close(); | |
| } | |
| }); | |
| }); | |
| function buildArtifact(input: { id: string; requestId: string; filename: string }): AgentArtifactRecord { | |
| return { | |
| id: input.id, | |
| requestId: input.requestId, | |
| filename: input.filename, | |
| filepath: path.join(process.cwd(), 'generated-images', `${input.id}.png`), | |
| contentUrl: `/api/agent/artifacts/${input.id}/content`, | |
| metadataUrl: `/api/agent/artifacts/${input.id}`, | |
| outputFormat: 'png', | |
| mimeType: 'image/png', | |
| sizeBytes: 1, | |
| width: 1, | |
| height: 1, | |
| model: 'gpt-image-2', | |
| promptHash: 'hash', | |
| createdAt: '2026-05-12T00:00:00.500Z' | |
| }; | |
| } | |