Files
claude-code-conduit/claude-info/architecture.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

131 lines
4.6 KiB
Markdown

# Architecture
## File layout
```
server/
index.mjs — Express app, routing, policy enforcement
actions.mjs — Action registry (add new actions here)
auth.mjs — HMAC-SHA256 middleware, canApprove/policy checks
config.mjs — Loads config.json + secrets.json
helpers.mjs — exec(), path resolution, volume mapping
queue.mjs — In-memory queue for policy=queue actions
mail_perms.mjs — Persistent mail permission store (JSON file)
mailer.mjs — Nodemailer wrapper
policy_overrides.mjs — Runtime policy override store (JSON file)
google_calendar.mjs — Google Calendar API wrapper (googleapis)
bin/
ccc-server.mjs — Server entry point
ccc-client.mjs — CLI client (used by agent)
ccc-queue.mjs — TUI queue manager (used by human on host)
ccc-keygen.mjs — Secret generation / filtering utility
```
## Request lifecycle
1. Client signs request with HMAC-SHA256 (includes timestamp; replays rejected after 30s)
2. `auth.mjs` middleware validates signature, sets `req.conduit_user`
3. `POST /action` looks up the action definition in `actions.mjs`
4. Validates required params
5. Applies policy:
- `auto-accept` → runs handler immediately, returns result
- `queue` → enqueues, returns `{ status: 'queued', id }`; human approves/denies via TUI
- `auto-deny` → rejects immediately
## Action handler context (`ctx`)
```js
{
caller, // string — authenticated username
users, // object — full users map from secrets.json
mail_perm_store, // MailPermStore instance
exec, // (bin, args) => void — runs a host process (no-op in --dry-run)
mailer_send, // async (to, subject, body) => void
calendar, // Google_Calendar_Client | null — null if not configured
}
// Effective policy resolution (index.mjs, evaluated per request):
// 1. action's group is disabled → auto-deny
// 2. action has individual override → use it
// 3. fall back to hardcoded policy in actions.mjs
```
New modules are instantiated in `index.mjs` and added to `make_ctx()`.
## Config file (`config.json`)
```json
{
"secrets": "../secrets.json", // required — path to users/secrets
"mail_perms": "mail-perms.json", // optional — persists mail permissions
"smtp": { ... }, // optional — enables send-email action
"bind": "127.0.0.1",
"port": 3015
}
```
All relative paths are resolved from the config file's directory.
## Secrets file (`secrets.json`)
```json
{
"users": {
"agent": { "secret": "<hmac-key>", "canApprove": [] },
"user": { "secret": "<hmac-key>", "canApprove": ["agent"] }
}
}
```
`canApprove` lists whose queued actions a user may approve/deny.
## Path resolution
Container paths are translated to host paths via `VOLUME_MAPPING` in `server/helpers.mjs`. Paths outside known volumes are rejected. Default mapping:
| Container | Host |
|-----------|------|
| `/workspace` | `<CONTAINER_PATH>/workspace` |
| `/home/claude` | `<CONTAINER_PATH>/claude-home` |
Edit `CONTAINER_PATH` and `VOLUME_MAPPING` in `helpers.mjs` to match the local setup.
## Adding a new action
```js
// server/actions.mjs
'my-action': {
description: 'What it does',
params: [
{ name: 'foo', required: true, type: 'string' },
{ name: 'bar', required: false, type: 'path' },
],
policy: 'auto-accept', // or 'queue' | 'auto-deny'
group: 'my-group', // optional — makes it togglable in the TUI policy view
handler: ({ foo, bar }, ctx) => {
// ctx is optional — omit second arg if unused
return { result: foo };
},
},
```
## Permission model
- `canApprove` in `secrets.json` — static root of trust, never runtime-writable
- Queue approve/deny — requires `canApprove` over the submitting user
- Mail permissions — requires `canApprove` over the target user
- **Policy overrides** — requires `canApprove.length > 0` (i.e. any user trusted to approve others)
This precedent means: **to mutate permissions, you must have authority over the affected party.** Agents start with `canApprove: []` so they can never elevate their own access.
## Adding a new injectable service (like mailer or calendar)
1. Create `server/my_service.mjs` exporting a factory function
2. Extend `load_config` in `server/config.mjs` to read and return the new config block
3. Instantiate the service in `index.mjs` using the config values
4. Add it to the `make_ctx()` return object
5. Use it in action handlers via `ctx.my_service`
See [`contributing.md`](contributing.md) for the full checklist when adding any feature.