Skip to main content
Version: v5.6.1

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-governance entitlement (see opschain 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_refs are {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)
  • key and domain (security|change|agent) are immutable; retiring is a status update (opschain admin governance controls update licence-key-custody --status retired answers updated control licence-key-custody (status retired)) — retired controls stay in the catalog because attestations cite them.

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.

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.

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 verify recompute 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 verify proves 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-governance surfaces, 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).