Files
tyx_AI_xhs/tests/integration/wp1-06-audit-immutability.test.ts
T

137 lines
6.8 KiB
TypeScript

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 });
});
});