Example: Evidence Store in the Network Authority

Up to v0.58 the Network Authority signed every boundary decision but kept no record of it beyond an audit event, and it never saw what was actually done. Execution evidence existed (a signed hash chain per decision, see Worked Example: Execution Evidence Hash Chain), but it lived wherever the controller that acted chose to keep it, so an audit of a vendor or a secret depended on the controller’s own storage.

v0.59 makes the Network Authority the durable record. With the evidence store on, the NA stores every decision it signs (with its context and justification proof) and accepts signed execution evidence from controllers after they act, for example creating, rotating or revoking a secret. It links each record to its decision and keeps one chain per secret across decisions (resource_id, resource_sequence, prev_resource_digest). The store is append-only: the database refuses edits, and every entry links to the digest of the one before it. The key design decision is that the NA never signs execution evidence: controllers sign their own records with registered executor keys, and the NA only validates, links and keeps them.

This is not a secrets manager. The store holds metadata only: identifiers, versions and timestamps. Secret values never reach the NA, and records that look like they carry one are refused.

What the store proves

  • Every stored decision carries the NA’s signature, the exact context it was made for and, for policy-aware decisions, its justification proof.

  • Every execution record was signed by a registered, active executor key, under an authorized decision, inside the decision’s validity window, for the capability the decision covered.

  • For each secret, the records form one gap-free chain: a missing, duplicated, reordered or changed record is detected.

  • The store as a whole is one hash chain, so removing or editing an entry is detectable even by someone with direct database access. The only permitted removal is a retention run, which leaves a signed checkpoint the remaining history verifies from.

Walkthrough: a vendor’s API key

1. Turn the store on and register the controller

EVIDENCE_STORE=on gunicorn "genesis_mesh.na_service.wsgi:app"
# or: genesis-mesh na start --evidence-store on

A privileged operator registers the secrets controller’s executor key. An executor identity is all a controller needs; it does not enrol as a node.

POST /admin/evidence/executor-keys
{ "key_id": "ctrl-1",
  "public_key": "<base64 Ed25519>",
  "executor_sovereign_id": "secrets-controller" }

2. Authorize, act, submit evidence

The vendor holds a membership attestation (see Example: Attestation-Backed Boundary Evaluation). Each request is evaluated and the decision is stored automatically:

POST /admin/boundary/evaluate
{ "attestation_id": "<vendor attestation>", "requested_capability": "secret.manage" }

After acting, the controller signs an ExecutionEvidence record and submits it:

from genesis_mesh.trust.execution import record_execution

created = record_execution(
    decision, "secrets-controller", "secret.manage", "success", controller_key,
    issued_by="ctrl-1",
    execution_parameters={"secret_version": "7", "vault_uri": "https://kv.example/secrets/api"},
    resource_id="kv:vendor-acme/api-key", resource_action="create",
)
# POST /evidence/execution {"evidence": created}
rotated = record_execution(
    next_decision, "secrets-controller", "secret.manage", "success", controller_key,
    issued_by="ctrl-1", execution_parameters={"secret_version": "8"},
    resource_id="kv:vendor-acme/api-key", resource_action="rotate",
    prior_resource_record=created,
)

The NA refuses a record with a stable code when it is signed by an unknown or retired key (evidence_unknown_executor), fails its signature (evidence_invalid_signature), points to a missing or denied decision (evidence_decision_not_found, evidence_decision_denied), falls outside the decision’s window (evidence_outside_decision_window), names another capability (evidence_capability_mismatch), leaves a gap or forks a chain (evidence_chain_gap, resource_chain_gap, resource_chain_mismatch), collides with a stored record (evidence_conflict), or carries secret material (evidence_secret_material). An identical resubmission is accepted once and then answered as a duplicate. Every write and every rejection is an audit event.

3. Revoke the vendor

When the vendor’s attestation is revoked, the next decision is a signed DENY, and evidence submitted under it is refused with evidence_decision_denied.

4. Show and verify the history from the NA alone

GET /admin/evidence/resources/kv:vendor-acme/api-key
GET /admin/evidence/vendors/vendor-acme
GET /admin/evidence?capability=secret.manage&outcome=success&since=2026-09-01T00:00:00Z
GET /admin/evidence/verify

Each history response lists the decisions and records, decision to execution, with a verification result covering signatures, both chains, decision links and windows, and the store chain.

5. Export for a SIEM and verify offline

GET /admin/evidence/export?since_sequence=N returns gm.evidence.event JSON Lines (Evidence event schema (gm.evidence.event v1)), for a SIEM pipeline to poll incrementally. Anyone with the NA public key and the executor keys can verify an export offline:

genesis-mesh evidence verify-export \
    --file export.jsonl \
    --na-public-key <base64> \
    --executor-keys executor-keys.json   # from GET /admin/evidence/executor-keys

6. Retention

The store keeps everything by default. To remove old entries, a privileged operator applies retention:

POST /admin/evidence/retention/apply
{ "older_than_days": 365 }

Only a prefix of the store is removed. The latest record of every secret, every decision still inside its window, and anything that would split a decision from its evidence are kept. The NA signs a RetentionCheckpoint recording what was removed, including each affected secret’s last removed record, so the remaining history still verifies. The run is audited.

CLI usage

genesis-mesh na start --evidence-store on
genesis-mesh evidence verify-export --file export.jsonl \
    --na-public-key <base64> --executor-keys executor-keys.json

Integration with BoundaryEngine

Nothing changes in the engine. The NA stores the signed BoundaryDecision, its ContextRecord and its JustificationProof after evaluate_with_policies() or evaluate_attestation_with_policies() returns, in the same request. If the store cannot record a decision, the request fails with 503 evidence_store_unavailable instead of returning a decision the store does not hold.

Limits of this release

  • The metadata guard refuses common secret field names, PEM blocks, JWTs and long opaque strings. It is a guard, not a guarantee: controllers must send identifiers, versions and timestamps, never values.

  • Decisions made before the store was enabled, or by another NA, cannot be referenced by evidence.

  • The store runs on SQLite. The SQL database option for multi-instance deployments is planned for v0.60, and the store’s invariants are already enforced by database constraints for it.

Test command

python -m pytest genesis_mesh/tests/test_evidence_store.py \
    genesis_mesh/tests/integration/test_evidence_store_lifecycle.py -q