@erichll/pi-auto-review 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/package.json +2 -2
- package/src/index.ts +4 -2
- package/src/path-surfaces.ts +31 -0
- package/src/policy-audit/classifier.ts +9 -3
- package/src/policy.ts +3 -1
- package/src/ui-auto-confirm.ts +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0 - 2026-08-28
|
|
4
|
+
|
|
5
|
+
- Add compatibility with `@gotgenes/pi-permission-system` 27.1 directional
|
|
6
|
+
`path_*` and `external_directory_*` surfaces while retaining the original
|
|
7
|
+
surface in authorization evidence, UI binding, logs, and policy audits.
|
|
8
|
+
- Keep the permission-system peer range at `^27.0.0` so existing 27.x bare
|
|
9
|
+
`path` and `external_directory` integrations remain supported.
|
|
10
|
+
|
|
3
11
|
## 0.12.0 - 2026-08-27
|
|
4
12
|
|
|
5
13
|
- Add the private policy audit
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erichll/pi-auto-review",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Fail-closed, model-backed approval broker for Pi: deterministically hard-denies dangerous operations, reviews dangerous boundaries with a reviewer model, and issues one-shot expiring grants that OS sandbox adapters must consume before retrying.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@earendil-works/pi-ai": "^0.84.1",
|
|
56
56
|
"@earendil-works/pi-coding-agent": "^0.84.1",
|
|
57
|
-
"@gotgenes/pi-permission-system": "
|
|
57
|
+
"@gotgenes/pi-permission-system": "27.1.0",
|
|
58
58
|
"@types/node": "^26.2.0",
|
|
59
59
|
"typescript": "^6.0.3"
|
|
60
60
|
}
|
package/src/index.ts
CHANGED
|
@@ -72,6 +72,7 @@ import {
|
|
|
72
72
|
type PolicyAuditArguments,
|
|
73
73
|
type PolicyAuditConfig,
|
|
74
74
|
} from "./policy-audit/index.ts";
|
|
75
|
+
import { isPathSurface, pathSurfaceInfo } from "./path-surfaces.ts";
|
|
75
76
|
export { parseHostPort };
|
|
76
77
|
|
|
77
78
|
type ReasoningLevel =
|
|
@@ -697,6 +698,7 @@ function protectedWriteHardDeny(
|
|
|
697
698
|
): { rule: string; reason: string } | undefined {
|
|
698
699
|
const isWrite =
|
|
699
700
|
request.surface === "filesystem-write" ||
|
|
701
|
+
pathSurfaceInfo(request.surface)?.effect === "write" ||
|
|
700
702
|
/\b(?:write|create|delete|rename|chmod|chown)\b/i.test(
|
|
701
703
|
request.operation,
|
|
702
704
|
);
|
|
@@ -773,7 +775,7 @@ function sessionConfig(
|
|
|
773
775
|
}
|
|
774
776
|
|
|
775
777
|
function boundedRequest(surface: string): boolean {
|
|
776
|
-
return
|
|
778
|
+
return isPathSurface(surface);
|
|
777
779
|
}
|
|
778
780
|
|
|
779
781
|
type PermissionsService = {
|
|
@@ -796,7 +798,7 @@ function boundaryRequest(
|
|
|
796
798
|
const surface = evidence.surface;
|
|
797
799
|
const value =
|
|
798
800
|
evidence.resolvedPath ??
|
|
799
|
-
(surface
|
|
801
|
+
(isPathSurface(surface)
|
|
800
802
|
? evidence.path
|
|
801
803
|
: evidence.command ?? evidence.value ?? evidence.destination) ??
|
|
802
804
|
details.skillName ??
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type PathSurfaceFamily = "path" | "external_directory";
|
|
2
|
+
export type PathSurfaceEffect = "read" | "write";
|
|
3
|
+
|
|
4
|
+
export type PathSurfaceInfo = {
|
|
5
|
+
family: PathSurfaceFamily;
|
|
6
|
+
effect?: PathSurfaceEffect;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const PATH_SURFACES: Readonly<Record<string, PathSurfaceInfo>> = Object.freeze({
|
|
10
|
+
path: Object.freeze({ family: "path" }),
|
|
11
|
+
path_read: Object.freeze({ family: "path", effect: "read" }),
|
|
12
|
+
path_write: Object.freeze({ family: "path", effect: "write" }),
|
|
13
|
+
external_directory: Object.freeze({ family: "external_directory" }),
|
|
14
|
+
external_directory_read: Object.freeze({
|
|
15
|
+
family: "external_directory",
|
|
16
|
+
effect: "read",
|
|
17
|
+
}),
|
|
18
|
+
external_directory_write: Object.freeze({
|
|
19
|
+
family: "external_directory",
|
|
20
|
+
effect: "write",
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/** Return metadata only for permission-system's six explicit path surfaces. */
|
|
25
|
+
export function pathSurfaceInfo(surface: unknown): PathSurfaceInfo | undefined {
|
|
26
|
+
return typeof surface === "string" ? PATH_SURFACES[surface] : undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isPathSurface(surface: unknown): boolean {
|
|
30
|
+
return pathSurfaceInfo(surface) !== undefined;
|
|
31
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { homedir, tmpdir } from "node:os";
|
|
2
2
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
import { pathSurfaceInfo } from "../path-surfaces.ts";
|
|
3
4
|
|
|
4
5
|
export type PolicyAuditRisk =
|
|
5
6
|
| "read_only"
|
|
@@ -44,6 +45,10 @@ const KNOWN_SURFACES = new Set([
|
|
|
44
45
|
"ls",
|
|
45
46
|
"path",
|
|
46
47
|
"external_directory",
|
|
48
|
+
"path_read",
|
|
49
|
+
"path_write",
|
|
50
|
+
"external_directory_read",
|
|
51
|
+
"external_directory_write",
|
|
47
52
|
"skill",
|
|
48
53
|
"mcp",
|
|
49
54
|
"network",
|
|
@@ -208,11 +213,12 @@ export function classifyPermission(surfaceValue: unknown, value: unknown, cwd: s
|
|
|
208
213
|
return { ...classified, surface: rawSurface || "bash" };
|
|
209
214
|
}
|
|
210
215
|
const surface = KNOWN_SURFACES.has(rawSurface) ? rawSurface : "custom_tool";
|
|
211
|
-
|
|
216
|
+
const pathSurface = pathSurfaceInfo(surface);
|
|
217
|
+
if (pathSurface || ["read", "write", "edit"].includes(surface)) {
|
|
212
218
|
const pathClass = classifyPath(value, cwd);
|
|
213
|
-
const risk = surface === "read"
|
|
219
|
+
const risk = pathSurface?.effect === "read" || surface === "read"
|
|
214
220
|
? "read_only"
|
|
215
|
-
: surface === "write" || surface === "edit"
|
|
221
|
+
: pathSurface?.effect === "write" || surface === "write" || surface === "edit"
|
|
216
222
|
? "workspace_mutation"
|
|
217
223
|
: "unknown";
|
|
218
224
|
return { surface, signature: `<${surface}>`, bashCategory: "not_bash", risk, pathClass, features: [] };
|
package/src/policy.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isPathSurface } from "./path-surfaces.ts";
|
|
2
|
+
|
|
1
3
|
export type RiskLevel = "low" | "medium" | "high" | "critical";
|
|
2
4
|
|
|
3
5
|
export type ModelDecision = {
|
|
@@ -327,7 +329,7 @@ export function normalizePermissionEvidence(
|
|
|
327
329
|
"unknown";
|
|
328
330
|
const value = nonEmptyString(details.value);
|
|
329
331
|
const isBash = surface === "bash" || surface === "bash_escalated";
|
|
330
|
-
const isPath = surface
|
|
332
|
+
const isPath = isPathSurface(surface);
|
|
331
333
|
const command =
|
|
332
334
|
nonEmptyString(details.command) ??
|
|
333
335
|
parsedPreviewCommand(details.toolInputPreview) ??
|
package/src/ui-auto-confirm.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
ExtensionContext,
|
|
3
3
|
ExtensionUIContext,
|
|
4
4
|
} from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { pathSurfaceInfo } from "./path-surfaces.ts";
|
|
5
6
|
|
|
6
7
|
type UiCustom = ExtensionUIContext["custom"];
|
|
7
8
|
|
|
@@ -61,7 +62,8 @@ export class PermissionUiAutoConfirmer {
|
|
|
61
62
|
|
|
62
63
|
stage(requestId: string, surface: string): boolean {
|
|
63
64
|
this.pruneExpired();
|
|
64
|
-
|
|
65
|
+
const info = pathSurfaceInfo(surface);
|
|
66
|
+
if (!info || !this.enabledSurfaces().includes(info.family)) return false;
|
|
65
67
|
this.pending.set(requestId, {
|
|
66
68
|
surface,
|
|
67
69
|
expiresAt: Date.now() + this.pendingTtlMs,
|
|
@@ -84,10 +86,14 @@ export class PermissionUiAutoConfirmer {
|
|
|
84
86
|
}
|
|
85
87
|
this.pending.delete(event.requestId);
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
const info = pathSurfaceInfo(pending.surface);
|
|
90
|
+
if (!info || !this.enabledSurfaces().includes(info.family)) return;
|
|
88
91
|
// Top-level event.surface is the display tool name (e.g. bash), not the
|
|
89
92
|
// gate. Only request.surface is comparable to the staged allow.
|
|
90
93
|
if (event.gateSurface && event.gateSurface !== pending.surface) return;
|
|
94
|
+
// Directional approvals cannot safely fall back to a legacy payload that
|
|
95
|
+
// omits request.surface: read and write members share the same family.
|
|
96
|
+
if (!event.gateSurface && info.effect) return;
|
|
91
97
|
this.settleActiveForConflict();
|
|
92
98
|
|
|
93
99
|
const attempt: PromptAttempt = {
|