import { randomUUID } from "node:crypto"; import { createRequire } from "node:module"; import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { RegistrationService } from "../../apps/api/src/registration.js"; import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js"; import { RetentionCleanup } from "../../apps/worker/src/retention-cleanup.js"; import { registrationNotice } from "../../packages/shared-contracts/src/registration-notice.js"; const requireFromApi = createRequire(new URL("../../apps/api/package.json", import.meta.url)); const Database = requireFromApi("better-sqlite3"); const retentionMilliseconds = 180 * 24 * 60 * 60 * 1_000; const baseNow = Date.now(); const roots: string[] = []; const services: RegistrationService[] = []; function createHarness() { const root = mkdtempSync(join(tmpdir(), "dada-wp1-06-audit-")); roots.push(root); const databasePath = join(root, "dada.sqlite3"); const service = new RegistrationService({ challengePepper: Buffer.alloc(32, 0x41), clock: () => baseNow, currentPrivacyNoticeVersion: registrationNotice.version, databasePath, invitePepper: Buffer.alloc(32, 0x42), resend: new MockResendAdapter(), sessionPepper: Buffer.alloc(32, 0x43), }); services.push(service); return { databasePath, service }; } function insertAuditPair(service: RegistrationService, occurredAt: number) { const adminLogId = randomUUID(); const privateLogId = randomUUID(); const expiresAt = occurredAt + retentionMilliseconds; service.database.prepare(` INSERT INTO admin_operation_logs ( log_id, actor_type, actor_ref, operation_type, target_type, target_ref, result, before_summary, after_summary, occurred_at, expires_at ) VALUES (?, 'system', 'backend_secure_config', 'secure_config_apply', 'secure_config_revision', 'revision:fixture', 'succeeded', ?, ?, ?, ?) `).run(adminLogId, JSON.stringify({ revision: 1 }), JSON.stringify({ revision: 2 }), occurredAt, expiresAt); service.database.prepare(` INSERT INTO private_content_access_logs ( log_id, actor_ref, subject_ref, target_ref, content_type, occurred_at, expires_at ) VALUES (?, ?, ?, ?, 'image', ?, ?) `).run(privateLogId, randomUUID(), randomUUID(), randomUUID(), occurredAt, expiresAt); return { adminLogId, expiresAt, privateLogId }; } function writeEvidence(file: string, value: unknown) { const directory = process.env.DADA_EVIDENCE_DIR_AUDIT; if (!directory) return; mkdirSync(directory, { recursive: true }); writeFileSync(resolve(directory, file), `${JSON.stringify(value, null, 2)}\n`); } afterEach(() => { for (const service of services.splice(0)) service.close(); for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true }); }); describe("TDD-WP1-AUD-001-immutable-triggers", () => { it("blocks ordinary and forged-scope mutations but lets the Worker delete both audit classes at expiry", () => { const { databasePath, service } = createHarness(); const pair = insertAuditPair(service, baseNow); const ordinary = new Database(databasePath); ordinary.pragma("busy_timeout = 5000"); try { expect(() => ordinary.prepare("UPDATE admin_operation_logs SET result = 'failed' WHERE log_id = ?").run(pair.adminLogId)).toThrow(); expect(() => ordinary.prepare("DELETE FROM admin_operation_logs WHERE log_id = ?").run(pair.adminLogId)).toThrow(); expect(() => ordinary.prepare("UPDATE private_content_access_logs SET actor_ref = ? WHERE log_id = ?").run(randomUUID(), pair.privateLogId)).toThrow(); expect(() => ordinary.prepare("DELETE FROM private_content_access_logs WHERE log_id = ?").run(pair.privateLogId)).toThrow(); ordinary.function("dada_allow_retention_purge", () => 1); expect(() => ordinary.prepare("DELETE FROM private_content_access_logs WHERE log_id = ?").run(pair.privateLogId)).toThrow(); ordinary.function("dada_allow_privacy_purge", () => 1); expect(() => ordinary.prepare(` UPDATE private_content_access_logs SET subject_ref = ?, target_ref = ? WHERE log_id = ? `).run(randomUUID(), randomUUID(), pair.privateLogId)).toThrow(); } finally { ordinary.close(); } const early = new RetentionCleanup({ clock: () => pair.expiresAt - 1, databasePath }); expect(early.purgeExpired()).toEqual({ admin_operation_logs: 0, anonymous_events: 0, private_access_logs: 0 }); early.close(); const expired = new RetentionCleanup({ clock: () => pair.expiresAt, databasePath }); expect(expired.purgeExpired()).toEqual({ admin_operation_logs: 1, anonymous_events: 0, private_access_logs: 1 }); expired.close(); expect(service.database.prepare("SELECT COUNT(*) AS count FROM admin_operation_logs").get().count).toBe(0); expect(service.database.prepare("SELECT COUNT(*) AS count FROM private_content_access_logs").get().count).toBe(0); writeEvidence("sql-results.json", { forged_scope_delete: "blocked", forged_privacy_scope_update: "blocked", ordinary_delete: "blocked", ordinary_update: "blocked", retention_delete_at_expiry: "allowed", }); writeEvidence("db-diff.json", { admin_operation_logs: 0, private_content_access_logs: 0 }); }); it("enforces exact retention and rejects sensitive operation summaries", () => { const { service } = createHarness(); const badExpiry = baseNow + retentionMilliseconds + 1; expect(() => service.database.prepare(` INSERT INTO private_content_access_logs ( log_id, actor_ref, subject_ref, target_ref, content_type, occurred_at, expires_at ) VALUES (?, ?, ?, ?, 'prompt', ?, ?) `).run(randomUUID(), randomUUID(), randomUUID(), randomUUID(), baseNow, badExpiry)).toThrow(); expect(() => service.database.prepare(` INSERT INTO admin_operation_logs ( log_id, actor_type, actor_ref, operation_type, target_type, target_ref, result, before_summary, after_summary, occurred_at, expires_at ) VALUES (?, 'system', 'backend_secure_config', 'secure_config_apply', 'secure_config_revision', 'revision:fixture', 'failed', ?, NULL, ?, ?) `).run( randomUUID(), JSON.stringify({ relative_path: "private/fixture", session_token: "sensitive-fixture-token" }), baseNow, baseNow + retentionMilliseconds, )).toThrow(); expect(service.database.prepare("SELECT COUNT(*) AS count FROM admin_operation_logs").get().count).toBe(0); expect(service.database.prepare("SELECT COUNT(*) AS count FROM private_content_access_logs").get().count).toBe(0); const triggers = service.database.prepare(` SELECT name, sql FROM sqlite_master WHERE type = 'trigger' AND name LIKE '%logs_%' ORDER BY name `).all(); writeEvidence("trigger-definition.json", { triggers }); }); });