Zones
A zone is the unit of protection: path patterns + owner + risk level + default policy. This is CC’s own zone map, trimmed — the real file the product’s development runs under, not an idealised example:
# .zones/zones.yml — committed, reviewed like codeversion: 1
zones: - id: schema name: Database schema risk: high owner: paulo@commitcycle.com description: > D1 migrations and table structure. The only thing a git revert does not undo. paths: - "apps/api/migrations/**" - "apps/api/src/db/schema/**" default_policy: open
- id: enforcement name: Enforcement layers risk: high owner: paulo@commitcycle.com description: > The hook and the GitHub Action. A silent bug here means the gate is decorative while still reporting success. paths: - "packages/hook/**" - "packages/action/**" default_policy: open
# Everything not listed here is unprotected, on purpose.Both zones read open since 2026-10-10, when every zone in this map moved from deny or read-only to open. That opened them for reading and nothing else: writing into either still takes a grant that opens it, and a high risk zone still takes its owner’s approval first.
The design bets
Section titled “The design bets”Undeclared paths are unprotected. Most of a repo is ordinary code where a mistaken agent edit costs one git revert. Reserving zones for the places where that isn’t true — migrations, contracts, the enforcement code itself — keeps the false-positive rate near zero, and the false-positive rate is what decides whether a gate survives contact with a real team.
The policy is about reading; writing always takes a grant. Every zone is closed for writing until a grant opens it, whatever its policy. default_policy decides only whether the zone can be read without asking: deny closes reading too, for what should not even be seen, while read-only and open both leave it readable (packages/contracts/src/matcher.ts). A zone marked secrets: true is closed for reading whatever its policy says. Paths whose risk is “review it before it merges” need no zone at all: the closing gate sees every changed file anyway.
The file is the interface. No dashboard toggle, no per-session exceptions. Changing a boundary is a diff with an author and a reviewer, which is precisely the audit trail a boundary change deserves. The board holds the map in one direction only: a machine running cycle sync can push a map that keeps or tightens what the board enforces, but a map that drops a zone, uncovers a path, loosens a policy or a risk level, turns secrets off, changes an owner, or adds a path that could claim another zone’s files under weaker protection is refused unless an owner of the organization pushes it, signed in (CC-1252, CC-1265, CC-1301, CC-1323).
Which tool surfaces the hook models
Section titled “Which tool surfaces the hook models”A zone is only as good as the set of tools it is applied to, so this is worth stating plainly.
The file and shell tools are modelled by name. Edit, Write, MultiEdit, NotebookEdit and Read are judged on the path they declare, and a Grep into a zone closed for reading is refused like a Read of it (CC-1260); Bash is contained rather than classified — commands run against a git snapshot and changes to protected paths the task does not hold are reverted, byte for byte. Gitignored paths are not seen at all: the snapshot cannot see them. A command started with run_in_background is contained only until it launches, so on the trunk it is put to the person first while a zone is closed for writing. A foreground command that Claude Code moves to the background at its timeout, and a cmd & inside a command, get no prompt and escape containment until the close checks the diff (CC-1379). And a repository with any zone marked secrets: true refuses every Bash command that is not read-only inspection, listed in the zone map’s extra_commands, or the push pr.open permits, because containment cannot see the gitignored files secrets usually live in.
Outside the repository, the file tools are refused — with one closed list of exceptions on the trunk: the editor’s own folders, such as Claude Code’s session scratchpad, the project’s memory folder, todos and plans (CC-1348). The refusal says what it is: a claim about the tools the hook judges, not a sandbox, because a program started from Bash can still write where the hook cannot see, and that gap is not a way through (D-65, CC-456). Both are newer than the published 0.1.25.
Everything else — every MCP server, every browser tool, the harness’s own control plane — is judged on the evidence the call carries about itself. There is no list of servers, because a list of servers goes stale the week after it is written. There are two signals:
- A path under a path-shaped key.
{"target": "apps/api/migrations/003.sql"}is judged exactly as aWriteto that file would be — same zone map, same grant.target,dest,destination,output_path,filenameand their siblings all count;prompt,title,contentandurlnever do. - A database statement under a statement-shaped key.
{"query": "drop table orders"}is refused while the schema zone is closed, because an MCPapply_migrationis neither Bash nor a declared path, and a dropped table is the one thing a revert cannot restore.
The statement has to be anchored — the value starts with the verb, or the verb follows a ; — and it has to read as a statement rather than a sentence. {"query": "how do I drop a table in D1?"} is a documentation search and passes. So does {"prompt": "fix the classifier in packages/hook/src/tools.ts"}: prose under a prose key is prose, whatever paths and verbs are in it. That boundary is the point — a gate whose false-positive rate is not near zero does not survive contact with a real team.
A call that carries no evidence is still allowed, and that is deliberate: reading documentation, driving a preview and filing a board record are legitimate, and most of what an agent does through these surfaces is exactly that. What changed is that it is no longer silent. Every unmodelled call is recorded as unwatched with the surface it came from, and one that named a path leaves a mutation event carrying that path — so the closing gate can see that something outside the matrix ran, instead of reporting a clean picture of a task whose real work happened somewhere it never looked.
cycle doctor prints a tool surfaces row saying which surfaces this repository has actually seen and how many of its recorded events came from calls the matrix does not model. It reports a warning while any of those surfaces is bound to nothing — an allow-by-default that nobody has accounted for is a warning, not health.
What a zone is not
Section titled “What a zone is not”Not a sandbox (the agent still runs on your machine), not a network policy, and not secrets management — gitignored paths inside a zone are a known open question from Phase 0: .env files are both gitignored and the archetypal secret, so secrets zones need a different mechanism than the git-snapshot containment everything else gets.