feat(permission-management): add scoped permission management - #533
Conversation
Codex ReviewVerdict: needs changes [P1] Do not connect custom MCP servers before approvalsrc/main/connectors/service.ts:221 Impact: An Ask-policy custom server is connected and its tools/list request runs before the user approves. Remote servers receive configured headers, and stdio servers can execute their command, even when the subsequent approval is denied. Recommendation: Run the authorization gate before listTools; only discover tools and invoke the server after approval or a matching durable grant. [P2] Skip stale legacy grants during migrationsrc/main/compute/permission-grant-adapter.ts:50 Impact: Legacy settings can retain compute grants for projects deleted before this migration. registry.remember rejects those targets, leaving the shared migration promise rejected; every contextual agent compute request then fails before showing an approval prompt. Recommendation: Treat missing-project legacy entries as stale and skip or remove them while continuing valid imports; do not let one invalid grant permanently reject the migration. Summary: Static inspection found two merge-blocking regressions in approval gating and legacy compute-grant migration. |
ad539f8 to
e0eb8ed
Compare
|
Addressed both findings in e0eb8ed.
Validation: 41 focused tests pass, typecheck passes, and ESLint passes for all changed files. The full suite has 8,693 passing tests; only the existing spreadsheet cache-policy failures remain. |
Codex ReviewVerdict: needs changes [P1] Revoke session RPC capabilities when their owner is deletedsrc/main/notebook/local-rpc-server.ts:319 Impact: Session bearer tokens remain accepted for mcpCall and computeCall after session deletion or failed session creation because capabilities are only removed on server shutdown or token rotation. A stale notebook or agent process can therefore continue invoking operations under the deleted session. Recommendation: Add explicit session-capability release, including alias cleanup, and invoke it when ACP session creation fails and when sessions are deleted. Summary: Static inspection found one lifecycle authorization defect in the new session-bound notebook RPC capability flow. Branch and title prechecks are valid. |
e0eb8ed to
943a044
Compare
|
Addressed the session RPC capability lifecycle finding in 943a044.
Validation: 325 focused tests pass across the RPC, ACP, Connector, Compute migration, and Permission Registry suites; typecheck and ESLint pass. |
Codex ReviewVerdict: needs changes [P1] Refresh the registry client after database disconnectsrc/main/permission-grants/registry.ts:232 Impact: Storage migration calls disconnectProjectDbClient() and returns to the running app after cancellation, failure, or discarding a staged copy. The registry still uses this disconnected client for remember, revoke, restore, and prune, so new durable approvals and permission-management actions fail until the app relaunches. Recommendation: Resolve the current Prisma client for each operation, or add a coordinated lifecycle hook that invalidates and reinitializes the registry after disconnect, including refreshing its cache. Summary: The permission registry retains a Prisma client that storage migration disconnects while the app remains running, breaking durable permission mutations until restart. Branch and PR title prechecks passed. |
|
Investigated the registry-client lifecycle finding; no code change is needed. I reproduced the exact production sequence with the real client factory: create the PermissionGrantRegistry from getProjectDbClient, call disconnectProjectDbClient, call registry.remember, then query the row through the newly returned shared client. The mutation and durable read both succeed on the current implementation (1 focused test passed). Prisma reconnects the captured client lazily after $disconnect. The migration also does not replace this database: open-science.db is the authority store under the fixed config root, while the migration moves the configurable data root. Consequently the registry cache is not crossing database identities, and cancellation/discard does not strand it on a different store. Resolving and rehydrating the registry client on every operation would add lifecycle complexity without fixing a reproducible failure, so I am leaving this finding unchanged. |
ewen-poch
left a comment
There was a problem hiding this comment.
Code review: feat(permission-management): add scoped permission management
I read the full surface area — 90 files, +6942/-521 lines, single commit. The work lands as one coherent, end-to-end change rather than a stack of half-related diffs, and the local Vitest run on the PR branch passes 128/128 main-side tests; the full CI matrix is green on mac/win/linux per the workflow runs.
This is a substantial refactor of the permission model. The result is a much clearer story — one durable PermissionGrant table, one Broker identity catalog, and one Renderer projection — than the previous in-memory ConversationPermissionGrantStore. My main takeaways, in the order I'd raise them in a face-to-face review:
Strengths
- Identity → Capability → Registry → Catalog → Projection is a real separation of concerns.
identity-catalog.tsis a closed v1 bootstrap (the right place to keep the v1 promise; dynamic Connector / Compute identities are admitted only by their trusted runtime adapters, and the comment makes that contract explicit).capability.tsis the only place that knows about legacy category keys and the secret-bearing denylist.registry.tsowns durability.catalog.tsowns the renderer projection. Each module is testable in isolation; the cross-cutting wiring lives in one place (src/main/ipc.ts:303-337). - Idempotency is right.
INSERT OR IGNOREon the fingerprint index, revision-checked revoke, and version-stamped IPC snapshots are the three pieces a concurrent app needs and they're each placed where they belong. ThelatestLoadRequestcounter inpermission-grants-store.tsplus theversiondiscard inload()is two independent race guards and both are correct. - Secret-bearing exact-command persistence is opt-in and documented as such.
capability.ts:42-96says plainly that a denylist cannot enumerate credential transports and that only simple local analysis invocations with known non-auth flags become durable. The list of patterns plus the executable/flag/value allowlists is the right shape, and thecapability.test.tscases forAKIA…,ghp_…,x-api-key:, userinfo URLs,--token,X-Auth-Token, etc. cover the obvious cases. - Cascade cleanup is defended on three independent paths. FK CASCADE on the Project relation, the explicit
permissionGrants.prune({ kind: 'project' })after the row is gone (idempotent on recovery replay, asdeletion-coordinator.ts:202-204calls out), and startupreconcilePermissionGrantOwnersfor soft owners (custom UUID MCP servers, compute provider ids). The reconciliation test pins exactly the contract: catalog-shaped ids are never guessed stale; only UUID-shaped custom ids are. Good. - Renderer projection never leaks secrets.
expect(JSON.stringify(snapshot)).not.toContain(digest)incatalog.test.tsis a small but load-bearing test. coveredByprojection is the right UI affordance. Showing "Also allowed globally" without over-granting is the right behavior; the test pins it. TheconnectorProjectionblock correctly projectseffectiveStatefor app-owned and bundled connectors, and the testdoes not project Connector policy onto app-owned MCP toolspins the negative case.- Codex policy-amendment stripping and OpenCode native-skill silent accept are both narrowly scoped and tested. The "projection tests pin this contract — option ID, not position" comment in
permission-broker.ts:91-98is exactly the kind of comment that prevents a future codex-acp rename from silently regressing. The OpenCode fallback is guarded by framework id +title === 'skill'+kind === 'other', which is the minimum needed to be safe. - Project / Global confirmations come through a dedicated dialog with a destructive button. The
codeExecutionboolean ingetScopeConfirmationSubjectcorrectly distinguishes shell / notebook from file operations, so the dialog text adapts (PermissionScopeConfirmationDialog.tsx:43-50). - Compute legacy migration is safely retriable.
migrateLegacyonly clearssettings.jsonafter every Registry write succeeds, and aPermissionGrantTargetUnavailableErrorfor a project deleted between the two phases is swallowed. The next startup is idempotent. - Store test coverage of the queue and the
latestLoadRequestdiscard is exactly what I'd want.consumes consecutive revoke receipts FIFO,does not let an older list response overwrite a newer snapshot,rolls the optimistic removal back when persistence fails,explains when an owner disappeared before restore— these are the cases that fail in production.
Concerns / questions
-
purgeExpiredReceiptsruns only insiderevoke(registry.ts:242-246). Idle registries will keep expired receipt rows in memory. It's bounded by revoke frequency so it doesn't matter much in practice, but I'd either sweep periodically or self-dismiss viasetTimeout(also keyed off the existingreceiptTtlMs). Tiny. -
compute-grant-adapter.ts:55-58migrates legacysettings.jsoncompute grants using a magicsessionId: ''. The fingerprint then includes the empty string, which is fine because the legacy shape has no session id, but it would read more cleanly ifcomputeCapabilityacceptedsessionId?: stringand the migration path constructed the capability withqualifier: { mode: 'any' }directly. Cosmetic. -
connector-broker.ts:42-58preflightthrows on a blocked tool, butauthorizedoes not re-check policy. A caller that goes straight toauthorizewithout callingpreflightfirst will not see the blocked-throw. Today every caller does call preflight, so this is latent, not a bug — but I'd consider gatingauthorizeonpreflight(request)at the top, so the failure mode is impossible rather than convention. The same pattern works for thedeferRememberbranch. -
permission-broker.ts:90-99CODEX_MCP_PERSISTENT_ALLOW_OPTION_IDis the right shape, but the project now has three places that name the Codex option-ID contract (permission-broker.ts×2 and theresolveAllowOptionIdshape). If/when codex-acp renames these, the failure will be loud because the tests pin the contract — but I'd still co-locate the three strings into a single Codex policy-amendment constant block so a rename is one edit. -
registry.ts:411-446pruneformcp_serverandcompute_providerowners usesLIKEwith manually escaped%/_/\\. That's correct, but it's also the kind of code that benefits from a single helper (escapeLikePattern) since the same two cases duplicate the escape. Not a bug, just a small dedup opportunity. -
The
SettingsPage"Manage permissions" affordance wired fromConnectorDetailViewcorrectly hands off to the right sub-view (custom-server edit vs. bundled detail) — nice touch. One minor consideration: a click on thepolicyHintlink insidePermissionsPaneljumps tonavigateConnectorsand closes settings, which is the right behavior, but the user loses their current filter selection inPermissionsPanel. AuseRetentionor URL-state handoff would preserve it on return; right now re-opening Settings lands on the model panel, which is the default. Out of scope for this PR, but worth noting for the next Settings pass. -
WorkspaceMessageScroller.tsx:19-20is part of the "collapse legacy Artifact version aliases in conversation history" bullet in the PR body. That change is unrelated to permissions and ships in the same commit. Tests pass and the contract is right, but a permissions-only PR is the cleaner narrative — splitting it out would have made review materially easier. -
No design document in the repo is the explicit non-goal, and the inline comments in
capability.ts,registry.ts,permission-broker.ts:91-98, andreconciliation.ts:43-48cover the load-bearing decisions. I don't think a separate doc is required, but if you do write one for the user-visible "what counts as a remembered grant" story, link it fromdocs/internal/.
Test verification
- Local run on the PR branch (vitest): 10 test files / 128 tests pass in the permission-grants / permission-broker / connector-broker / deletion-coordinator / registry surface.
- Workflow runs for this PR:
Verifygreen onmacos-14,ubuntu-latest,windows-latest;Analyze(js + python) green;CodeQLgreen;Validate commit messagesgreen. - The
simplify: no permission data is added to settings.jsonnon-goal is verified: only one new table (PermissionGrant) is added toprisma/schema.prisma, and the runtime DDL inprisma-client.ts:29-60is byte-compatible with the generated client. No settings.json writes for grants.
Bottom line
I'd merge after the small dedup / consistency items in concerns #1-#5 are addressed or explicitly deferred. Concerns #6-#8 are notes for follow-ups rather than blockers. The architecture is the right one; the secret-handling story is honestly documented; and the test surface covers the cases that actually break in production. Good PR.
943a044 to
6c670fb
Compare
Codex ReviewVerdict: needs changes [P1] Release Notebook capability when session setup failssrc/main/acp/runtime.ts:4054 Impact: The production connection provider issues a bearer capability and stores it in the RPC server. Resume, context-reset, and fresh-adoption paths call this method without a surrounding cleanup path. If later agent setup fails, the failed agent retains a valid token that can still invoke session-bound mcpCall or computeCall operations despite no successfully attached app session. Recommendation: Treat the returned capability as provisional until the session is fully attached, and release it in every resume/reset/adoption failure path, including failures after the agent receives the MCP configuration. Transfer ownership only after successful adoption. Summary: A session-scoped Notebook RPC capability is not released when resume or session adoption fails after the connection is issued. |
6c670fb to
6ae05b2
Compare
|
Addressed the actionable review findings in 6ae05b2:
Validation: focused regression tests pass; ACP runtime 270/270; typecheck passes; lint reports 0 errors; the full suite is 8731 passed with only the same 3 pre-existing spreadsheet-cache-policy failures. Deferred as lower trade-off follow-ups: timer-driven receipt purging, Codex option-ID constant consolidation, Settings Connector filter retention, and splitting the pre-existing Artifact alias cleanup. Connector policy re-checking is already enforced by authorize defaulting to preflight, while app-owned ACP MCP servers intentionally remain outside external Connector policy. |
Codex ReviewVerdict: needs changes [P2] Session grants can fail before the new session is persistedsrc/main/ipc.ts:309 Impact: A first-turn permission response can arrive before the renderer's asynchronous session save completes. The live-scope check then cannot find the session on disk, so selecting “This session” is converted into a cancelled permission and no grant is recorded. Recommendation: Treat active ACP sessions as live in the registry, or await/retry session persistence before validating and recording a session-scoped grant. [P2] Deleting an ACP session leaves the notebook runtime with a revoked cached tokensrc/main/acp/runtime.ts:3271 Impact: This new cleanup revokes the session's notebook RPC capabilities, including the cached control token held by NotebookRuntimeService, but ACP deletion does not shut down that service's RuntimeSession. A live or subsequent REPL host.mcp/host.compute call can therefore use a stale token and fail authorization, while notebook executors remain alive until broader shutdown. Recommendation: Coordinate ACP/session deletion with NotebookRuntimeService.shutdown or add an explicit release callback that clears the cached control connection and removes the notebook runtime session before revoking its capabilities. Summary: Static review found two concrete lifecycle and persistence defects. Branch and title prechecks are valid; no tests or project commands were run. |
6ae05b2 to
92c45db
Compare
|
Implemented the actionable findings against the current head:
The following suggestions conflict with previously approved product behavior and were intentionally not applied:
No SQL table, column, relationship, or settings.json change was introduced by this follow-up. Validation: 122 targeted tests passed; typecheck passed; full lint passed with 0 errors and 23 existing warnings; full Vitest passed 8,737 tests with four pre-existing failures confined to the untouched spreadsheet cache-policy test. |
Codex ReviewVerdict: needs changes [P1] Prevent concurrent grant writes from resurrecting revoked authoritysrc/main/permission-grants/registry.ts:305 Impact: The database transaction completes before the in-memory cache is updated, while revoke/prune delete the cache after their transactions. An overlapping remember can therefore reinsert a grant into Recommendation: Serialize registry mutations through one queue/lock that covers both the database transaction and cache update, or make cache updates generation-aware and ensure [P1] Do not auto-approve untrusted OpenCode requests shaped like the native Skill toolsrc/main/acp/permission-policy.ts:154 Impact: Any OpenCode permission request with Recommendation: Require a trusted native-tool provenance marker or a verified OpenCode tool identity. Do not treat missing provider metadata as evidence of native Skill; otherwise route the request through normal approval. Summary: Two permission-boundary defects were introduced. Branch and pull-request title prechecks are valid. |
92c45db to
cdbf3f3
Compare
|
Addressed the actionable standards findings on the current head:
Intentional trade-offs:
Final checks after the last material edit: 139/139 permission-focused tests passed; typecheck passed; full lint passed with 0 errors and 23 pre-existing warnings; the full suite passed 8,745 tests with the same four failures confined to the untouched spreadsheet cache-policy test. |
Codex ReviewVerdict: needs changes [P1] Do not auto-approve title-only OpenCode Skill requestssrc/main/acp/permission-policy.ts:154 Impact: A request with kind Recommendation: Require trusted provider identity or another proof that the request is OpenCode’s built-in Skill loader; reject title-only and MCP/custom requests. Summary: One concrete permission-bypass defect was found in the OpenCode Skill auto-approval path. |
088a01e to
1d6cbc8
Compare
|
Addressed the actionable review findings on the latest
Intentionally not changed:
Validation on |
Codex ReviewVerdict: needs changes [P1] Revoke session-bound notebook tokens on ACP disconnectsrc/main/notebook/local-rpc-server.ts:363 Impact: ACP teardown clears runtime session maps without releasing these tokens. A stale agent can continue invoking mcpCall or computeCall with the old session context after disconnect. Recommendation: Release all session and provisional-alias capabilities in every ACP teardown path, including close and unexpected connection loss, before clearing routing maps. Summary: Static inspection found one merge-blocking lifecycle/security defect. No tests or project commands were run. |
1d6cbc8 to
d46de9e
Compare
|
Addressed the remaining actionable review findings in d46de9e:
The Artifact Version alias collapse remains in this PR because it fixes the reported duplicate GENERATED cards for historical alias/native IDs and was explicitly included in the requested scope. Validation: typecheck passed; lint passed with 0 errors and 23 existing warnings; focused renderer tests passed 54/54; focused ACP cleanup tests passed 3/3; the full suite passed 587 files and 8,798 tests, with only the three known failures in the untouched spreadsheet-cache-policy test file. |
Codex ReviewVerdict: needs changes [P2] Owner pruning leaves undo receipts restorablesrc/main/permission-grants/registry.ts:414 Impact: Deleting an MCP server or compute provider prunes its grants but leaves revocation receipts active. Undo can then restore a grant for the deleted owner because liveness checks cover only project/session scopes, leaving stale authority that may apply if the owner identity is reused. Recommendation: Invalidate receipt rows during owner pruning, or add MCP/compute owner liveness checks to restore and report target-unavailable. Summary: Static inspection found one lifecycle defect. Branch and title prechecks are valid. |
46a2b69 to
2590167
Compare
|
Addressed both current-head review findings in
Validation: focused Compute/permission-policy/broker/runtime tests pass 343/343; typecheck and changed-file ESLint pass; |
Codex ReviewVerdict: needs changes [P1] Do not persist approvals for mutable scripts by command text alonesrc/main/permission-grants/capability.ts:84 Impact: Project or global approval for commands such as Recommendation: Restrict durable approvals to commands whose safety is independent of mutable script contents, or include the canonical script path and content identity in the qualifier and require re-approval whenever they change. Summary: One blocking security issue was found in durable command permission matching. |
45c7642 to
dfffc47
Compare
|
Addressed the mutable-script approval finding in Durable exact-command grants are now restricted to commands whose safety is independent of mutable workspace content. V1 retains scoped memory for the content-independent Regression coverage now verifies both sides of the boundary: |
Codex ReviewVerdict: needs changes [P1] Serialize cache updates with grant mutationssrc/main/permission-grants/registry.ts:350 Impact: After the revoke transaction commits, another remember operation can insert and cache the same fingerprint before this deletion runs. The revoke then removes the newly remembered record from the in-memory cache even though it remains durable, causing stale authorization decisions and incorrect settings snapshots; the reverse interleaving can briefly authorize a grant after revocation. Recommendation: Serialize registry mutations, including the database transaction and cache update, or reconcile the cache from the committed database state using a mutation lock/version check before publishing changes. Summary: Static inspection found a concurrent mutation race in the permission-grant registry that can leave its authorization cache inconsistent with the database. |
dfffc47 to
36a2179
Compare
|
Addressed the current-head review findings in |
Codex ReviewVerdict: mergeable No actionable findings. Summary: No concrete blocking defects found in the pull request changes. Branch and pull request title prechecks passed. |
36a2179 to
b7b7c44
Compare
|
Rebased the single feature commit onto The two conflicts were both in the new shared Web RPC registration seam. The resolution preserves the latest Post-rebase validation:
The PR description now points to the rebased HEAD and latest main baseline. |
Codex ReviewVerdict: needs changes [P1] Custom server edits retain grants for changed endpointssrc/main/ipc.ts:737 Impact: Durable grants are pruned when a custom server is removed, but not when its URL, command, environment, headers, or transport changes. Because the server ID remains unchanged, existing approvals continue authorizing calls against the new endpoint or credentials without re-approval. Recommendation: Invalidate security-sensitive grants when custom server configuration changes, or version/rotate the server identity so the changed configuration requires fresh approval. Summary: Static inspection found one merge-blocking security defect in custom MCP grant lifecycle handling. |
b7b7c44 to
2c8e2dc
Compare
|
Addressed the custom MCP grant-lifecycle finding in
Validation: 105 focused Settings, IPC, Registry, and Connector tests pass; Node and Web typechecks pass; changed-file ESLint passes with 0 errors; |
Codex ReviewVerdict: needs changes [P2] Register permission handlers with the Web RPC registrysrc/main/permission-grants/ipc.ts:115 Impact: These handlers use raw ipcMain.handle, bypassing the shared registry that publishes Web RPC channels. Consequently, the web bootstrap omits permissions:list, permissions:revoke, and permissions:restore, so the Permissions settings panel cannot load or mutate grants in web mode. Recommendation: Import and use ipcMainHandle for all three permission handlers so Electron IPC and Web RPC expose the same implementation. Summary: Static inspection found one concrete Web RPC integration defect. |
2c8e2dc to
788633d
Compare
|
Addressed the Web RPC registration finding in
Validation: permission IPC, IPC handler registry, and shared Web RPC contract suites pass (3 files, 8 tests); Node and Web typechecks pass; changed-file ESLint and Prettier pass; |
Codex ReviewVerdict: needs changes [P1] Revalidate pending custom-server approvals before remembering grantssrc/main/connectors/service.ts:246 Impact: If a custom MCP server endpoint, command, or credentials change while approval is pending, the update prunes existing grants but does not invalidate the pending call. Approval then remembers a grant under the unchanged server ID, allowing future calls to the new configuration without re-approval. Recommendation: Bind approvals to a server configuration generation or fingerprint and revalidate it before remembering or dispatching; cancel pending approvals when security-sensitive configuration changes. [P1] Prevent deleted compute hosts from restoring stale grantssrc/main/compute/compute-approval-broker.ts:126 Impact: A compute approval can remain pending while its host is deleted and grants are pruned. Approving afterward writes the grant anyway; recreating a host with the same deterministic provider ID then inherits authorization for the previous host. Recommendation: Invalidate pending approvals on host deletion and re-check the current host identity/generation before persisting the grant, or key grants by an immutable host identity rather than the reusable provider ID. Summary: Static inspection found two authorization races where pending approvals can recreate grants after their owners are invalidated. |
788633d to
be7df7c
Compare
|
Addressed both pending-owner authorization races from the review on
This does not change the permission schema, scope semantics, RPC contracts, or UI. Validation:
|
Codex ReviewVerdict: needs changes [P1] Invalidate compute grants before provider ID reusesrc/main/compute/ipc.ts:449 Impact: Provider deletion completes before grant pruning, and pruning is awaited separately. A concurrent recreation of the same provider ID can resolve the cached old session/project/global grant and run on the replacement host without approval. Recommendation: Serialize compute deletion, grant invalidation, and provider creation, or block provider-ID reuse until pruning completes. Also validate the persisted grant against the current host identity before auto-allowing. [P2] Track cancellation while grant resolution is pendingsrc/main/acp/permission-broker.ts:627 Impact: Registry-backed permission requests await asynchronous grant resolution before entering pendingRequests. A session cancellation during that await is missed by cancelForSession; the continuation can later emit a stale permission prompt and leave the cancelled ACP call unresolved. Recommendation: Register the pending request before awaiting registry resolution, or track a per-session cancellation generation and resolve/cancel the request without emitting it when the generation changes. Summary: Static inspection found two concrete permission-lifecycle races introduced by the change. |
be7df7c to
88e4a68
Compare
|
Addressed the two lifecycle race findings in
Regression coverage was added for provider-ID reuse during deferred cleanup, replacement-host durable auto-allow denial, and cancellation during deferred grant resolution. Focused tests, Node/Web typechecks, ESLint, |
Codex ReviewVerdict: needs changes [P1] Rebind the registry after project DB disconnectssrc/main/permission-grants/registry.ts:236 Impact: Data-root migration disconnects this captured Prisma client, but the registry continues using it. Subsequent permission approvals and management mutations fail for the rest of the process. Recommendation: Reacquire/reload the client after migration teardown, or add an explicit registry rebind hook. Summary: Static inspection found one merge-blocking lifecycle defect. Branch and pull-request title prechecks are valid. |
Centralize stable Broker identities and persist Global, Project, and Session grants across ACP runtimes. Add confirmation, connector coordination, Settings revoke/undo controls, and navigation back to remembered sessions. Preserve project deletion intent across grant cleanup failures, migrate legacy Compute grants only after a complete import, repair historical Artifact aliases idempotently, and keep provider-native web tools Once-only.
88e4a68 to
7c58c27
Compare
|
Addressed the registry client-lifecycle finding in The PermissionGrant registry now uses its existing A regression test retires the initialization client, swaps the provider to a newly opened client, and verifies that a subsequent grant persists without touching the retired instance. Registry tests pass (14/14), Node/Web typechecks, ESLint, Prettier, |
Problem
Permission approvals were tied to ACP-specific tool names and runtime lifetimes. The app could not consistently remember, explain, or revoke approvals across Claude Code, Codex, and OpenCode, and Connector policy could conflict with per-call approvals.
Proposed change
builtin_toolnamespace while keeping provider built-ins Once-only until an explicit cross-framework mapping exists.git statusby a redacted digest. Path-qualified executables, provider built-ins, interpreters, scripts, test runners, commands with unproven syntax, and secret-bearing inputs remain Once-only.Permission flow
flowchart LR Request["ACP / Connector / Compute request"] --> Identity["Broker stable identity"] Identity --> Policy{"Connector policy applies?"} Policy -->|"Block"| Deny["Deny"] Policy -->|"Allow"| Once["Send downstream allow-once"] Policy -->|"Require approval / ACP"| Registry{"Matching durable grant?"} Registry -->|"Yes"| Once Registry -->|"No"| Prompt["User approval"] Prompt -->|"Once"| Once Prompt -->|"Session / Project / Global"| Confirm["Broad-scope confirmation when required"] Confirm --> Persist["PermissionGrant registry"] Persist -->|"Commit succeeds"| Once Persist -->|"Commit fails"| Deny Persist --> Settings["Settings → Permissions"] Settings -->|"Revoke"| Receipt["Short-lived Undo receipt"] Receipt -->|"Undo"| PersistScope and non-goals
repl_execute -> host.mcp, where runner and nested Connector authorization are intentionally distinct. A single-use call permit is deferred until a Connector is directly exposed through ACP; no current direct path can double-prompt.settings.json. LegacycomputeGrantsis read only for retry-safe migration and is cleared only after every source row imports successfully; partial or stale-target batches retain the complete legacy source.Acceptance criteria and validation
The checks below ran after the final material edit against commit
7c58c27, based onmain@a6ab4fc.npm test -- --run src/main/acp/permission-broker-registry.test.ts src/main/acp/permission-broker.test.ts src/renderer/src/lib/acp/useAcpRuntime.test.tsgit status; path-qualified executables and mutable scripts remain Once-onlynpx vitest run src/main/permission-grants/capability.test.tsnpx vitest run src/main/acp/runtime.test.ts -t "does not use the OpenCode Skill fallback when tool metadata names a different tool"npx vitest run src/main/permission-grants/registry.test.tsnpx vitest run src/main/projects/deletion-coordinator.test.ts src/main/permission-grants/registry.test.tsnpx vitest run src/renderer/src/components/PermissionUndoSnackbar.test.tsxnpm test -- --run src/main/connectors/service.test.ts src/main/settings/service.connectors.test.ts src/main/settings/ipc.test.tsnpm test -- --run src/main/compute/compute-approval-broker.test.ts src/main/compute/ipc.test.tsnpm run check:web-api-mapplus permission IPC registry testsnpm run typechecknpx eslint <changed files>npm testspreadsheet-cache-policy.test.ts; GitHub Verify passed on macOS, Ubuntu, and WindowsUncovered/accepted risks:
host.mcpcalls continue to require their own authorization.Review focus