import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { archiveSessions, redactVisibleText, restoreManifest, verifyManifest, } from "./archive.mjs"; test("visible text is redacted without erasing ordinary corrections", () => { const namedSecret = `${["tok", "en"].join("")}=abc123456789`; const result = redactVisibleText(`不是这个方向。${namedSecret} user@example.com 10.0.0.8 /Users/demo/work`); assert.match(result.text, /不是这个方向/); assert.doesNotMatch(result.text, /abc123456789|user@example\.com|10\.0\.0\.8/); assert.equal(result.counts.named_secret, 1); assert.equal(result.counts.email, 1); assert.equal(result.counts.ip_address, 1); }); test("archive is content addressed, excludes reasoning and restores exact bytes", async () => { const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "codex-session-archive-")); const source = path.join(root, "source"); const destination = path.join(root, "archive"); const restoreRoot = path.join(root, "restore"); const session = path.join(source, "2026", "08", "05", "rollout-test.jsonl"); await fs.promises.mkdir(path.dirname(session), { recursive: true }); await fs.promises.mkdir(destination, { recursive: true }); await fs.promises.writeFile(path.join(destination, "._generated-sidecar"), "regenerable"); const rows = [ { timestamp: "2026-08-05T00:00:00Z", type: "session_meta", payload: { id: "thread-1", cwd: "/private/work" } }, { timestamp: "2026-08-05T00:00:01Z", type: "event_msg", payload: { type: "user_message", message: `不是这个方向,${["pass", "word"].join("")}=fixture-value` } }, { timestamp: "2026-08-05T00:00:02Z", type: "response_item", payload: { type: "reasoning", summary: [{ text: "hidden chain" }], encrypted_content: "ciphertext" } }, { timestamp: "2026-08-05T00:00:03Z", type: "event_msg", payload: { type: "agent_message", message: "已按边界修正。" } }, { timestamp: "2026-08-05T00:00:04Z", type: "response_item", payload: { type: "function_call", name: "exec_command", arguments: "{\"secret\":\"do-not-index\"}" } }, ]; await fs.promises.writeFile(session, `${rows.map((row) => JSON.stringify(row)).join("\n")}\n`); const archived = await archiveSessions({ source, destination, stableSeconds: 0, now: new Date("2026-08-05T01:00:00Z") }); assert.equal(archived.manifest.totals.archived_verified, 1); assert.equal(archived.manifest.totals.pending_live, 0); assert.equal(archived.appleDoubleRemoved, 1); assert.equal(fs.existsSync(path.join(destination, "._generated-sidecar")), false); const entry = archived.manifest.entries[0]; const indexText = await fs.promises.readFile(path.join(destination, entry.private_index), "utf8"); assert.match(indexText, /不是这个方向/); assert.match(indexText, /REDACTED:NAMED_SECRET/); assert.match(indexText, /exec_command/); assert.doesNotMatch(indexText, /hidden chain|ciphertext|do-not-index|fixture-value/); const verified = await verifyManifest({ destination, manifest: archived.manifestPath }); assert.equal(verified.ok, true); const dryRun = await restoreManifest({ destination, manifest: archived.manifestPath, restoreRoot, execute: false }); assert.equal(dryRun.planned_files, 1); assert.equal(fs.existsSync(restoreRoot), false); await restoreManifest({ destination, manifest: archived.manifestPath, restoreRoot, execute: true }); assert.equal(await fs.promises.readFile(path.join(restoreRoot, entry.relative_path), "utf8"), await fs.promises.readFile(session, "utf8")); }); test("recent live sessions are recorded as pending instead of misreported archived", async () => { const root = await fs.promises.mkdtemp(path.join(os.tmpdir(), "codex-session-live-")); const source = path.join(root, "source"); const destination = path.join(root, "archive"); await fs.promises.mkdir(source); await fs.promises.writeFile(path.join(source, "live.jsonl"), "{}\n"); const archived = await archiveSessions({ source, destination, stableSeconds: 3600 }); assert.equal(archived.manifest.totals.archived_verified, 0); assert.equal(archived.manifest.totals.pending_live, 1); assert.equal(archived.manifest.entries[0].state, "pending_live"); });