Skip to content

feat(core): edit saved permissions - #56659

Open
rekram1-node wants to merge 1 commit into
devfrom
saved-permission-edit
Open

rekram1-node wants to merge 1 commit into
devfrom
saved-permission-edit

Conversation

@rekram1-node

@rekram1-node rekram1-node commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

Saved permissions ("Always allow" answers) can be listed and removed through the API, but not changed. To narrow a broad approval, such as a saved shell: *, a client has to delete it, and there is no way to recreate a narrower one. This blocks a UI for reviewing saved approvals (#50600).

Change

Adds PATCH /api/permission/saved/:id (permission.saved.update), which changes a saved permission's action and/or resource. The project can't change, and the rule stays an allow.

 /api/permission/saved
   GET     permission.saved.list
+  PATCH   permission.saved.update   { action?, resource? } → { data: PermissionSaved.Info }
   DELETE  permission.saved.remove
PermissionSaved.update({ id, action?, resource? })
  row = find id                        → NotFoundError  (404 PermissionSavedNotFoundError)
  next = row with action/resource replaced
  if another row in the project has the same action + resource
                                       → ConflictError  (409 ConflictError)
  write next, return it

The edit applies to the next permission check, like any saved permission. Requests already waiting are not re-checked, the same as remove.

  • packages/core/src/permission/saved.ts: update, plus NotFoundError and ConflictError
  • packages/protocol: the endpoint and a new PermissionSavedNotFoundError (the existing PermissionNotFoundError is for pending requests)
  • packages/server/src/handlers/permission.ts: maps the core errors to 404 and 409
  • packages/client: regenerated

Out of scope

Verification

  • packages/core/test/permission.test.ts: editing narrows what a saved permission allows (the edited resource is allowed, the old pattern prompts again), and conflict and not-found errors. bun run script/test.ts test/permission.test.ts --rerun-each 5: 530 pass.
  • packages/server/test/permission-saved.test.ts: creates two saved permissions through the real request and "always" reply flow, then checks PATCH returns the updated entry, the list reflects it, a duplicate returns 409, and an unknown ID returns 404. bun test test/permission-saved.test.ts --rerun-each 10: 10 pass.
  • bun run check: 34 of 34 tasks pass.

Added by the opencode bot (AI-generated).

Related: #50600

Public API: adds PATCH /api/permission/saved/:id (permission.saved.update) and client saved.update (backwards compatible)

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks sound. I ran the new server test, which goes through the real request handler and the real "always" reply flow (PATCH returns the updated entry, the list shows the edit, a duplicate gets 409, an unknown ID gets 404), and the core permission tests (106 pass). Type checks are clean in core, protocol, server and client.

Public API: adds PATCH /api/permission/saved/:id (permission.saved.update, payload { action?, resource? } → { data: PermissionSaved.Info }, errors 404 PermissionSavedNotFoundError / 409 ConflictError). Also adds the PermissionSavedNotFoundError error schema, saved.update in the generated effect and promise clients, and PermissionSaved.update, UpdateInput, NotFoundError and ConflictError in core. Additive only, so it is backwards compatible.

One small concern, inline: the duplicate check isn't atomic with the write.

Related: #50600

.get()
.pipe(Effect.orDie)
if (duplicate) return yield* new ConflictError({ id: duplicate.id, action, resource })
const updated = yield* db

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The duplicate check and the update aren't atomic. If two edits run at once, or an "always" reply adds the same rule in between, this update hits permission_project_action_resource_idx, and Effect.orDie turns that into a 500 instead of the 409. Wrapping the select and the update in a transaction, or mapping the unique-constraint failure to ConflictError, would keep the response consistent.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant