# Attested Agent Receipt (AAR) v0.1

Status: proposed draft, 2026-10-20. This document describes a proposed receipt format; it is not a description of a deployed Lugano runtime, public log, or attestation service. A receipt under this format would be a signed, logged record of claims about an AI-agent run: which tools it was allowed to use, where bytes went, what files it touched, what it paid, and under which attestation it ran. The [reference verifier](../verify/README.md) checks supplied sample data offline. It verifies a signature, signed tree head, inclusion proof, and stated `retained` value; it does not observe a runtime, verify an attestation root, or independently establish zero retention.

## Goals

1. The format is intended to let a regulator, customer, or counterparty inspect a supplied receipt with public code and no account.
2. The proposed format is designed not to include prompts, outputs, file contents, or direct identities. It may include hashes, hosts, counts, and measurements, which can still disclose operational metadata.
3. The proposed trust model uses a vendor or device root plus an append-only log. The runtime operator signs, but is not intended to be the sole root of trust.

## Illustrative format example

The following is an illustrative format example, not a record of a real run, customer, payment, log, or deployment. A receipt in this proposed format has two top-level members: `body` and `signature`.

```json
{
  "body": {
    "aar": "0.1",
    "run_id": "01J9ZK8W3N7Q4R2S6T8V0X2Y4Z",
    "issued_at": "2026-10-20T14:03:11Z",
    "runtime": { "name": "lugano-runtime", "version": "0.1.0", "measurement": "sha256:…" },
    "attestation": {
      "type": "device",
      "key_id": "did:key:z6Mk…",
      "evidence": "base64url(…)",
      "vendor": null
    },
    "model": { "measurement": "sha256:…", "label": "sha256 only; no model name is required" },
    "policy": { "hash": "sha256:…" },
    "tools": [
      { "name": "calendar-sync", "artifact": "sha256:…", "preflight": { "endpoints": ["api.calendar.com", "cdn.vendor.net", "185.0.0.0/8"], "verdict": "blocked" } }
    ],
    "ledger": {
      "egress": [ { "host": "swiss.com", "bytes": 48213, "allowed": true } ],
      "files": [ { "path_hash": "sha256:…", "op": "read" } ],
      "payments": [ { "method": "single-use-card", "merchant": "swiss.com", "amount_hash": "sha256:…" } ],
      "tool_calls": 9
    },
    "retained": 0,
    "log": { "log_id": "lugano-log-1", "index": 48112, "root": "sha256:…", "tree_size": 48113, "inclusion_proof": ["sha256:…"] }
  },
  "signature": { "alg": "ed25519", "key_id": "did:key:z6Mk…", "sig": "base64url(…)" }
}
```

### Fields

| Field | Meaning |
|---|---|
| `aar` | Spec version. |
| `run_id` | ULID, unique per run. |
| `runtime.measurement` | Claimed hash of the runtime binary that produced the receipt. |
| `attestation.type` | Proposed `device` (key held in Secure Enclave or TPM) or `tee` (Intel TDX, AMD SEV-SNP, NVIDIA CC) value. |
| `attestation.evidence` | Proposed raw device certificate chain or vendor quote, base64url. The v0.1 verifier does not validate it. |
| `model.measurement` | Claimed hash of the weights that served the run. Names are optional. |
| `policy.hash` | Claimed hash of the allowlist policy in force. |
| `tools[].preflight` | Proposed static-analysis record before a tool is allowed, with its stated verdict. |
| `ledger.egress` | Claimed hosts and byte counts, with the stated policy result. Blocked attempts would remain in the list with `allowed: false`. |
| `ledger.files` | Claimed hash of each path and operation, rather than the raw path or content. |
| `ledger.payments` | Proposed method and merchant in clear; amount as a hash with a salt the customer holds. |
| `retained` | Stated bytes of prompt or output retained by the runtime after the run. The draft requires `0` for a receipt claiming Lugano conformance; the verifier only compares the supplied value to zero. |
| `log` | Proposed position in a signed Merkle log and its inclusion proof. |
| `signature` | Proposed Ed25519 signature over the canonical body. |

## Canonical form

The draft defines the signed bytes as canonical JSON of `body`: members sorted by code point, no insignificant whitespace, UTF-8, numbers as shortest round-trip decimals, and strings escaped per RFC 8785 (JCS). The [reference verifier](../verify/verify.mjs) implements the subset needed for the supplied samples.

`body_hash = SHA-256(canonical(body))` is what the signature covers.

`leaf_input = SHA-256(canonical(body without the "log" member))` is what the log records, because the `log` member is filled in after the leaf exists.

## Log

The draft uses an append-only Merkle tree in the style of RFC 6962. The leaf for a receipt is `SHA-256(0x00 || leaf_input)`. Interior nodes are `SHA-256(0x01 || left || right)`. A deployment implementing the format would issue signed tree heads; `log.root` and `log.tree_size` would identify the head an inclusion proof is built against. This draft does not identify a deployed public log.

## Proposed verification rules

A verifier implementing this draft would evaluate the following:

1. `body` parses and `aar` is a version the verifier knows.
2. `signature.sig` verifies over `canonical(body)` with the public key that `signature.key_id` names **in the verifier's trust store**. A key carried inside the receipt is not trusted on its own.
3. The inclusion proof in `log` recomputes, for `log.index` and `log.tree_size`, to the root in a **signed tree head** checked with a log public key from the verifier's trust store. The `log.root` carried in a receipt is informational; a proof that only matches that receipt's own root does not establish independent log inclusion.
4. `attestation.evidence` verifies against the vendor's root (TDX, SEV-SNP, NVIDIA) for `tee`, or the device manufacturer's root for `device`. The v0.1 reference verifier reports this step as `not implemented`.
5. The supplied `retained` value is `0`.

The [reference verifier](../verify/verify.mjs) implements steps 1 to 3 and compares the supplied `retained` value to zero. Without a trust store and signed tree head it reports `UNVERIFIED`, not `VERIFIED`; it skips step 4. A passing result means the supplied data is cryptographically consistent with the supplied trust store and signed tree head, and states `retained: 0`. It does not attest a device or runtime, observe retention, or establish zero retention.

### Signed tree head

```json
{ "log_id": "lugano-log-1", "tree_size": 48113, "root": "sha256:…", "sig": "base64url(ed25519 over canonical({log_id, root, tree_size}))" }
```

### Trust store

```json
{ "keys": { "<signature.key_id>": "<SPKI PEM>" }, "logs": { "<log_id>": "<SPKI PEM>" } }
```

A deployment would need an independently authenticated way to distribute the runtime and log keys its verifier accepts. This draft does not publish or identify production keys.

## Privacy

A format instance includes hosts, counts, hashes, and measurements. Those values can disclose operational metadata, such as destinations, activity volume, or timing. The draft's listed fields do not include prompt, output, file-content, raw-path, user-identity, internal-hostname, or private-address fields, but the format alone does not provide a privacy guarantee, remove all sensitive metadata, or establish zero retention.

## Changes

- 0.1 (2026-10-20): first draft, with a reference verifier and synthetic samples. The draft requires a trust store and signed tree head for a `VERIFIED` prototype result after review found a receipt could otherwise vouch for itself.
