feat: archive Codex sessions as private cognition evidence
This commit is contained in:
parent
0ac0025fdf
commit
7d85d79e61
8 changed files with 798 additions and 0 deletions
|
|
@ -0,0 +1,70 @@
|
|||
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");
|
||||
});
|
||||
Loading…
Reference in a new issue