mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
142 lines
4.8 KiB
TypeScript
142 lines
4.8 KiB
TypeScript
import { mkdtempSync, readFileSync, rmSync, statSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
DEFAULT_AUTO_REVIEW_ANALYSIS_CONFIG,
|
|
DEFAULT_AUTO_REVIEW_CONFIG,
|
|
} from "#src/auto-review/config";
|
|
import { AutoReviewLedger } from "#src/auto-review/ledger";
|
|
import type { PromptPermissionDetails } from "#src/service";
|
|
|
|
const roots: string[] = [];
|
|
|
|
afterEach(() => {
|
|
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
function details(requestId: string): PromptPermissionDetails {
|
|
return {
|
|
requestId,
|
|
source: "tool",
|
|
surface: "bash",
|
|
toolName: "bash",
|
|
command: "npm install example",
|
|
agentName: "main",
|
|
} as unknown as PromptPermissionDetails;
|
|
}
|
|
|
|
describe("AutoReviewLedger", () => {
|
|
it("marks analysis pending only after the configured number of completed verdicts", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "auto-review-ledger-"));
|
|
roots.push(root);
|
|
const ledger = new AutoReviewLedger(root);
|
|
const analysis = { ...DEFAULT_AUTO_REVIEW_ANALYSIS_CONFIG, threshold: 2 };
|
|
const assessment = {
|
|
outcome: "allow" as const,
|
|
riskLevel: "low" as const,
|
|
userAuthorization: "high" as const,
|
|
rationale: "directly authorized",
|
|
};
|
|
|
|
ledger.recordDecision({
|
|
details: details("request-1"),
|
|
assessment,
|
|
policyRevision: "policy-a",
|
|
config: DEFAULT_AUTO_REVIEW_CONFIG,
|
|
analysis,
|
|
});
|
|
expect(ledger.hasPendingAnalysis()).toBe(false);
|
|
expect(ledger.prepareBatch(analysis)).toBeUndefined();
|
|
|
|
ledger.recordDecision({
|
|
details: details("request-2"),
|
|
assessment,
|
|
policyRevision: "policy-a",
|
|
config: DEFAULT_AUTO_REVIEW_CONFIG,
|
|
analysis,
|
|
});
|
|
expect(ledger.hasPendingAnalysis()).toBe(true);
|
|
const batch = ledger.prepareBatch(analysis);
|
|
expect(batch?.fromSequence).toBe(1);
|
|
expect(batch?.toSequence).toBe(2);
|
|
expect(batch?.records).toHaveLength(2);
|
|
expect(batch?.records[0]).not.toHaveProperty("rationale");
|
|
expect(batch?.records[0].normalizedPattern).toBe("npm install *");
|
|
});
|
|
|
|
it("persists owner-only state and resumes a pending batch", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "auto-review-ledger-"));
|
|
roots.push(root);
|
|
const analysis = { ...DEFAULT_AUTO_REVIEW_ANALYSIS_CONFIG, threshold: 1 };
|
|
const ledger = new AutoReviewLedger(root);
|
|
ledger.recordDecision({
|
|
details: details("request-1"),
|
|
assessment: {
|
|
outcome: "deny",
|
|
riskLevel: "high",
|
|
userAuthorization: "unknown",
|
|
rationale: "unsafe",
|
|
},
|
|
policyRevision: "policy-b",
|
|
config: DEFAULT_AUTO_REVIEW_CONFIG,
|
|
analysis,
|
|
});
|
|
|
|
const resumed = new AutoReviewLedger(root);
|
|
expect(resumed.hasPendingAnalysis()).toBe(true);
|
|
expect(resumed.prepareBatch(analysis)?.records[0].outcome).toBe("deny");
|
|
expect(statSync(join(root, "state.json")).mode & 0o777).toBe(0o600);
|
|
expect(readFileSync(join(root, "ledger", "decisions.jsonl"), "utf8")).not.toContain("unsafe");
|
|
});
|
|
|
|
it("does not retain command arguments, secret path segments, or rationale", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "auto-review-ledger-"));
|
|
roots.push(root);
|
|
const ledger = new AutoReviewLedger(root);
|
|
const analysis = { ...DEFAULT_AUTO_REVIEW_ANALYSIS_CONFIG, threshold: 10 };
|
|
const assessment = {
|
|
outcome: "allow" as const,
|
|
riskLevel: "medium" as const,
|
|
userAuthorization: "high" as const,
|
|
rationale: "secret-rationale",
|
|
};
|
|
const commandRecord = ledger.recordDecision({
|
|
details: {
|
|
requestId: "request-command",
|
|
source: "tool",
|
|
agentName: "main",
|
|
surface: "bash",
|
|
toolName: "bash",
|
|
command: "curl https://token.example/?api_key=secret-value",
|
|
} as unknown as PromptPermissionDetails,
|
|
assessment,
|
|
policyRevision: "policy-c",
|
|
config: DEFAULT_AUTO_REVIEW_CONFIG,
|
|
analysis,
|
|
});
|
|
const pathRecord = ledger.recordDecision({
|
|
details: {
|
|
requestId: "request-path",
|
|
source: "tool",
|
|
agentName: "main",
|
|
surface: "path",
|
|
toolName: "read",
|
|
path: "clients/acme-secret/token.txt",
|
|
} as unknown as PromptPermissionDetails,
|
|
assessment,
|
|
policyRevision: "policy-c",
|
|
config: DEFAULT_AUTO_REVIEW_CONFIG,
|
|
analysis,
|
|
});
|
|
|
|
expect(commandRecord.normalizedPattern).toBe("curl *");
|
|
expect(pathRecord.normalizedPattern).toBe("<relative>/*");
|
|
const persisted = readFileSync(join(root, "ledger", "decisions.jsonl"), "utf8");
|
|
expect(persisted).not.toContain("secret-value");
|
|
expect(persisted).not.toContain("acme-secret");
|
|
expect(persisted).not.toContain("secret-rationale");
|
|
});
|
|
});
|