Files
SikongJueluo 99e953664d refactor(ai-judge): group src modules into domain directories
- move evidence.ts and conversation.ts into evidence/ as bash.ts and conversation.ts
- move prompt.ts and model.ts into judge/
- move highrisk.ts and judge.ts into authority/, renaming judge.ts to enforce.ts to avoid clashing with the judge/ directory
- move review.ts and audit.ts into telemetry/
- move config.ts and catalog.ts into config/ as judge.ts and catalog.ts
- rewrite static and dynamic imports across src, tools, and tests to the new paths
- fix the models-catalog.json relative URL broken by the move (caught by fallow unresolved-import)
- update the PIEXTENSIO-12 module map in the ADR to the new paths
- add editorconfig for 4-space ts indentation
2026-08-22 01:34:29 +08:00

125 lines
4.2 KiB
TypeScript

import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { createAuditLog } from "../src/telemetry/audit";
const dirs: string[] = [];
function tmp(): string {
const dir = mkdtempSync(join(tmpdir(), "ai-judge-audit-"));
dirs.push(dir);
return dir;
}
afterEach(() => {
for (const dir of dirs.splice(0)) {
rmSync(dir, { recursive: true, force: true });
}
});
function readAudit(agentDir: string): Array<Record<string, unknown>> {
return readFileSync(
join(agentDir, "extensions", "pi-permission-ai-judge", "logs", "audit.jsonl"),
"utf-8",
)
.trim()
.split("\n")
.filter((line) => line.length > 0)
.map((line) => JSON.parse(line) as Record<string, unknown>);
}
describe("createAuditLog — healthy path", () => {
it("appends one JSONL record per audit call with timestamp and runtime id", () => {
const agentDir = tmp();
const log = createAuditLog({
agentDir,
runtimeId: "rt-1",
now: () => "2026-08-18T00:00:00.000Z",
});
expect(log.healthy()).toBe(true);
log.audit("ai_bash_judge.enrolled", { requestId: "perm-a", origin: "local" });
log.audit("ai_bash_judge.result", { requestId: "perm-a", resultKind: "judgment" });
const rows = readAudit(agentDir);
expect(rows).toHaveLength(2);
expect(rows[0]).toEqual({
timestamp: "2026-08-18T00:00:00.000Z",
judgeRuntimeId: "rt-1",
event: "ai_bash_judge.enrolled",
requestId: "perm-a",
origin: "local",
});
expect(rows[1]).toEqual({
timestamp: "2026-08-18T00:00:00.000Z",
judgeRuntimeId: "rt-1",
event: "ai_bash_judge.result",
requestId: "perm-a",
resultKind: "judgment",
});
});
it("strips privacy-forbidden keys from audit records", () => {
const agentDir = tmp();
const log = createAuditLog({ agentDir, runtimeId: "rt-1" });
log.audit("ai_bash_judge.result", {
requestId: "perm-a",
apiToken: "leak",
secretPath: "/x",
verdict: "allow",
});
const rows = readAudit(agentDir);
expect(rows[0]).toEqual({
timestamp: rows[0].timestamp,
judgeRuntimeId: "rt-1",
event: "ai_bash_judge.result",
requestId: "perm-a",
verdict: "allow",
});
expect(rows[0].apiToken).toBeUndefined();
expect(rows[0].secretPath).toBeUndefined();
});
it("creates nested log directories on first use", () => {
const agentDir = join(tmp(), "deep", "agent");
const log = createAuditLog({ agentDir, runtimeId: "rt-1" });
log.audit("e", {});
expect(readAudit(agentDir)).toHaveLength(1);
});
});
describe("createAuditLog — fail-closed health", () => {
it("marks unhealthy permanently when the write fails once", () => {
const agentDir = tmp();
const log = createAuditLog({ agentDir, runtimeId: "rt-1" });
// Corrupt the logs dir into a file: open("a") on a path whose parent
// is a regular file throws ENSUREDIR/ENOTDIR.
const logsDir = join(
agentDir,
"extensions",
"pi-permission-ai-judge",
"logs",
);
rmSync(logsDir, { recursive: true, force: true });
writeFileSync(logsDir, "not a directory");
log.audit("e1", {});
expect(log.healthy()).toBe(false);
// Sticky: health never recovers within this runtime (ADR 0006).
log.audit("e2", {});
expect(log.healthy()).toBe(false);
});
it("is unhealthy from creation when the directory cannot be made", () => {
const agentDir = tmp();
const poisoned = join(agentDir, "extensions");
writeFileSync(poisoned, "file blocks mkdir");
const log = createAuditLog({ agentDir, runtimeId: "rt-1" });
expect(log.healthy()).toBe(false);
log.audit("e", {});
expect(log.healthy()).toBe(false);
});
});