11.2 Controls and the evidence ledger
packs:security-governance
After this you can: attest a control, verify the evidence chain, export it, read the posture report, and place a legal hold.
Before you start — tick each one:
- Lesson 11.1 — Classification and the register (
opschain learn 11.1)
In the GUI: Governance → Evidence & controls — the control catalog and the hash-chained evidence ledger (list, verify, attest), with the waivers register beside them; the posture report is under Governance → Compliance.
A control is a named, owned, reviewable statement about
enforcement — "production changes require CAB approval", "licence keys
are custodied offline" — not an enforcement instrument itself. Controls
wrap the mechanisms you already run (an authorisation policy, an
approval policy, a notification mandate) and give your auditor a single
catalog to walk. Every review of a control lands as an attestation
in a hash-chained, append-only evidence ledger. The model reference
is opschain docs governance; the RPC surface is GovernanceService
on opschain docs api-v2.
Licence entitlement. The control catalog and ledger recording are base platform. Reading the ledger as the auditor, verifying the chain, writing attestations and administering legal holds require the
packs:security-governanceentitlement (seeopschain docs entitlements).Who can do this. Catalog curation and legal hold are admin-plane acts. The reviewer/auditor verbs are user-plane: path-RBAC grants on
/governance/controls(readable: catalog reads) and/governance/evidence(readable: ledger read/verify; updatable: attestation writes) — admin status never implies them. Attestation writes and legal-hold changes sit on the step-up floor (opschain learn 7.4).
1. Curate the catalog
As an admin, state a control and point it at the instrument that implements it — the approval policy from lesson 10.1:
opschain admin governance controls create \
--key prod-change-approval --name "Production change approval" \
--domain change --owner-role "Change manager" \
--description "Production changes require CAB approval before start" \
--instrument approval_policy:<policy-id>
created control prod-change-approval (01a2f0e0-5d2c-7a18-9b4e-6f7a8b9c0d1e)
opschain governance controls list
KEY NAME DOMAIN STATUS OWNER ROLE INSTRUMENTS
prod-change-approval Production change approval change active Change manager 1
opschain governance controls show prod-change-approval
key: prod-change-approval
name: Production change approval
domain: change
status: active
owner role: Change manager
description: Production changes require CAB approval before start
instruments: approval_policy:01a2f4c0-7d1e-7b3a-9c2e-5f0a1b2c3d4e
instrument_refsare{kind, id}citations: the instrument's own schema is untouched, and the authz decide path never consults the catalog.- A control with zero instrument refs is legal — a procedural control attested by humans:
opschain admin governance controls create \
--key licence-key-custody --name "Licence key custody" \
--domain security --owner-role "Security administrator" \
--description "Licence keys are custodied offline"
created control licence-key-custody (01a2f0e1-7e3d-7b29-ac5f-7a8b9c0d1e2f)
keyanddomain(security|change|agent) are immutable; retiring is a status update (opschain admin governance controls update licence-key-custody --status retiredanswersupdated control licence-key-custody (status retired)) — retired controls stay in the catalog because attestations cite them.
In the GUI the same curation lives on Governance → Evidence & controls → Controls: New control, and each row's Edit (name, description, owner role, status, the instrument refs) and Retire, shown to administrators; the key opens the control's detail page — its fields, the implementing instruments and the review campaigns that cite it.
2. Attest
An attestation is the dated statement that a named control was
reviewed: attestor, verdict
(verified | verified_with_exceptions | failed), notes, and
evidence_refs — typed citations, never copies: an artifact by
sha256: digest, an audit-event id, a campaign id, an export manifest
hash, a waiver id. An audit_event citation is validated when the
attestation is appended — the id must name a stored event (find it with
opschain events, inspect it with opschain events show <id>; the GUI's
attestation dialog offers an event picker), and an unknown id is refused
by name. As a reviewer holding updatable on
/governance/evidence (elevated):
opschain governance evidence attest --control prod-change-approval \
--verdict verified \
--evidence audit_event:<event-id> \
--notes "Q4 review: funnel held and released CHG-1042 as designed"
attestation appended — seq 41 (sha256:b81d…4c2a)
3. Read and verify the chain
The ledger is a separate append-only store where every row carries
prev_hash/row_hash — a SHA-256 chain from genesis to head, with a
database guard trigger refusing any rewrite of hashed content:
opschain governance evidence list --limit 20
SEQ KIND CREATED BY CREATED AT HOLD ROW HASH
41 attestation thomas 2026-10-27T10:02:15Z - sha256:b81d…4c2a
40 attestation thomas 2026-10-20T09:48:03Z - sha256:7e0c…a913
39 campaign thomas 2026-10-01T02:00:11Z - sha256:51f2…0d7b
opschain governance evidence verify
ledger valid — 41 row(s) verified, head seq 41 (sha256:b81d…4c2a)
verify recomputes the whole chain, segment by segment, and names the
first unacknowledged broken row — a clean run is itself evidence you
can cite in the next attestation. Once the retention sweep has archived
aged rows into sealed segments (opschain docs retention,
retention.evidence_ledger_days), the verdict also reports the archived
boundary and segment count — the chain verifies across the archives.
If a database incident sustains real data loss and the chain can no longer verify, the governed recovery is a re-anchor — never a repair:
opschain governance evidence re-anchor \
--justification "database incident - data loss sustained, backup restore not possible"
chain re-anchored — discontinuity recorded at seq 42 (sha256:0a4e…c7d1); the break stays on the record and new entries verify from here
From then on every verify lists the break as an acknowledged
discontinuity:
rows 1-41 valid
ACKNOWLEDGED DISCONTINUITY at 42 (re-anchored 2026-10-27T03:14:07Z by admin: database incident - data loss sustained, backup restore not possible)
rows 43-57 valid
ledger valid — 57 row(s) scanned, 1 acknowledged discontinuity(ies), head seq 57 (sha256:…)
A healthy chain refuses the verb, it sits on the step-up floor, and the act is audited loudly. Restore from a database backup first whenever you can (lesson 8.1).
4. Export for your auditor
Hand the ledger over without handing over your server: each export batch is the canonical NDJSON encoding — every row carries its stored hashes verbatim — and batches chain, so the accumulated export re-verifies offline:
opschain governance evidence export --out batch-1.ndjson
opschain governance evidence export --after-seq <last seq of batch 1> --out batch-2.ndjson
The CLI re-computes each batch's SHA-256 and refuses on a mismatch with
the server's digest; record the printed digests alongside the files. The
GUI's Export on the Evidence ledger tab does the same batch by batch:
the cursor and batch size, the digest checked in the browser against the
server's before the .ndjson download, and a mismatch flagged rather than
handed over.
5. The posture report
One page for the regulator's question — findings over the catalog, campaigns and waivers, per-domain coverage, the monitoring metric set and the chain verdict, opening with its honest posture statement (a tool-assisted attestation of recorded posture, never a certification):
opschain governance posture
opschain-governance-posture/v1 — generated 2026-10-27T10:05:00Z
Tool-assisted attestation of recorded governance posture: the control catalog, the evidence ledger's hot window and the exceptions register as stored at generation time, mapped by governance domain. This report is evidence of practice, never a certification.
DOMAIN CONTROLS ACTIVE VERIFIED
change 1 1 1
security 1 1 0
SEVERITY KIND CONTROL DOMAIN DETAIL
medium control_never_attested licence-key-custody security no attestation in the evidence ledger's hot window
METRIC VALUE
controls_never_attested 1
…
ledger valid — 41 row(s), 0 acknowledged discontinuity(ies), archived through seq 0 across 0 segment(s)
opschain governance posture -o json is the same report as a document.
The command exits non-zero when the chain reports an unacknowledged
break, so a scheduled posture run fails loudly on tamper or data loss.
6. Legal hold
A flagged ledger row is exempt from every purge/retention path until explicitly unflagged. Flagging and unflagging are audited admin acts on the step-up floor, stored outside the hashed content — holds never break the chain:
opschain admin governance evidence hold <ledger-row-id>
ledger row seq 41 is under legal hold
opschain admin governance evidence unhold <ledger-row-id>
opschain audit --type api:evidence_ledger:legal_hold
7. The audit trail
Catalog and ledger writes are events like everything else:
opschain audit --type-prefix api:governance_controls:
opschain audit --type api:attestations:create
The full event families are on opschain docs events.
Check yourself
- What does
evidence verifyrecompute along the chain, and what does its last line print on a healthy ledger? Then: which rows can nobody purge while a legal hold stands, and does the hold change any row hash?
What you learned
- Controls wrap instruments by citation (or none at all, for procedural controls); the catalog is the compliance surface, never the enforcement path.
- Attestations cite typed evidence and land in a hash-chained,
append-only ledger;
evidence verifyproves the chain end to end. - Recording is base; auditor reads, verification, the export lane, the
posture report, attestation writes and legal hold are
packs:security-governancesurfaces, with the writes on the step-up floor. - The ledger's retention is sealed-segment archival, never a bare purge: aged rows leave only after a verified blob-store export, and the chain verifies across the archives.
Next: lesson 11.3 — Review campaigns and remediation work items (opschain learn 11.3), or back to your track (opschain learn).