diff --git a/AGENTS.md b/AGENTS.md index e965d44..f4e5c92 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,7 +64,7 @@ - Codex fast mode 只为符合条件的 `openai-codex-responses` 请求设置 priority service tier,由 `/fast-mode` 在会话内控制。 - 权限策略默认允许常规工具,允许 FFF 工具;拒绝 Bash 直搜和敏感凭据路径;Git 非只读操作、包管理、外部目录、文件/系统/网络高风险操作与普通 MCP 调用先由 `pi-permission-system` 判为 `ask`。 - `pi-permission-auto-review` 不是独立 `tool_call` gate,而是 `pi-permission-system` authorizer chain 中名为 `auto-review` 的链路;只复核权限基线产生的 `ask`,不会重复处理已 `allow` 或已 `deny` 的请求。 -- reviewer 返回 `allow` 时自动批准、返回 `deny` 时直接拒绝,配置、模型、认证、超时或响应异常时必须 `defer` 到正常人工提示。`pi-permission-system` 的 delegation envelope 继续禁止 authorizer 自动批准全部 `path` 请求;`external_directory` 只对内置 `read` 接受 reviewer 的 `allow`,写入、编辑、Bash、未知工具和其他外部访问仍转人工。 +- reviewer 返回 `allow` 时自动批准、返回 `deny` 时直接拒绝,配置、模型、认证、超时或响应异常时必须 `defer` 到正常人工提示。`pi-permission-system` 的 delegation envelope 继续禁止 authorizer 自动批准全部 `path` 请求;`external_directory` 对内置只读路径工具(`read`、`find`、`grep`、`ls`)接受 reviewer 的 `allow`,写入、编辑、Bash、未知工具和其他外部访问仍转人工。 - 默认 reviewer 为 `openai-codex/codex-auto-review`、low reasoning、90 秒总重试预算和内置 Codex Guardian 风格策略;只把 active branch 中的直接用户消息与已识别结构化问答作为授权证据,assistant/tool/compaction 内容不能自行授权。 - `config/pi-permission-system.json` 必须显式配置 `authorizerChain: ["auto-review"]`,并把需要自动复核的 Git 非只读操作、包管理及其他类别声明为 `ask`;硬 `deny` 不得改成可由模型覆盖的 `ask`。 - `pi-lsp` 的 TypeScript/JavaScript 后端由根包固定依赖提供,`extensions/lsp.ts` 使用当前 Node 可执行文件直接启动包内 `typescript-language-server` CLI,不得依赖或调用 VS Code GUI;Kotlin/JDT LS 仍使用组合包部署的 `kotlin-lsp --stdio` 与 `jdtls` 配置,根包不安装这两个系统可执行文件,便捷脚本可在用户逐项明确确认后通过 Homebrew 安装 Kotlin LSP、Java 21 和 JDT LS。 @@ -113,7 +113,7 @@ - 扩展入口是 `pi-permission-system/src/index.ts`,公共跨扩展服务入口是 `pi-permission-system/src/service.ts`,测试保留在该目录的 `test/`。 - 同步上游必须从明确 tag/commit 移植并记录快照;保留上游 `LICENSE`、作者和来源,不导入 `dist/`、嵌套 `.git` 或上游包目录的 `.pi` 本地状态。 -- 本地 delegation envelope 只允许内置 `read` 对 `external_directory` 接受 authorizer 的 `allow`;`write`、`edit`、Bash、未知工具、未确定 surface 以及全部 `path` ask 必须继续 `defer` 到终端人工 authority。 +- 本地 delegation envelope 允许内置只读路径工具(`read`、`find`、`grep`、`ls`)对 `external_directory` 接受 authorizer 的 `allow`;`write`、`edit`、Bash、未知工具、未确定 surface 以及全部 `path` ask 必须继续 `defer` 到终端人工 authority。 - 修改 gate、authorizer chain、delegation envelope、子代理转发或公共 service 类型时,必须同步更新包内 README/架构文档和对应测试。 - 根包必须通过 `file:./pi-permission-system` 提供运行时依赖,包装入口必须直接加载仓库源码;不得同时加载 npm 预编译入口或第二个 permission-system 实例。 diff --git a/README.md b/README.md index 8fb47db..b7f7707 100644 --- a/README.md +++ b/README.md @@ -251,7 +251,7 @@ codegraph status - `allow`:权限基线已明确允许的常规操作不会调用 reviewer。 - `deny`:Bash 直搜、敏感凭据路径等硬拒绝不会交给 reviewer,也不能被其绕过。 -- `ask`:Git 非只读操作、包管理、文件/系统/网络/环境操作和普通 MCP 调用交给 reviewer;`path` 仍全部受 delegation envelope 保护。`external_directory` 仅允许内置 `read` 接受 reviewer 的 `allow`,`write`、`edit`、Bash、未知工具及其他外部目录访问仍降级为人工确认。 +- `ask`:Git 非只读操作、包管理、文件/系统/网络/环境操作和普通 MCP 调用交给 reviewer;`path` 仍全部受 delegation envelope 保护。`external_directory` 允许内置只读路径工具(`read`、`find`、`grep`、`ls`)接受 reviewer 的 `allow`,`write`、`edit`、Bash、未知工具及其他外部目录访问仍降级为人工确认。 reviewer 默认使用 `openai-codex/codex-auto-review`、low reasoning 和内置 Codex Guardian 风格策略,并读取当前 session active branch 中的可信用户证据。可通过 `/permission-auto-review` 查看或调整全局/项目配置;无配置时使用源码内置默认值。 diff --git a/pi-permission-auto-review/README.md b/pi-permission-auto-review/README.md index d2cbf99..00caf5a 100644 --- a/pi-permission-auto-review/README.md +++ b/pi-permission-auto-review/README.md @@ -106,7 +106,7 @@ Transcript rendering uses separate 10k-token message and tool budgets with per-e - Model, authentication, timeout, provider, or response-format failures defer to the normal human prompt. - Unexpected internal review failures also defer to the human prompt instead of escaping into the permission gate. - Three consecutive denials, or ten denials in the latest fifty reviews, open a circuit breaker until the next Pi turn. -- my-pi's maintained pi-permission-system keeps every `path` request inside the delegation envelope. For `external_directory`, only the built-in `read` tool may accept an auto-review `allow`; write, edit, bash, unknown tools, and undetermined surfaces are downgraded to the normal human prompt. +- my-pi's maintained pi-permission-system keeps every `path` request inside the delegation envelope. For `external_directory`, built-in read-only path tools (`read`, `find`, `grep`, `ls`) may accept an auto-review `allow`; write, edit, bash, unknown tools, and undetermined surfaces are downgraded to the normal human prompt. ### Diagnostics diff --git a/pi-permission-system/README.md b/pi-permission-system/README.md index d892454..51e6daf 100644 --- a/pi-permission-system/README.md +++ b/pi-permission-system/README.md @@ -11,7 +11,7 @@ Permission enforcement extension for the [Pi](https://pi.mariozechner.at/) codin > **Fork notice:** This package is a full fork of [MasuRii/pi-permission-system](https://github.com/MasuRii/pi-permission-system), published to npm as `@gotgenes/pi-permission-system`. > It has diverged substantially from upstream in config format, internal architecture, and permission model. > -> **my-pi maintenance note:** This directory was imported from upstream tag `pi-permission-system-v26.2.1` at commit `ec4fdb11343dc94f7185b113e559a4cf9f8dc035`. It is loaded from source and maintained directly by my-pi; npm `dist` output is not used. The local delegation envelope accepts an authorizer `allow` for built-in `read` access to `external_directory`, while write, edit, bash, unknown tools, and all `path` asks still defer to human confirmation. +> **my-pi maintenance note:** This directory was imported from upstream tag `pi-permission-system-v26.2.1` at commit `ec4fdb11343dc94f7185b113e559a4cf9f8dc035`. It is loaded from source and maintained directly by my-pi; npm `dist` output is not used. The local delegation envelope accepts an authorizer `allow` for built-in read-only path tools (`read`, `find`, `grep`, `ls`) accessing `external_directory`, while write, edit, bash, unknown tools, and all `path` asks still defer to human confirmation. ## What It Does diff --git a/pi-permission-system/docs/architecture/architecture.md b/pi-permission-system/docs/architecture/architecture.md index 8eecff2..3015705 100644 --- a/pi-permission-system/docs/architecture/architecture.md +++ b/pi-permission-system/docs/architecture/architecture.md @@ -856,7 +856,7 @@ src/ │ ├── authorizer-chain.ts `composeAuthorizerChain(links, terminal, query, log)` - folds non-terminal `NamedAuthorizer` links ahead of the context-selected terminal (`defer` → next link, `allow`/`deny` → decision stamped `decidedBy: {kind: "authorizer", name, verdict, reason}` at the point the loop breaks, so a link that deferred is not credited), injecting `query` and the review-log `log` into each link; zero links returns the terminal instance (identity) │ ├── decision-source.ts `DecisionSource` discriminated union (`user | authorizer | rule | session_approval | yolo | infrastructure_read | unavailable | gate_error | forwarded`) + depth-bounded tolerant guard `asDecisionSource`. Constraint: each variant is self-contained (it repeats its own surface/pattern/origin/name/reason) because the forwarded response file carries no such columns to lean on; the recursive `forwarded` variant is read off disk, so its guard is depth-bounded and rejects an over-deep chain whole rather than truncating it │ ├── authorizer-registry.ts `AuthorizerRegistry` (+ `AuthorizerLookup`/`AuthorizerRegistrar` ISP interfaces) - name → link `authorize` map mirroring `ToolAccessExtractorRegistry`; one instance in `index.ts`, exposed cross-extension via `PermissionsService.registerAuthorizer`; throw-on-duplicate, identity-guarded disposer -│ ├── delegation-envelope.ts `encloseInDelegationEnvelope(authorize)` + `DELEGATION_EXCLUDED_SURFACES` - the bounded-delegation checkpoint (ADR 0007 §5): caps a link's `allow` on `path`, undetermined surfaces, and `external_directory` except for the built-in `read` tool; deny/defer pass through +│ ├── delegation-envelope.ts `encloseInDelegationEnvelope(authorize)` + `DELEGATION_EXCLUDED_SURFACES` - the bounded-delegation checkpoint (ADR 0007 §5): caps a link's `allow` on `path`, undetermined surfaces, and `external_directory` except for built-in read-only path tools (`read`, `find`, `grep`, `ls`); deny/defer pass through │ ├── local-user-authorizer.ts `LocalUserAuthorizer` class - `TerminalAuthorizer` for a session with UI and the single `permissions:ui_prompt` emit site: renders a forwarded ask's provenance as a non-degraded broadcast + `(Subagent)` title, then dispatches to the inline keybind dialog (TUI) or the `select`/`input` fallback │ ├── permission-dialog.ts Dialog option semantics + `requestPermissionDecisionFromUi` (`select`/`input` fallback) + `PermissionPromptDecision` (whose `decidedBy` is required) and `UnattributedDecision` (the same minus it); the mode dispatch lives in `permission-prompt-component.ts` │ ├── permission-prompt-decision.ts Pure decision model (`reducePrompt` + `PromptModelConfig`/`PromptViewState`) for the inline keybind dialog - hotkey arming (double-press), step transitions, reason validation; no SDK/TUI imports diff --git a/pi-permission-system/docs/configuration.md b/pi-permission-system/docs/configuration.md index e6a0ab5..640504f 100644 --- a/pi-permission-system/docs/configuration.md +++ b/pi-permission-system/docs/configuration.md @@ -227,7 +227,7 @@ Three invariants govern the chain: 3. **Registration alone grants no authority.** Installing a judge extension gives it nothing; a link decides nothing until you name it here (opt-in activation). -The chain owner caps every link with a **bounded-delegation checkpoint**. The `path` surface is always excluded. On `external_directory`, my-pi permits an `allow` only for the built-in `read` tool; write, edit, bash, extension/unknown tools, and missing tool identity are downgraded to `defer`. +The chain owner caps every link with a **bounded-delegation checkpoint**. The `path` surface is always excluded. On `external_directory`, my-pi permits an `allow` for the built-in read-only path tools (`read`, `find`, `grep`, `ls`); write, edit, bash, extension/unknown tools, and missing tool identity are downgraded to `defer`. Deny and defer are never capped. The gate surface remains authoritative: a `write` blocked by a `path` rule is capped even though the displayed tool name is `write`. This holds for an ask forwarded up from a subagent exactly as it does for a local one. diff --git a/pi-permission-system/src/authority/delegation-envelope.ts b/pi-permission-system/src/authority/delegation-envelope.ts index 17f73cf..6bce3f8 100644 --- a/pi-permission-system/src/authority/delegation-envelope.ts +++ b/pi-permission-system/src/authority/delegation-envelope.ts @@ -8,12 +8,14 @@ * verdict — it never turns a `defer`/`deny` into an `allow`. * * The excluded set is the whole `path` surface plus `external_directory`, with - * one bundle-maintained exception: the built-in `read` tool may accept a link's - * `allow` for an external-directory ask. Mutating tools, bash, extension tools, - * and unknown tools remain capped to the terminal human authority. A finer - * secret-shaped-`path` exclusion remains deferred; `path` stays fully excluded. + * one bundle-maintained exception: built-in read-only path tools may accept a + * link's `allow` for an external-directory ask. Mutating tools, bash, extension + * tools, and unknown tools remain capped to the terminal human authority. A + * finer secret-shaped-`path` exclusion remains deferred; `path` stays fully + * excluded. */ +import { READ_ONLY_PATH_BEARING_TOOLS } from "#src/access-intent/path-surfaces"; import type { Authorizer } from "./authorizer"; import type { PromptPermissionDetails } from "./permission-prompter"; @@ -23,9 +25,6 @@ export const DELEGATION_EXCLUDED_SURFACES: ReadonlySet = new Set([ "path", ]); -/** Read-only external-directory access explicitly delegable in my-pi. */ -const DELEGABLE_EXTERNAL_DIRECTORY_TOOLS: ReadonlySet = new Set(["read"]); - /** * Wrap a link's `authorize` so an `allow` on an excluded surface is capped to * `defer`. All other verdicts, and `allow`s on non-excluded surfaces, pass @@ -47,8 +46,8 @@ export function encloseInDelegationEnvelope( /** * Whether an allow verdict exceeds the delegation envelope. The gate-computed * surface is authoritative. `path` remains fully excluded; external-directory - * access is excluded unless it comes from the built-in `read` tool. Unknown - * surfaces fail safe to the terminal authority. + * access is excluded unless it comes from a built-in read-only path tool. + * Unknown surfaces fail safe to the terminal authority. */ function isExcludedSurface(details: PromptPermissionDetails): boolean { const surface = details.accessIntent?.surface ?? details.surface ?? undefined; @@ -58,7 +57,7 @@ function isExcludedSurface(details: PromptPermissionDetails): boolean { if (surface === "external_directory") { return !( details.toolName !== undefined && - DELEGABLE_EXTERNAL_DIRECTORY_TOOLS.has(details.toolName) + READ_ONLY_PATH_BEARING_TOOLS.has(details.toolName) ); } return DELEGATION_EXCLUDED_SURFACES.has(surface); diff --git a/pi-permission-system/test/authority/delegation-envelope.test.ts b/pi-permission-system/test/authority/delegation-envelope.test.ts index fe9f725..5065c66 100644 --- a/pi-permission-system/test/authority/delegation-envelope.test.ts +++ b/pi-permission-system/test/authority/delegation-envelope.test.ts @@ -91,15 +91,20 @@ describe("encloseInDelegationEnvelope", () => { expect(verdict).toEqual({ kind: "allow" }); }); - it("keeps an allow on external_directory for the built-in read tool", async () => { - const enclosed = encloseInDelegationEnvelope(makeLink({ kind: "allow" })); - const verdict = await enclosed( - makeDetails("external_directory", undefined, "read"), - query, - log, - ); - expect(verdict).toEqual({ kind: "allow" }); - }); + it.each(["read", "find", "grep", "ls"] as const)( + "keeps an allow on external_directory for the built-in read-only tool %s", + async (toolName) => { + const enclosed = encloseInDelegationEnvelope( + makeLink({ kind: "allow" }), + ); + const verdict = await enclosed( + makeDetails("external_directory", undefined, toolName), + query, + log, + ); + expect(verdict).toEqual({ kind: "allow" }); + }, + ); it("never caps a deny, even on an excluded surface", async () => { const enclosed = encloseInDelegationEnvelope(