Skip to content

Trail & verification

Opaline lets you prove that a decision was made the way it says it was. Every live decision is recorded in a per-tenant, append-only, hash-chained trail before the response is returned. You can verify that trail yourself, without trusting the server that produced it.

The chain

  • Each entry's hash is SHA256(JCS(row) || prev_hash), where JCS is the canonical (RFC 8785) form of the row. The sequence is strictly monotonic per tenant, and the genesis entry's prev_hash is 32 zero bytes.
  • The decide transaction writes the trail row, the usage event, and the stored idempotency response in one commit. Trail-before-response means a decision is never returned until that commit is durable. If the trail can't be written, the engine fails closed (503) rather than decide silently.
  • The database role has UPDATE and DELETE revoked on the trail and on published pack versions. The chain is append-only at the catalog level, not just by convention.

Tampering with any entry changes its hash, which breaks every entry after it, so a single altered row is detectable by replay.

Checkpoints and anchoring

Replay alone proves internal consistency, but not that history wasn't rewritten and re-hashed. Checkpoints close that gap:

  • Periodically the chain head is sealed into a signed checkpoint (Ed25519, C2SP signed-note format). Each checkpoint chains to its predecessor.
  • Checkpoints are anchored to external object storage under an object-lock compliance retention: write-once, deletion-proof for the retention window.
  • Because the anchor is outside the operator's control, it defends against both an operator rewriting history and a restore that silently truncates it.

Verify your trail

GET /v1/trail/verify has two modes.

Range mode (default): the server replays your chain and reports the first bad sequence, if any. A fast own-trail health check:

bash
curl "https://api.opaline.dev/v1/trail/verify?mode=range" \
  -H "Authorization: Bearer opl_live_…"
# { "ok": true, "first_bad_seq": null }

Entry mode returns a client-verifiable segment for one entry: the entry itself, the segment hashes up to the next checkpoint, the signed checkpoint note, and the anchor reference. You recompute the hashes and check the signature yourself, with no trust in the server's own replay required:

bash
curl "https://api.opaline.dev/v1/trail/verify?mode=entry&seq=42" \
  -H "Authorization: Bearer opl_live_…"
# { "mode": "entry", "seq": 42, "entry_hash": "…", "body": {…},
#   "segment": [...], "checkpoint_note": "…", "anchor_version": "…" }

Read your trail

GET /v1/trail returns your decision history. Each entry carries its sequence, entry hash, and the recorded body:

bash
curl "https://api.opaline.dev/v1/trail" \
  -H "Authorization: Bearer opl_live_…"

The auditor

For your own independent verification, use mode=entry above: it needs nothing but your key and recomputes the proof client-side. The auditor described here is the deeper, operator-side check.

A server replaying its own chain cannot, by itself, catch an operator who rewrites history and re-checkpoints. The auditor closes that last gap. We run it from a host the engine operator doesn't control, persisting the last checkpoint it trusted and, on each run, checking the live chain against it:

bash
opaline-cli verify --tenant <uuid> --namespace live --state-file ./audit.json
# first run:  audit: Bootstrapped { seq: N }     (trusts the head checkpoint)
# later runs: audit: Verified { from_seq, to_seq }
# on tamper:  AUDIT VIOLATION: Rollback { … } | Fork { … }   (non-zero exit)

It detects three classes of tampering, each covered by tests:

ClassWhat happenedReported as
RollbackThe head moved backwards below the trusted checkpoint.Rollback
ForkA divergent entry exists at or before the trusted sequence.Fork
Amputated middleA row was deleted between genesis and the head (caught by replay).Fork at that seq

The auditor's findings are Rollback or Fork; an amputated middle is caught by the full replay and reported as a Fork at the missing sequence. Run it on a schedule, store the state file on the auditing host, and a tamper attempt becomes a non-zero exit and an alert. The auditor needs direct read access to the trail store and the checkpoint signing key, so it is run by the operator (or an independent auditor granted that access), not over the public API.

Next