Skip to content

The audit record

Every task that reaches Done leaves one file in .zones/audit/. It is written by the machine at close, not composed afterwards by whoever remembers the week, and it is the artifact you hand a skeptic.

The unit is the task, and that is the whole distinction. Durable provenance for agent-written code already exists and is good — there are tools that record, per line, which model wrote it and from what prompt, surviving rebase and squash. That answers who typed this character. This record answers a governance question instead: for one unit of work, what did it declare it would touch, what did it actually touch, who approved the difference, and what was overridden and why. Same repository, different question — and a per-line log cannot answer this one no matter how complete it is.

The frontmatter is the machine-readable half:

task: CC-39
owner: paulo@commitcycle.com
branch: task/CC-39-approve-and-close-is-offered-by-the-cons
quality_gate: regression suite
zones_declared: []
zones_touched: []
declaration_matches: true
exceptions: 2
blocks: 0
mutations_observed: 0
evidence:
- type: capture
location: .zones/evidence/CC-39/verification.md
diff:
files: 13
base: HEAD~1
head: d8a07c8a
closed: 2026-08-04
closed_by: agent

zones_declared against zones_touched is the line that matters: it is the difference between what the task said it would do and what the diff shows it did. declaration_matches is that comparison, computed.

A fourth line appears only when it has something to say. If the work hit a wall mid-task, asked for a zone and an owner approved it, that approval amends the declaration — and the record splits the two, so you can tell a task that scoped a risky area up front from one that was let into it by a person:

zones_declared: [web:write]
zones_amended: [schema:write]
zones_touched: [web, schema]
declaration_matches: true

declaration_matches is measured against both together, because both are what the closing gate was checked against. The approval itself — who answered, when, for how long, and what the agent offered to do instead — is in the Access asked for table further down.

The body carries what was asked, what it was explicitly not allowed to do, how anyone would know it worked, the logged exceptions, and the file-by-file diff.

An exception is not a failure. It is something the close noticed and refused to swallow silently — recorded with an owner and a date, so it is a question someone can answer later rather than a detail nobody was told:

2026-08-04 — at close, 12 changed file(s) are in no zone: .zones/tasks/CC-39.md, AGENTS.md, apps/dashboard/package.json and 9 more. Should any be protected? cycle protect <glob> --zone <id> if yes, cycle dismiss <glob> to stop being asked.

Most repos have a long tail of paths nobody has decided about. Asking once, at close, when the evidence is in front of you, is cheaper than asking at declaration time when it is hypothetical.

The most useful entry any of these records carries is the one about what could not be seen:

2026-08-04 — at close, Nothing observed this work: 12 changed file(s) and no mutation events at all. That is what a hand-edited task looks like — but it is also what a task run with the hook switched off looks like, and from here they are the same.

CC cannot tell those two apart from the record alone. A task edited by a human at their keyboard and a task run with Layer 2 uninstalled produce the same silence.

This is the same finding that makes Layer 3 necessary — a hook is fail-open unless something outside the laptop checks — and it is why closes have been refused on this repository twice rather than overridden. A record that quietly rounded that silence up to “verified” would be worth less than no record.