Example: Changes Outside the Controlled Path¶
Up to 1.2 the evidence store held decisions and the execution evidence controllers submitted under them. A secret changed any other way left no record: someone rotated it in the cloud console, or a controller rotated it while it could not reach the Network Authority (NA). An audit of the secret then showed a history that was not the secret’s history.
1.3.0 brings those changes into the store. An observer that reads the
cloud provider’s activity log signs what it sees (ObservationRecord); a
controller that acts while the NA is unreachable signs a break-glass
record with the justification its caller gave (BreakGlassRecord). The NA
judges each change once, as of the time it happened, under the policies
that were active then, and keeps a record it refused after the fact,
quarantined, rather than losing it. The key design decision is that a
judgement is its own kind of record, with no authorized field: it says what
the NA concluded about a change that already happened, and nothing can use it
as permission for the next one.
What the records prove¶
An observation proves what its observer saw, signed by an observer key scoped to the resources it watches. The actor is as the source reported it, not authenticated.
A judgement proves which policy versions the NA applied, as of when, and with what result; whether today’s policies would agree; and, for a governed change seen by an observer, which execution record it matched.
Every record, and the registry of policy activations and keys the judgements rest on, is in the store’s hash chain and under its anchors.
See Changes Outside the Controlled Path for the settings, the state table and the rules in full.
The recording is a real run against a temporary local NA: a governed
rotation matched to its observation, a console rotation judged and denied, a
break-glass record judged on arrival, then the denied rotation remediated.
Run it yourself (rendering the GIF needs pip install pillow; --no-gif
writes only the transcript):
python docs/examples/assets/scripts/out-of-band-changes-demo.py
Walkthrough: one secret, three kinds of change¶
These records are off by default. The NA runs with EVIDENCE_STORE=on and
EVIDENCE_OUT_OF_BAND=on; otherwise the observation, break-glass and changes
routes answer 404 out_of_band_disabled. Steps 6 to 9 also need
EVIDENCE_REMEDIATION=on.
1. Register an observer¶
A privileged operator registers the observer’s key with the observer role,
scoped to the resources it watches:
POST /admin/evidence/executor-keys
{ "key_id": "activity-log-observer",
"public_key": "<base64 Ed25519>",
"executor_sovereign_id": "cloud-observer",
"role": "observer",
"resource_prefix": "kv:vendor-acme/" }
The registration is recorded in the store as a registry entry.
2. A governed rotation, seen by the observer¶
The controller rotates the secret under a decision, as in Example: Evidence Store in the Network Authority, and names the version it produced:
evidence = record_execution(
decision, "secrets-controller", "secret.manage", "success", controller_key,
issued_by="ctrl-1",
execution_parameters={"version_id": "v7"},
resource_id="kv:vendor-acme/api-key", resource_action="rotate",
prior_resource_record=previous,
)
# POST /evidence/execution {"evidence": evidence}
A minute later the observer reads the activity log entry for the same rotation and signs it:
from genesis_mesh.crypto import sign_model
from genesis_mesh.models.out_of_band import ObservationRecord
seen = ObservationRecord(
observer_sovereign_id="cloud-observer",
resource_id="kv:vendor-acme/api-key", action="rotate", capability="secret.manage",
changed_at=log_entry_time, observed_at=now,
actor="principal-7f3a", source="cloud-activity-log", source_event_id=log_entry_id,
version_id="v7", metadata={"operation": "SetSecret"},
)
seen = seen.model_copy(update={"signature": sign_model(seen, observer_key, "activity-log-observer")})
# POST /evidence/observations {"observation": seen.to_wire()}
The NA matches it to the controller’s record (same resource, action,
capability and version, made at the same time give or take the clock skew)
and records a judgement with governed_by: prior_decision. The match
consumes the execution record. A second observer’s report of the same v7
rotation is the same change: it is judged governed_by: prior_decision too,
naming the record in its reason, without consuming it again. A later change
that happens to report version v7 again is a different change, judged on
its own.
3. A rotation in the cloud console¶
Someone rotates the secret by hand. The observer sees it with version v8,
which no execution record names. The NA judges it after the fact, as of
changed_at, under the policies active then. With a policy capping
lifetime_days at 90 and the change reporting 400, the judgement is:
{ "subject_kind": "observation", "governed_by": "after_the_fact",
"verdict": "deny", "reason": "policy gate 'max-lifetime' failed",
"evaluated_as_of": "2026-11-02T09:14:00Z",
"current_verdict": "deny" }
Had the policy been changed since, current_verdict would differ and the
judgement would carry flagged_for_review: true.
4. A break-glass rotation¶
The key leaks while the NA is down. The controller’s caller passes a justification, and the TypeScript SDK runs the action and keeps a signed break-glass record in its outbox. The Rust SDK and the edge agent work the same way; the Go and .NET SDKs have no break-glass or outbox:
const result = await governedAction(gm, recorder, {
attestation_id, requested_capability: 'secret.manage',
resource_id: 'kv:vendor-acme/api-key', resource_action: 'rotate',
breakGlass: { justification: 'Key leaked in a public repository; NA unreachable' },
verify: { operatorPublicKeys, expectedPolicies, expectedAttestation },
}, async () => rotate());
When the NA is back the outbox submits the record, and the NA judges it as the failed evaluation would have gone at that moment, with the attestation as it stood then. A DENY from the NA never leads to break-glass.
5. Read the resource’s changes¶
GET /admin/evidence/changes/kv:vendor-acme/api-key
{ "resource_id": "kv:vendor-acme/api-key", "truncated": false, "changes": [
{ "kind": "execution", "action": "rotate", "governed_by": "prior_decision", "state": "recorded" },
{ "kind": "observation", "action": "rotate", "governed_by": "prior_decision", "state": "matched" },
{ "kind": "observation", "action": "rotate", "governed_by": "after_the_fact", "state": "judged_denied" },
{ "kind": "break_glass", "action": "rotate", "governed_by": "after_the_fact", "state": "judged_allowed",
"justification": "Key leaked in a public repository; NA unreachable" } ] }
Walkthrough, continued: what a DENY leads to (1.4.0)¶
With EVIDENCE_REMEDIATION=on the denied console rotation of step 3 does not
stop at a judgement. The steps below follow it, then a review, a waiver and
an urgent change. Remediation and Reviews and
Emergency Changes and Break-Glass give the rules in full; the test
genesis_mesh/tests/test_remediation.py runs each step against a live NA.
6. Remediate without breaking the reader¶
The lifetime policy says what its gate’s DENY leads to:
"remediation": {"max-lifetime": {"mode": "cancel", "grace_seconds": 3600}}
So the judgement of the v8 rotation arms a remediation, shown with the
change and in the due queue:
GET /admin/evidence/remediations?state=remediation_due
{ "subject_kind": "judgement", "subject_id": "<judgement_id>",
"change_kind": "observation", "change_id": "<observation_id>",
"resource_id": "kv:vendor-acme/api-key", "actor": "principal-7f3a",
"state": "remediation_due", "mode": "cancel", "steps": "rotate_forward_then_disable",
"grace_seconds": 3600, "remediation_due_at": "2026-11-02T10:16:00Z" }
The controller tells the actor and records the notice before the due time
(POST /admin/evidence/remediations/<judgement_id>/notified). Then it
cancels the change in two steps. A billing service reads the secret’s
current version the whole time:
Step |
Current version |
Enabled |
The reader gets |
|---|---|---|---|
The console rotation |
|
|
|
The controller rotates forward under a governed decision, submits the execution evidence ( |
|
|
|
It disables the denied |
|
|
|
The reader never finds a disabled version: the denied version is disabled
only once a governed replacement is current. The observer sees the v9
rotation and the NA matches it to its evidence: governed, not judged again.
Last, the controller signs what it did:
from genesis_mesh.models.remediation import RemediationRecord
done = RemediationRecord(
executor_sovereign_id="secrets-controller",
subject_kind="judgement", subject_id=judgement_id,
change_kind="observation", change_id=observation_id,
resource_id="kv:vendor-acme/api-key", outcome="cancelled",
steps="rotate_forward_then_disable", rotation_evidence_id=rotation.evidence_id,
version_id="v9", disabled_version_id="v8", remediated_at=now,
)
done = done.model_copy(update={"signature": sign_model(done, controller_key, "ctrl-1")})
# POST /evidence/remediations {"record": done.to_wire()}
The two linked records, the rotation’s execution evidence and the
remediation record naming it, are in the store; the change’s state is
remediated.
7. Review a judgement¶
Had the console rotation been planned with the vendor, an operator asks for a review with a justification:
POST /admin/evidence/reviews
{ "subject_kind": "judgement", "subject_id": "<judgement_id>",
"justification": "Planned with the vendor; the ticket was filed late" }
The review holds nothing: the remediation stays due, and becomes overdue if
the grace passes first. A privileged key of another holder decides it,
neither the requester’s holder nor the holder the actor principal-7f3a
maps to:
POST /admin/evidence/reviews/<review_id>/decide
{ "verdict": "deny", "note": "Not in the ticket" }
A denial makes the remediation due again. The review record the NA signs
carries both operators’ signed requests, so the approval verifies offline,
years later, from the export alone.
8. Waive it until a time¶
Or the decider lets the change stand for a week, while the vendor moves to a governed rotation:
POST /admin/evidence/reviews/<review_id>/decide
{ "verdict": "waive_until", "waive_until": "2026-11-09T09:00:00Z" }
The state is waived. When the week ends the remediation is due again, with
its grace. A waiver always ends, and none is granted after a remediation is
recorded.
9. An urgent change under a DENY¶
The key leaks while the policy denies any 400-day lifetime. The controller asks for the emergency capability instead, with a justification:
POST /admin/boundary/evaluate
{ "attestation_id": "...", "requested_capability": "secret.manage.emergency",
"context": {"request_parameters": {"lifetime_days": 1,
"justification": "Key leaked in a public repository"}} }
The emergency policy allows it, the controller rotates, and the NA opens a
review of the decision. Had the NA been down, or answered 503 or 429, a
break-glass record naming secret.manage.emergency would have been judged
under the same policy when it arrived. An observer that merely calls a
change an emergency gets it judged under the ordinary policy, and flagged.
Verify offline¶
The export carries every new kind; genesis-mesh evidence verify-export
checks their signatures, that each judgement names the digest of the record
it judges, that no execution record was matched twice and that no execution
record rests on a judgement. Since 1.3.1 it also checks that each record
appears once, that every envelope names what its record says, and that a
judgement’s matched record is in the export before it (see
Evidence event schema (gm.evidence.event v1)). Since 1.4.0 it checks that each
change is remediated once, after its forward rotation, and that each review’s
embedded approvals verify, by a second holder.
genesis-mesh evidence verify-export --file export.jsonl --na-public-key <na-key> \
--executor-keys executor-keys.json
What this does not do¶
It does not authenticate the actor a source reports; a notice goes to it, but it is not proof of who acted.
It does not roll a credential back, and the NA never changes a resource itself: the controller performs the remediation the NA states.
A device that must act while the NA is unreachable does so under a pre-issued grant (1.6.0, Example: Pre-Issued Grants) or as break-glass.