Skip to main content

Signed bundles

A bundle holds ledger records (the whole ledger, or one run), the ledger head at export time and an Ed25519 signature over a canonical summary.
verify-bundle recomputes every record hash, checks the chain where it applies and checks the signature.
  • Whole-ledger bundle: the full hash chain from genesis to the recorded head is verified.
  • Single-run bundle: each record’s own hash and its link to its neighbour are verified. It is a subset, so it cannot prove nothing else happened; keep the whole-ledger bundle for that.
  • --pub checks the signature against a key you already trust. Without it, a valid signature only proves the bundle is self-consistent: anyone could re-sign an edited bundle with their own key.
The server serves bundles (GET /v1/runs/{id}/bundle, /v1/ledger/bundle) when started with REVERSI_SIGNING_KEY (or signing_key: in the project file), and publishes the public key at /v1/evidence/key. Give auditors the public key out of band. The same key signs anchors and retention archives.

Anchoring

A hash chain detects edits, deletions and reordering. It cannot detect truncation of the tail or a rewrite of the whole file by someone who controls it. Anchoring publishes the ledger head somewhere the operator cannot quietly rewrite, so that later you can prove the history is unchanged.

What is anchored

One JSON line per anchor: the hash of the last record (head), how many records the ledger had (records), a timestamp, the data region if set under egress, and, when a signing key is configured, the key_id and an Ed25519 signature.

Sinks and schedule

reversi.config.yaml
  • File sinks append one line per anchor. Webhook sinks POST the JSON; with secret the body is signed in the x-reversi-signature: sha256=<hmac> header, which you check like a notification webhook. Webhooks go through the egress guard and time out after 10 s.
  • The server and gateway anchor once at start and then every interval (default one hour). A failing sink is logged and does not lose the anchor for the other sinks.
  • On demand: reversi anchor publish [--file <path>] [--webhook <url>] [--secret <s>] [--key <file>] (it uses the sinks in the config when none are given, and exits 1 if any sink failed), or POST /v1/anchor (admin).
There is no scheduler for reports or bundles. Anchors are the scheduled evidence; run reversi report and reversi bundle from cron if you want files on a timetable.

Verifying

Every anchor in the file is checked against the ledger: the ledger itself must verify, the signature must match the key you trust (with --pub; an unsigned anchor is then rejected), the ledger must still have at least that many records, and the record at that position must still have the anchored hash. A shortened ledger fails with the ledger has 5 records but was anchored at 9: it has been truncated; a rewritten one with history was rewritten. Exit code 1 if any anchor fails. Keep the anchor file where the ledger’s operator cannot edit it, otherwise it proves nothing.

Run report

The extension picks the format. Both hold who ran what, step statuses, the rollback outlook (what stands, what cannot be undone and why) and the full event trail with record hashes. The PDF also states the data region and the ledger head at generation. The PDF is a plain, dependency-free A4 document (monospaced text) that any viewer opens and text tools can read. Over HTTP: GET /v1/runs/{id}/report (HTML) and GET /v1/runs/{id}/report.pdf; the console links both.

Period report

What the agents did over a window, for a weekly review or an auditor.
It covers calls, runs, outcomes, calls by tool, actor and reversibility class, what was blocked and what would have been blocked in observe and advise modes, tools called without a manifest, approvals (requested, approved, denied, expired, escalated, median time to decision), rollback results and verification rate, irreversible actions that ran, runs needing attention, budgets near their limit, and calls per day. The server serves it at GET /v1/report?since=7d&format=md|html|json (auditor and up).

Retention archives

When retention deletes a run’s working data, it can first write a signed bundle of that run’s ledger records to an archive folder (reversi retention apply --archive-dir ./arch --key signing.key). A manifest with audit.evidence_export: required makes that mandatory. The archive is an ordinary single-run bundle:
The ledger itself is never purged; the deletion is recorded in it as a purged event.

Offline verification without Node

The ledger format uses SHA-256 so anyone can verify it: python -m reversi_sdk verify ledger.jsonl, the browser viewer, or sha256sum and a short script. Bundle signatures need an Ed25519 verifier (OpenSSL 3, any crypto library).