786f194858f85de9865d0913c384dad652692979

Author
TheEdgeOfRage <git@theedgeofrage.com>
Committer
TheEdgeOfRage <git@theedgeofrage.com>
Date

Message

Minor improvements

Diff

  1diff --git a/src/core/audit.ts b/src/core/audit.ts
  2index 46cfc5e9e18a308cb56f2bd1e3707638e23e2840..b1b8aa4778ca5a575b4f7a4760552105bdb5553b 100644
  3--- a/src/core/audit.ts
  4+++ b/src/core/audit.ts
  5@@ -1,5 +1,6 @@
  6 import { appendFile, mkdir } from "node:fs/promises";
  7 import { dirname } from "node:path";
  8+import { POLICY_VERSION } from "./rules";
  9 import type { DecisionAudit } from "./types";
 10 
 11 export function createJsonlDecisionAudit(file: string): DecisionAudit {
 12@@ -7,7 +8,10 @@ export function createJsonlDecisionAudit(file: string): DecisionAudit {
 13     async record(entry): Promise<void> {
 14       try {
 15         await mkdir(dirname(file), { recursive: true });
 16-        await appendFile(file, `${JSON.stringify({ recordedAt: new Date().toISOString(), ...entry })}\n`);
 17+        await appendFile(
 18+          file,
 19+          `${JSON.stringify({ recordedAt: new Date().toISOString(), ...entry, policyVersion: POLICY_VERSION })}\n`,
 20+        );
 21       } catch {}
 22     },
 23   };
 24diff --git a/src/pi/static-permissions.ts b/src/pi/static-permissions.ts
 25index 99a94b5fe993b3fb16746f13e490af9e2042c738..99129d4b6a2503af433d15a091674e99e25bd44b 100644
 26--- a/src/pi/static-permissions.ts
 27+++ b/src/pi/static-permissions.ts
 28@@ -1,6 +1,6 @@
 29 import { realpath } from "node:fs/promises";
 30 import { homedir } from "node:os";
 31-import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
 32+import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
 33 import type { ExternalDirectories, PermissionAction, PiPolicyEngineConfig } from "./config";
 34 import type { Decision, DecisionCategory } from "../core/types";
 35 
 36@@ -77,6 +77,13 @@ export async function checkStaticPermission(
 37   if (PATH_TOOLS.has(toolType)) {
 38     const path = typeof input.path === "string" ? input.path : ".";
 39     const absolutePath = await canonicalPath(path, cwd);
 40+    if (["edit", "write"].includes(toolType) && basename(absolutePath) === "policy-engine.json") {
 41+      return {
 42+        decision: "ask",
 43+        reason: "Policy engine configuration changes require confirmation",
 44+        category: "dangerous",
 45+      };
 46+    }
 47     const workspacePath = await canonicalPath(cwd, cwd);
 48     if (!isInside(absolutePath, workspacePath)) {
 49       if (!matchesExternalDirectory(config.externalDirectories, absolutePath)) return undefined;
 50diff --git a/test/core/deterministic.test.ts b/test/core/deterministic.test.ts
 51index 1a9179c31fd983f4506641a47c7e3bc339698a59..44799577c4f9fa4fc7a056705db74146a845edbc 100644
 52--- a/test/core/deterministic.test.ts
 53+++ b/test/core/deterministic.test.ts
 54@@ -68,6 +68,7 @@ test("allows recognized deterministic command grammars", () => {
 55     "cut -d: -f1,3",
 56     "golangci-lint run --fix",
 57     "rg --hidden --glob '*.ts' policy src",
 58+    'rg -l "stt|transcri" --type go -i',
 59     "rg --files --hidden",
 60   ]) {
 61     expect(checkDeterministic("bash", { command })).toMatchObject({ decision: "allow" });
 62diff --git a/test/pi/static-permissions.test.ts b/test/pi/static-permissions.test.ts
 63index 3b8b9373d9fb46e295bc74a9313dc227f8fde745..64782cc574959938c3c50dc7295740f235c98d49 100644
 64--- a/test/pi/static-permissions.test.ts
 65+++ b/test/pi/static-permissions.test.ts
 66@@ -3,6 +3,7 @@ import { mkdtempSync, rmSync } from "node:fs";
 67 import { tmpdir } from "node:os";
 68 import { join } from "node:path";
 69 import { checkStaticPermission, matchesExternalDirectory } from "../../src/pi/static-permissions";
 70+import type { PiPolicyEngineConfig } from "../../src/pi/config";
 71 
 72 test("explicit deny wins before external path handling", async () => {
 73   const cwd = mkdtempSync(join(tmpdir(), "policy-engine-cwd-"));
 74@@ -19,6 +20,26 @@ test("explicit deny wins before external path handling", async () => {
 75   }
 76 });
 77 
 78+test("asks before changing a policy engine configuration", async () => {
 79+  const cwd = mkdtempSync(join(tmpdir(), "policy-engine-cwd-"));
 80+  try {
 81+    const config: PiPolicyEngineConfig = {
 82+      reviewer: { kind: "none" },
 83+      tools: { edit: "allow", write: "allow" },
 84+      externalDirectories: [],
 85+    };
 86+    for (const toolType of ["edit", "write"]) {
 87+      await expect(
 88+        checkStaticPermission(toolType, { path: ".pi/policy-engine.json" }, cwd, config),
 89+      ).resolves.toMatchObject({
 90+        decision: "ask",
 91+      });
 92+    }
 93+  } finally {
 94+    rmSync(cwd, { recursive: true, force: true });
 95+  }
 96+});
 97+
 98 test("matches external directories by path components", async () => {
 99   const root = mkdtempSync(join(tmpdir(), "policy-engine-external-"));
100   const cwd = join(root, "cwd");