import { randomUUID } from "node:crypto"; import { mkdirSync, readFileSync, readdirSync, statSync, utimesSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { afterEach, describe, expect, it } from "vitest"; import { ManagedStorage } from "../../apps/api/src/managed-storage.js"; import { LOG_FILE_LIMIT, LOG_RETENTION_DAYS, LOG_SIZE_LIMIT_BYTES, StructuredJsonlLogger, sanitizeDiagnosticRecord, } from "../../apps/api/src/structured-log.js"; const temporaryRoots: string[] = []; function fixtureRoot() { const root = join(tmpdir(), `dada-wp0-08-${randomUUID()}`); mkdirSync(root, { recursive: true }); temporaryRoots.push(root); return root; } function evidence(name: string, value: unknown) { const directory = process.env.DADA_EVIDENCE_DIR_LOG; if (!directory) return; mkdirSync(directory, { recursive: true }); writeFileSync(join(directory, name), `${JSON.stringify(value, null, 2)}\n`); } afterEach(async () => { const { rm } = await import("node:fs/promises"); await Promise.all(temporaryRoots.splice(0).map((root) => rm(root, { force: true, recursive: true }))); }); describe("TDD-WP0-LOG-001 rotation and redaction", () => { it("enforces 10 MiB, 10 files, 30 days and emits only allowlisted JSONL fields", () => { const root = fixtureRoot(); const logDirectory = join(root, "logs", "api"); mkdirSync(logDirectory, { recursive: true }); const activePath = join(logDirectory, "api.jsonl"); const seedLine = `${JSON.stringify({ component: "api", correlation_id: "corr_fixture", schema_version: "1.0", status_category: "ready", timestamp: "2026-01-01T00:00:00.000Z", })}\n`; const repeats = Math.floor(LOG_SIZE_LIMIT_BYTES / Buffer.byteLength(seedLine)); writeFileSync(activePath, seedLine.repeat(repeats)); const bytesBeforeBoundaryWrite = statSync(activePath).size; const marker = `trap-${randomUUID()}`; const logger = new StructuredJsonlLogger({ component: "api", directory: logDirectory }); logger.write({ absolute_path: join(root, marker), api_key: `${marker}-key`, correlation_id: "corr_boundary", duration_ms: 12, email: `${marker}@example.invalid`, error_category: "none", image: Buffer.from(marker), object_id: "obj_boundary", prompt: `${marker} private prompt`, provider_error: `${marker} upstream body`, status_category: "completed", }); expect(statSync(join(logDirectory, "api.1.jsonl")).size).toBeLessThanOrEqual(LOG_SIZE_LIMIT_BYTES); expect(statSync(activePath).size).toBeLessThanOrEqual(LOG_SIZE_LIMIT_BYTES); for (let index = 2; index <= 14; index += 1) writeFileSync(join(logDirectory, `api.${index}.jsonl`), seedLine); const stalePath = join(logDirectory, "api.4.jsonl"); const stale = new Date(Date.now() - (LOG_RETENTION_DAYS + 1) * 24 * 60 * 60 * 1000); utimesSync(stalePath, stale, stale); new StructuredJsonlLogger({ component: "api", directory: logDirectory }).maintain(); const files = readdirSync(logDirectory).sort(); const scanned = files.map((name) => readFileSync(join(logDirectory, name), "utf8")).join(""); expect(files.length).toBeLessThanOrEqual(LOG_FILE_LIMIT); expect(files).not.toContain("api.4.jsonl"); expect(scanned).not.toContain(marker); const activeEntry = JSON.parse(readFileSync(activePath, "utf8").trim()) as Record; expect(Object.keys(activeEntry).sort()).toEqual([ "component", "correlation_id", "duration_ms", "error_category", "object_id", "schema_version", "status_category", "timestamp", ]); const diagnostic = sanitizeDiagnosticRecord({ app_version: "0.0.0", checked_at: "2026-01-01T00:00:00.000Z", component: "api", message_key: "log_write_failed", private_content: marker, result: "fail", stable_check_code: "log_writable", user_path: join(root, marker), }); expect(JSON.stringify(diagnostic)).not.toContain(marker); evidence("log-manifest.json", { bytes_before_boundary_write: bytesBeforeBoundaryWrite, file_limit: LOG_FILE_LIMIT, files: files.map((name) => ({ name, size: statSync(join(logDirectory, name)).size })), retention_days: LOG_RETENTION_DAYS, size_limit_bytes: LOG_SIZE_LIMIT_BYTES, status: "passed", total_generated_bytes: bytesBeforeBoundaryWrite + statSync(activePath).size, }); evidence("redaction.json", { diagnostic, marker_absent: true, status: "passed" }); }); it("marks storage unavailable when the log directory cannot be written", () => { const root = fixtureRoot(); const storage = new ManagedStorage({ dataRoot: root, databasePath: join(root, "db", "dada.sqlite3") }); const blockedDirectory = join(root, "logs-blocked"); writeFileSync(blockedDirectory, "not-a-directory"); const logger = new StructuredJsonlLogger({ component: "api", directory: blockedDirectory, onWriteFailure: () => storage.setLogAvailability(false), }); expect(() => logger.write({ correlation_id: "corr_log_failure", error_category: "log_write_failed", status_category: "failed" })).toThrow("log_write_failed"); expect(storage.getState().storage_status).toBe("unavailable"); expect(storage.inspectAction("ai_call")).toBe("reject_unavailable"); evidence("response.json", { ai_call: storage.inspectAction("ai_call"), business_objects_changed: 0, storage_status: storage.getState().storage_status, status: "passed", }); storage.setLogAvailability(true); expect(storage.getState().storage_status).toBe("unavailable"); storage.applyControlledMeasurement(0); expect(storage.getState().storage_status).toBe("active"); storage.close(); }); });