# Trust ledger and action receipts

Every agent write leaves a receipt bound to the approval digest, policy fingerprint and permission ceiling it ran under, the model and prompt that produced it and its idempotency key. Receipts are hash-chained and exported as signed bundles that an auditor or insurer can verify offline.

Liyagent's governed execution binds an approved action to a stored digest, a data-policy fingerprint and, for a delegated run, a permission ceiling, so the bytes executed are the bytes approved. The **trust ledger** is where each action leaves a receipt that carries those bindings, chained so that no receipt can be changed, dropped or reordered without the chain showing it.

The ledger is also the one source of an agent's track record. The [autonomy ladder](/docs/govern/autonomy-ladder), [approval auto-triage](/docs/govern/approval-triage), approver segregation of duties and the outcome scorecard all read from it, so the record they judge an agent on is the same record an auditor can verify.

## What gets a receipt

| Receipt | Written when |
| --- | --- |
| `action` | An agent's write is sent: after a person approved it (`authorization: approval`), because the posture allows it (`posture`), because an accepted auto-allow rule let it through (`auto_rule`), or under the autonomy ladder (`autonomy`). Reads get no receipt. |
| `decision` | A held write is rejected, or approved but refused by a re-check. |
| `verdict` | An independent reviewer judges a sampled or shadow action. |
| `autonomy` | An agent's autonomy level is set, seeded, demoted or cleared. |

An action receipt's body carries:

- `approval`: the approval's `digest`, `policies` (the data-policy fingerprint), `ceiling`, `run_as` and `invoker`, `decided_by`, and how many times a person edited it;
- `arguments_sha256`: a hash of the arguments as sent, and their `shape`;
- `model` and `prompt_sha256`: the model and the system prompt of the run step that made the call;
- `idempotency_key`: the key the vendor was sent;
- `outcome` (`ok` or `error`) and, where the change was read back from the target, `verified`;
- `on_behalf_of` and the run's `correlation` id.

The agent's `agent.tool.call` audit row names its receipt and carries the same `model`, `prompt_sha256` and `idempotency_key`.

## Every decision names its digest

A held write can be edited before it is decided, so a decision is bound to the version its decider read: the request's `digest`, as `GET /api/approvals/{id}` returns it. `POST /api/approvals/{id}/approve`, `/reject` and `PATCH /api/approvals/{id}` refuse a request body without it (`428`, audited `agent.approval.digest.missing`), and one that no longer matches with `409`. The console, the mobile app and the CLI send it; with the SDKs, pass `digest=`.

A batch (`POST /api/approvals:batch`) may leave `digests` out: each request is bound to the version the route read before deciding it, as before. Teams approval cards decide inside Liyagent and are bound by the card.

`TICKETIQ_APPROVAL_DIGEST_REQUIRED=false` lets a script decide by id alone.

## The chain

Each tenant has its own chain. Each receipt has a sequence number, the hash of the receipt before it (`prev`), and its own `hash`: the SHA-256 of its canonical JSON without the hash. `GET /api/governance/ledger/verify` walks the whole chain and reports any receipt that no longer hashes or no longer links. A receipt that fails the check counts for nothing in an agent's track record.

## Export and verify offline

`GET /api/governance/ledger/export` (`audit.read`; filter with `agent`, `since`, `until`) returns a bundle: the receipts, a manifest that commits to them, and an Ed25519 attestation of the manifest made with the instance's [audit signing key](/docs/monitor/audit-log). `GET /api/governance/ledger/{receipt}` returns one receipt with its **evidence pack**: the receipt and every receipt about it, such as its reviewers' verdicts, signed the same way.

To verify a bundle with nothing but Python and the `cryptography` package:

```bash
python scripts/verify_trust_ledger.py bundle.json --pin <fingerprint>
```

Take the fingerprint from `/.well-known/liyagent-audit-key.json`. The script checks every receipt's hash, the links between consecutive receipts, the manifest's commitment to exactly these receipts, and the signature. It exits 0 when everything verifies.

## Approvals decided before the ledger

`POST /api/governance/ledger:backfill` receipts the decisions already in the approval history, once each, marked `backfilled`, so a live agent's track record starts from its history and not from nothing. Adopting every agent onto the autonomy ladder runs the backfill first.

## Format

The receipt format is `liyagent-trust-ledger/1`, defined in `ticketiq/trustledger.py`. The durable run event log uses the same format, so run events and action receipts can be verified together.
