import assert from 'node:assert/strict' import { mkdtemp, readdir, readFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { test } from 'node:test' import { migrate, openDatabase } from '../shared/db.ts' import type { AuditEvent, ChatMessageIn, DeploymentEventMirror } from '../shared/events.ts' import { fixedClock } from '../shared/time.ts' import { createOps } from './ops.ts' async function freshOps(clockIso = '2026-09-12T12:00:00Z') { const dir = await mkdtemp(join(tmpdir(), 'will-ops-')) const db = openDatabase(join(dir, 'will.db')) migrate(db) const clock = fixedClock(clockIso) const ops = createOps( db, { dataDir: dir, archiveDir: join(dir, 'archive'), mediaDir: join(dir, 'media') }, clock, ) return { ops, dir, db } } function audit(id: string, ts: string, kind = 'tool'): AuditEvent { return { id, ts, kind, payload: { note: `payload-${id}` }, provenance: { revision: 'r1', worktreeHead: 'abc', worktreeDirty: false }, } } test('audit ingest is idempotent by event id and assigns seqs', async () => { const { ops } = await freshOps() const r1 = ops.ingestAudit([ audit('a', '2026-09-12T10:00:00Z'), audit('b', '2026-09-12T11:00:00Z'), ]) assert.equal(r1.accepted, 2) const r2 = ops.ingestAudit([audit('a', '2026-09-12T10:00:00Z')]) assert.equal(r2.accepted, 0) assert.equal(r2.duplicates, 1) assert.equal(ops.lastSeq(), 2) const since = ops.eventsSince(0, 10) assert.equal(since.length, 2) assert.equal((since[0]?.payload as { note: string }).note, 'payload-a') }) test('recall searches beyond the newest page in hot data and archives', async () => { const { ops } = await freshOps() const recent = Array.from({ length: 20 }, (_, i) => audit(`noise-${i}`, `2026-09-${String(i + 1).padStart(2, '0')}T00:00:00Z`), ) ops.ingestAudit([ audit('cold-needle', '2026-01-01T00:00:00Z', 'forgotten-audit'), audit('hot-needle', '2026-08-01T00:00:00Z', 'remembered-audit'), ...recent, ]) ops.ingestChat([ { id: 'chat-cold-needle', chatId: -100, messageId: 1, direction: 'in', ts: '2024-01-01T00:00:00Z', senderName: 'old friend', text: 'remember this phrase', }, ...recent.map((_, i) => ({ id: `chat-noise-${i}`, chatId: -100, messageId: i + 2, direction: 'in' as const, ts: `2026-08-${String(i + 1).padStart(2, '0')}T00:00:00Z`, text: 'routine chatter', })), ]) const hot = await ops.recall({ kind: 'audit', q: 'remembered-audit', limit: 2 }) assert.deepEqual( (hot.rows as { id: string }[]).map((r) => r.id), ['hot-needle'], ) await ops.archiveCycle() const coldAudit = await ops.recall({ kind: 'audit', q: 'forgotten-audit', limit: 2 }) assert.deepEqual( (coldAudit.rows as { id: string }[]).map((r) => r.id), ['cold-needle'], ) assert.equal(coldAudit.cold, true) const coldChat = await ops.recall({ kind: 'chat', q: 'remember this phrase', limit: 2 }) assert.deepEqual( (coldChat.rows as { id: string }[]).map((r) => r.id), ['chat-cold-needle'], ) assert.equal(coldChat.cold, true) }) test('recall orders overlapping archives and late hot rows globally', async () => { const { ops } = await freshOps() ops.ingestAudit([ audit('jan-01', '2026-01-01T00:00:00Z', 'shared-needle'), audit('jan-30', '2026-01-30T00:00:00Z', 'shared-needle'), ]) await ops.archiveCycle() ops.ingestAudit([audit('jan-15', '2026-01-15T00:00:00Z', 'shared-needle')]) await ops.archiveCycle() ops.ingestAudit([audit('late-jan-02', '2026-01-02T00:00:00Z', 'shared-needle')]) const result = await ops.recall({ kind: 'audit', q: 'shared-needle', limit: 1 }) assert.equal((result.rows[0] as { id: string }).id, 'jan-30') assert.equal(result.cold, true) }) test('recall has identical Unicode case matching before and after archival', async () => { const { ops } = await freshOps() ops.ingestAudit([ audit('kind-unicode', '2026-01-01T00:00:00Z', 'ÄPFEL'), audit('ÖL', '2026-01-02T00:00:00Z'), ]) ops.ingestChat([ { id: 'chat-unicode', chatId: -100, messageId: 1, direction: 'in', ts: '2024-01-01T00:00:00Z', senderName: 'ÄNNE', text: 'GRÜSSE', }, ]) for (const query of [ { kind: 'audit' as const, q: 'äpfel', id: 'kind-unicode' }, { kind: 'audit' as const, q: 'öl', id: 'ÖL' }, { kind: 'chat' as const, q: 'änne', id: 'chat-unicode' }, { kind: 'chat' as const, q: 'grüsse', id: 'chat-unicode' }, ]) { const hot = await ops.recall({ kind: query.kind, q: query.q, limit: 2 }) assert.equal((hot.rows[0] as { id: string }).id, query.id) } await ops.archiveCycle() for (const query of [ { kind: 'audit' as const, q: 'äpfel', id: 'kind-unicode' }, { kind: 'audit' as const, q: 'öl', id: 'ÖL' }, { kind: 'chat' as const, q: 'änne', id: 'chat-unicode' }, { kind: 'chat' as const, q: 'grüsse', id: 'chat-unicode' }, ]) { const cold = await ops.recall({ kind: query.kind, q: query.q, limit: 2 }) assert.equal((cold.rows[0] as { id: string }).id, query.id) assert.equal(cold.cold, true) } }) test('recall tie ordering is limit-prefix consistent before and after archival', async () => { const { ops } = await freshOps() const ts = '2026-01-01T00:00:00Z' ops.ingestAudit([audit('z', ts, 'tie'), audit('a', ts, 'tie')]) ops.ingestChat([ { id: 'z', chatId: -100, messageId: 1, direction: 'in', ts, text: 'tie' }, { id: 'a', chatId: -100, messageId: 2, direction: 'in', ts, text: 'tie' }, ]) const assertOrder = async (kind: 'audit' | 'chat') => { const one = await ops.recall({ kind, q: 'tie', limit: 1 }) const two = await ops.recall({ kind, q: 'tie', limit: 2 }) assert.deepEqual( (one.rows as { id: string }[]).map((r) => r.id), ['z'], ) assert.deepEqual( (two.rows as { id: string }[]).map((r) => r.id), ['z', 'a'], ) } await assertOrder('audit') await assertOrder('chat') await ops.archiveCycle() await assertOrder('audit') await assertOrder('chat') }) test('chat ingest is idempotent and hide tombstones', async () => { const { ops } = await freshOps() const msg: ChatMessageIn = { id: 'tg-1', chatId: -100, messageId: 5, direction: 'in', ts: '2026-09-12T10:00:00Z', updateId: 1, senderId: '7', text: 'hello', } const r1 = ops.ingestChat([msg]) assert.equal(r1.accepted, 1) const r2 = ops.ingestChat([msg]) assert.equal(r2.duplicates, 1) const hidden = ops.hideMessage({ messageId: 5, requesterId: '7', authority: 'sender', ts: '2026-09-12T10:05:00Z', }) assert.equal(hidden.alreadyHidden, false) assert.equal( ops.hideMessage({ messageId: 5, requesterId: '1', authority: 'oliver', ts: '2026-09-12T10:06:00Z', }).alreadyHidden, true, ) const page = await ops.chatPage({ limit: 10 }) assert.equal(page.rows.length, 1) assert.equal(page.rows[0]?.hidden, true) }) test('deployment events mirror idempotently', async () => { const { ops } = await freshOps() const e: DeploymentEventMirror = { id: 'd1', ts: '2026-09-12T10:00:00Z', kind: 'promote', revision: 'rev9', detail: { ok: true }, } assert.equal(ops.ingestDeploy([e]).accepted, 1) assert.equal(ops.ingestDeploy([e]).duplicates, 1) const recall = await ops.recall({ kind: 'deploy', limit: 10 }) assert.equal(recall.rows.length, 1) }) test('media store is content-addressed and idempotent', async () => { const { ops, dir } = await freshOps() const bytes = Buffer.from('hello media') const meta1 = await ops.putMedia(bytes, { type: 'text/plain', originalName: 'a.txt' }) const meta2 = await ops.putMedia(bytes, { type: 'text/plain', originalName: 'b.txt' }) assert.equal(meta1.digest, meta2.digest) assert.equal(meta1.size, bytes.length) const found = ops.getMediaMeta(meta1.digest) assert.ok(found) assert.equal(found.originalName, 'a.txt') const files = await readdir( join(dir, 'media', meta1.digest.slice(0, 2), meta1.digest.slice(2, 4)), ) assert.deepEqual(files, [meta1.digest]) }) test('analysis cache upserts by (digest, capability, model, schemaVersion)', async () => { const { ops } = await freshOps() const key = { digest: 'd'.repeat(64), capability: 'zai_vision', model: 'glm-5.3-flash', schemaVersion: 1, } assert.equal(ops.getAnalysis(key), undefined) ops.putAnalysis({ ...key, result: { text: 'a cat' } }) ops.putAnalysis({ ...key, result: { text: 'a cat v2' } }) const got = ops.getAnalysis(key) assert.deepEqual(got?.result, { text: 'a cat v2' }) const analyses = await ops.recall({ kind: 'analysis', digest: key.digest }) assert.equal(analyses.rows.length, 1) }) test('archive cycle moves old audit rows exactly once and cold reads serve them', async () => { const { ops, dir } = await freshOps() // clock now = 2026-09-12; insert rows older than 90 days and one fresh row ops.ingestAudit([ audit('old1', '2026-01-05T00:00:00Z'), audit('old2', '2026-01-20T00:00:00Z'), audit('old3', '2026-03-01T00:00:00Z'), audit('new1', '2026-09-01T00:00:00Z'), ]) const result = await ops.archiveCycle() assert.equal(result.exportedAudit, 3) assert.equal(result.segments, 2) // 2026-01 and 2026-03 // hot table only has the fresh row assert.equal(ops.counts().audit, 1) assert.equal(ops.eventsSince(0, 100).length, 1) // timeline cold fallback pages in archived rows (newest first) const page = await ops.timeline({ limit: 10 }) assert.equal(page.rows.length, 4) assert.equal(page.cold, true) assert.equal(page.rows[0]?.id, 'new1') assert.equal(page.rows[3]?.id, 'old1') assert.equal(page.rows[3]?.archived, true) // sidecar indexes exist and are versioned const indexFiles: string[] = [] for (const month of ['2026-01', '2026-03']) { for (const f of await readdir(join(dir, 'archive', 'audit', month))) { if (f.endsWith('.index.json')) indexFiles.push(f) } } assert.equal(indexFiles.length, 2) const sidecar = JSON.parse( await readFile(join(dir, 'archive', 'audit', '2026-01', indexFiles[0] as string), 'utf8'), ) assert.equal(sidecar.version, 1) assert.equal(sidecar.kind, 'audit') assert.equal(sidecar.count, 2) // second cycle exports nothing new (move exactly once) const second = await ops.archiveCycle() assert.equal(second.exportedAudit, 0) }) test('crash between publish and delete does not duplicate rows (exactly once)', async () => { const { ops } = await freshOps() ops.ingestAudit([audit('old1', '2026-01-05T00:00:00Z')]) // First cycle archives AND deletes. Simulate crash-after-publish by inserting // the same row back (as a crashed cycle would have left it) before cycle 2. await ops.archiveCycle() ops.ingestAudit([audit('old1', '2026-01-05T00:00:00Z')]) const second = await ops.archiveCycle() assert.equal(second.exportedAudit, 0) assert.equal(ops.counts().audit, 0) const page = await ops.timeline({ limit: 10 }) assert.equal(page.rows.length, 1) }) test('archive cycle moves chat after one year and tombstones keep filtering', async () => { const { ops } = await freshOps() ops.ingestChat([ { id: 'tg-old', chatId: -100, messageId: 1, direction: 'in', ts: '2024-09-01T00:00:00Z', text: 'ancient', }, { id: 'tg-new', chatId: -100, messageId: 2, direction: 'in', ts: '2026-08-01T00:00:00Z', text: 'recent', }, ]) ops.hideMessage({ messageId: 1, requesterId: '1', authority: 'oliver', ts: '2026-09-12T00:00:00Z', }) const result = await ops.archiveCycle() assert.equal(result.exportedChat, 1) const page = await ops.chatPage({ limit: 10 }) assert.equal(page.rows.length, 2) // hidden ancient message stays hidden even after archival const hiddenRow = page.rows.find((r) => r.id === 'tg-old') assert.equal(hiddenRow?.hidden, true) const recall = await ops.recall({ kind: 'chat', limit: 10 }) assert.equal(recall.rows.length, 1) // hidden rows are excluded from recall }) test('quick check reports ok and jobs are recorded', async () => { const { ops } = await freshOps() assert.equal(await ops.quickCheck(), 'ok') // Default thresholds depend on the host filesystem's free space; pin them // so storageState is deterministic regardless of the environment. ops.setStorageThresholds(0, 0) const status = await ops.status() assert.equal(status.schemaVersion, 1) assert.ok(status.storage.freePct >= 0) assert.equal(status.storageState, 'ok') assert.ok(status.lastQuickCheck) // Any non-negative freePct sits below huge thresholds: warn, then stop. ops.setStorageThresholds(1000, 0) assert.equal((await ops.status()).storageState, 'warn') ops.setStorageThresholds(1000, 1000) assert.equal((await ops.status()).storageState, 'stop') }) test('vacuum runs without error', async () => { const { ops } = await freshOps() await ops.vacuum() await ops.reconcile() })