Approval gates
Legal and Finance are the two roles with a hard rule: never act without a human saying yes, on a physical device, in the moment.
Why a gate is just a typed prompt
A gates.toml file is a policy, not code:
# cwc-finance/gates.toml
[gate.spend]
match = { tool = "*", action = "purchase|subscribe|deploy_paid" }
require = "operator"
surface = "buddy"
hint = "{{vendor}} ${{amount}}"
When a gated action is attempted, the coworker runtime emits a WirePermissionPrompt tagged with the role asking. Nothing about the gate is hard-coded per role — the same mechanism serves a legal send-for-review, a finance purchase, or an engineering deploy that happens to touch production.
The buddy: a physical approve/deny
The prompt surfaces on coworkers-desktop-buddy — a macOS app paired over BLE to a small hardware device. The operator presses a physical button; the decision round-trips back through the same permission-<id>.json file the desktop app's own Approve/Deny buttons write. A pending gate carries its role, so the device shows who is asking — [legal] awaiting approval: NDA v3 — not just that something is waiting.
Two-phase publish (the proof-of-loop)
Design's publish step is the pattern in miniature: artifact_request_publish asks, the operator decides on the buddy, artifact_finalize_publish only runs on approval. It's additive to the wire protocol — an optional role field both the Rust and Swift sides already tolerate via default/decodeIfPresent — so adding a gate to a new role never breaks an old one.
Related
- Role taxonomy — which roles are gated and why
- How it works
27667d8e91ec2293 · verify