7 min read
Exception Ledger
The exception ledger is how StateAnchor handles intentional breaking changes. When an ERR verdict is correct but the change is planned -- a versioned API migration, a controlled deprecation, a coordinated consumer update -- you file an exception. The exception is scoped, time-bounded, and requires approval from a second codeowner.
Exceptions do not lower the gate. They add a sanctioned bypass for a specific violation on a specific endpoint. Every other ERR still blocks.
What is the exception ledger?
Every filed exception is a structured record: the change kind, the endpoint, the reason, the requester, and an expiry time. The ledger is append-only. Exceptions cannot be edited -- only approved, revoked, or allowed to expire.
When the gate encounters an ERR that has a matching active exception, it allows the push to proceed and records the exception ID in the sync run audit trail. The exception is visible in the project cockpit, the compliance export, and the share token view.
This means every bypass is auditable. You can reconstruct exactly which exceptions were active during which sync runs, who approved them, and when they expired.
Two-signal approval
A freshly filed exception starts in the anergic state. It does not activate until a second independent signal arrives from a second codeowner. This is the anergic two-signal model (ADR-006).
The second signal can be one of:
- A second codeowner approving the exception in the StateAnchor dashboard
- A consumer test added to the PR that explicitly validates the breaking change
- A code-owner acknowledgment signal from the GitHub review
Why require a second approver? Two reasons:
- Audit trails for SOC 2. A single engineer can't self-authorize a bypass to a breaking-change gate without a second signal. This is the same principle as four-eyes financial controls.
- Coordination evidence. The second signal proves that at least two people with codeowner access agreed that the breaking change was intentional. For a compliance export, that's the difference between "someone bypassed the gate" and "someone bypassed the gate with explicit co-authorization."
See Handling a gate block for the full exception creation flow and bot deny-list details.
Exception lifecycle
Every exception moves through a defined state machine:
| State | Meaning | Gate behavior |
|---|---|---|
| ANERGIC | Filed, waiting for the second signal | Does not activate -- gate still blocks the matched ERR |
| ACTIVE | Second signal received, within expiry window | Gate allows the matched ERR to proceed; records exception ID in run |
| EXPIRED | Active exception passed its expiry timestamp | Gate blocks the matched ERR again -- scope is over |
| REVOKED | Manually revoked before expiry | Gate blocks the matched ERR immediately |
Exceptions are scoped to a specific change kind + endpoint. An exception for field_removed on GET /users/{id} does not cover field_removed on GET /orders/{id}.
CODEOWNER latency tracking
StateAnchor tracks how long each exception spends in the anergic state before receiving the second signal. This latency is surfaced in the compliance export as P95 approval latency.
For organizations using StateAnchor as a SOC 2 control, this metric answers: "How quickly does our team review and approve gate bypasses?" A high P95 latency indicates that exceptions sit unresolved for long periods -- a sign that the review process needs attention, not that the exceptions are invalid.
CODEOWNER latency is also surfaced in the project cockpit's protection summary stats and in the weekly digest email when enabled.
SOC 2 evidence
The exception ledger maps directly to SOC 2 Common Criteria CC8.1:
The entity authorizes, designs, develops or acquires, implements, operates, approves, maintains, and monitors environmental protections, software, data backup processes, and recovery infrastructure to meet its objectives.
Specifically, the exception ledger provides:
- Authorization evidence -- every exception is filed by a named user with a codeowner role and approved by a second named user.
- Scope and time-bounding -- each exception has a specific change kind, endpoint, and expiry. Indefinite bypasses are not possible.
- Revocation trail -- exceptions revoked before expiry are recorded with a reason and a timestamp.
- Linkage to sync runs -- each sync run that proceeded via an exception records the exception ID, creating a chain of custody from the policy bypass to the artifact it allowed.
Export the exception ledger as part of a compliance bundle from project settings → Compliance export (Team+ plan). See Compliance for the full CC8.1 mapping.
Example: stateanchor.yaml exception definition
Exceptions are filed through the dashboard, not the YAML. The YAML defines what your API should look like (desired state); exceptions are operational records of approved deviations. However, you can reference the kind names from stateanchor.yaml in exception requests:
# Exception filed via dashboard -- not YAML config
# This shows the fields used in the exception record:
kind: field_removed
endpoint: "DELETE /accounts/{accountId}"
reason: |
Removing the 'legacyBillingId' field as part of the v3 migration.
All consumers confirmed migrated by 2026-05-01.
expires_at: "2026-05-07T00:00:00Z"
requested_by: "user_abc123"
# Second signal required before this activates
# approved_by: (pending second codeowner approval)Related: Handling a gate block -- Compliance export (SOC 2)