mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
feat(permission): integrate auto-review and hard guards
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user