Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Verifier guide (for auditors)

You are handed an Primary Path audit bundle. This guide shows how to prove — on your own machine, offline — that it is authentic and untampered, without trusting the operator, the server, or the vendor.

Since F24, each agent is its own chain, signed under its own evidence profile. The working procedure is: pick the agent you are auditing, pin the ring, verify that agent’s chain — you never process the other agents’ logs. The control plane (who changed which policy/budget/gate, agent-to-unit mappings, evidence changes, break-glass reads) is a separate per-tenant governance chain, verified the same way.

What verification proves

  1. Chain integrity: every event’s hash recomputes from its own fields (sha256("{seq}|{ts_ms}|{type}|{payload}|{prev_hash}")), every prev_hash equals the prior hash from a "genesis" start, and the chain reaches the bundle’s declared head. Any modified byte fails naming the exact record; any omitted event fails.

  2. Authenticity: the Ed25519 signature over canonical({count, first_seq, head, last_seq}) verifies against the org’s signing key. Run-scoped bundles add events_hash (a commitment to every event hash) to the signed record, because a slice has no checkable continuity to bind its contents.

  3. Execution grants: every approval_granted payload may carry a signed execution grant (grant), and each grant_redeemed event names the executor that ran the action. The CLI verifies every grant signature against the same ring and checks that each redemption names a grant that exists, so the bundle proves not only that a decision was made but that the executor ran exactly that action — and could run it only once.

Points 2 and 3 only mean something if you know the key is the org’s. That is pinning.

Pin once, out of band

At deployment time (or on your first engagement), obtain the public-key ring directly from the operator through a channel you trust — not from inside a bundle you are later asked to verify:

curl -s -H "Authorization: Bearer $PRIMARYPATH_ADMIN_TOKEN" \
    https://primarypath.internal/api/audit/keys | jq .keys > pubring.json

Keep pubring.json with your working papers. It only ever grows (key rotation adds entries; old bundles keep verifying), so one pin serves all future audits.

Verify

pip install primarypath-verify   # one dependency: cryptography
primarypath-verify bundle.json --pubring pubring.json
# OK   chain OK: 812 events, head 3fc4a1b2… ; signature OK: key 7caa296f (pinned in ring)

# fetch + verify ONE agent's chain (its own head, its own key):
primarypath-verify https://primarypath.internal --token $ADMIN_TOKEN \
    --agent payments-bot --pubring pubring.json

# the control-plane chain:
primarypath-verify https://primarypath.internal --token $ADMIN_TOKEN \
    --governance --pubring pubring.json

Failure modes name themselves: broken at seq N (a modified record), prev_hash mismatch (splice), events were dropped from the tail, SIGNATURE INVALID, key … is not in the pinned public-key ring (rewritten-and-re-signed history).

Unpinned runs are labeled. Without --pubring the CLI still checks the chain and the signature against the bundle’s own embedded key, and prints that this proves self-consistency only — a malicious operator could rewrite history and re-sign with a fresh key. The pinned check catches exactly that (test: server/tests/test_fuzz_verify.py::test_resigned_history_needs_pinning_to_catch).

A pinned run requires a signature. With --pubring, a bundle that carries no signature FAILS (UNSIGNED: …, exit 1): anyone who can edit a bundle but cannot forge its signature can still delete it, so absence is treated like a bad signature. For a chain that is unsigned on purpose (signing_backend: none), pass --allow-unsigned to ask for an integrity-only check explicitly; the verdict then says “integrity only, NOT authenticity” and never “signature OK” (test: server/tests/test_review_0921.py::test_pinned_ring_rejects_a_signature_stripped_bundle).

In a browser

The inbox’s audit panel recomputes the same chain with WebCrypto and checks the same Ed25519 signature (“Verify chain”): useful for reviewers without a terminal. The CLI on an air-gapped machine remains the gold-standard procedure; the two always agree (server/tests/test_fuzz_verify.py::test_clean_bundles_always_agree).

Scope caveats

  • A per-agent export (scope: "agent:<id>") is a complete chain from its own genesis: it proves integrity and completeness of that agent’s history up to its head. The bundle carries the agent’s evidence profile; an agent profiled signing_backend: none exports unsigned (the chain is still tamper-evident; with --pubring it verifies only under --allow-unsigned). Altering one agent’s log can never affect another agent’s verification.
  • The governance export (scope: "governance") is the same, for the control plane.
  • A full export ({"chains": [...]}) carries every chain, each verified independently, with one signature over all the heads.
  • A run-scoped export (scope: "run:<id>") proves each record is what was written, but cannot prove no record was omitted from that run — ask for the full export when completeness matters. The CLI output labels run-scoped results accordingly. Its signature commits to every event hash (events_hash): the verifier recomputes the commitment, so an edited event fails even with its own hash recomputed, and stripping the field breaks the signature. Legacy run bundles without events_hash verify with an explicit warning that the event contents are not bound.
  • The verifier needs no network: a saved bundle.json + pubring.json is a complete, portable audit artifact.

Verifying a release (the software itself)

The same discipline applies to what you deploy: every release’s image and chart are signed with cosign (keyless, logged in Rekor), every file on the releases page carries its own signature, and each release ships with SBOMs and checksums. The commands are in Verifying a download; a running deployment reports its version on /healthz.

The evidence pack (for humans)

primarypath-verify bundle.json --pubring pubring.json --report pack.html verifies as usual and additionally writes a self-contained HTML evidence pack: the verification verdict, the signing key and pinning status, every decision in the bundle (approval, action and arguments, outcome, who decided as what role, the endorser under dual control, the reason, timestamps), and the control-plane changes (policy, budgets, gates, admin actions with their who/why). Everything in the pack is folded from the verified events themselves; a failed verification is rendered just as loudly, because that too is evidence. Hand the pack to the auditor, keep the bundle: the pack is a rendering, the bundle plus your pinned ring is the proof.