Files
claude-code-conduit/claude-info/plan-policy-overrides.md
mikael-lovqvists-claude-agent dd7743501a Add runtime policy overrides and TUI policy management view
- server/policy_overrides.mjs: persistent group/action override store
- Group toggle: disabling a group forces all its actions to auto-deny without
  touching individual overrides; re-enabling restores them intact
- Action overrides: cycle auto-accept/queue/auto-deny/clear per action
- Effective policy resolution in index.mjs: group > action override > hardcoded default
- auth.mjs: add check_can_manage_policies (requires canApprove.length > 0)
- Three new endpoints: GET /policies, POST /policies/group/:group, POST /policies/action/:action
- client/conduit.mjs: get_policies, set_group_policy, set_action_policy
- ccc-queue.mjs: policy view (Tab to switch); group/action panel with space/e/←/→
- actions.mjs: group fields on desktop/email/calendar actions; list-actions includes group
- config.example.json: add policy_overrides key
- Bump version to 1.3.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 16:01:45 +00:00

4.3 KiB

Plan: Runtime policy overrides & TUI policy management

Status: implemented

Permission model

Principle: to mutate permissions, you must have authority over the affected party.

This mirrors set-mail-permission, which requires canApprove over the target user. Policy overrides affect all agent users, so the caller must have canApprove over at least one other user — making them effectively a trusted human.

Implemented as check_can_manage_policies(users, caller) in server/auth.mjs:

return (users[caller]?.canApprove?.length ?? 0) > 0;

Why this matters as a precedent: any future permission-mutating operation in CCC (or derived systems) should require the same gate. Agents start with canApprove: [] by design, so they can never grant themselves new access. Only humans can change what agents are allowed to do.

The non-negotiable boundary (from issue #3): canApprove itself is never runtime-writable. It is loaded from secrets.json at startup and is the root of the entire trust tree.


Data model

Two independent stores, never entangled:

{
  "groups": { "calendar": "disabled" },
  "actions": { "calendar-list-events": "queue" }
}

Effective policy resolution (in order of precedence):

  1. Action's group is "disabled"auto-deny
  2. Action has an individual override → use it
  3. Fall back to hardcoded policy in actions.mjs

Key invariant: group toggling never touches action overrides. Disabling a group just sets the group flag. Re-enabling it removes the flag — individual overrides snap back intact.


Group membership

Actions in actions.mjs carry an optional group field:

Group Actions
calendar all four calendar actions
email send-email only (permission management actions are not gated by this)
desktop edit-file, open-browser, open-terminal

list-actions, set-mail-permission, get-mail-permissions, and meta-actions have no group.


New files

  • server/policy_overrides.mjs — load/save store, effective(), set_group(), set_action(), get_all()

Changed files

File Change
server/auth.mjs Add check_can_manage_policies
server/config.mjs Add policy_overrides_path
server/index.mjs Wire up store; use effective policy in action dispatch; add /policies endpoints
server/actions.mjs Add group field; update list-actions to include group
client/conduit.mjs Add get_policies, set_group_policy, set_action_policy
bin/ccc-queue.mjs Policy view: group toggles + action policy cycling
config.example.json Add policy_overrides key

API endpoints (human-only)

Method Path Auth required Description
GET /policies any authenticated Returns full policy state
POST /policies/group/:group canApprove.length > 0 Toggle group disabled state
POST /policies/action/:action canApprove.length > 0 Set or clear action override

TUI: policy view

[Tab] switches between queue view and policies view.

┌─ Groups ──────────────────────┐ ┌─ Actions ──────────────────────────────────┐
│                               │ │                                            │
│ > [●] calendar                │ │  auto-accept  calendar-list-events         │
│   [●] email                   │ │  queue        calendar-create-event        │
│   [●] desktop                 │ │  queue        calendar-update-event        │
│                               │ │  queue        calendar-delete-event        │
└───────────────────────────────┘ └────────────────────────────────────────────┘
  [space] toggle group  [→] edit actions  [tab] queue view  [q] quit

When a group is disabled, actions render greyed with auto-deny. [e] on the right panel cycles an action's override: auto-accept → queue → auto-deny → (clear/default) → ….

Action overrides display a * marker when a non-default policy is set.