SEE

SEE Receipt Format — Specification v1

Status: OPEN FORMAT. By the founder's decision (2026-10-29) this format is open at launch: anyone may implement producers, verifiers, and archives against this document without permission, licence, or a relationship with the operator. This document is the complete contract — it is written so that an independent implementer, with no access to the reference code, can build a verifier that reaches the same verdicts.

Capitalized MUST / MUST NOT / SHOULD are normative (RFC 2119 style).


1. Concepts

The system records authorizations and their executions as signed statements in an append-only, per-tenant ledger:

Two verdicts are always kept separate, in the format and in every verifier: authenticity (these bytes are what the signer sealed and have not changed) and currency (the cited mandate is still in force now). Offline verification (§10) establishes authenticity only, and MUST say so. A receipt sealed under a mandate that was later paused or revoked remains authentic.

2. Statements and canonical form

Every sealed record is an in-toto Statement (JSON object):

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [{"name": "<subject name>", "digest": {"sha256": "<hex>"}}],
  "predicateType": "<predicate URN>",
  "predicate": { "...": "..." }
}

Predicate URNs (v1):

RecordpredicateTypesubject namesubject digest
Mandate versionurn:see:predicate:mandate-version:1mandate/<mandate_id>/v<version>SHA-256 of the canonical terms object (§2.1)
Decisionurn:see:predicate:decision:1preauth/<preauth_id>the action hash (§3)
Human approvalurn:see:predicate:human-approval:1preauth/<preauth_id>the action hash
Aborturn:see:predicate:abort:1preauth/<preauth_id>the action hash
Receipturn:see:predicate:receipt:1receipt/<receipt_id>the action hash
Checkpointurn:see:predicate:checkpoint:1checkpoint/<checkpoint_id>SHA-256 of the Merkle root's hex encoding, as UTF-8 bytes

Every predicate carries schema_version: 1. A verifier MUST reject a statement whose schema_version it does not support.

2.1 Canonical JSON (creation-time only)

Payload bytes are produced ONCE, at creation, as:

After creation the bytes are stored, transmitted, and verified verbatim. A verifier MUST NOT re-serialize a parsed payload to check a signature — it verifies the exact bytes it received (§4). This is the DSSE exact-bytes rule, and it is what makes receipts verifiable decades later even as JSON libraries change.

Amounts are ALWAYS integer minor units (cents) plus an ISO 4217 currency code. Floats never represent money anywhere in the format.

A JSON document containing duplicate object keys is ambiguous — its meaning depends on which parser reads it — and a verifier MUST reject it rather than adopt any one reading. The same applies to the non-standard constants NaN / Infinity. Object field sets in this format are CLOSED: a statement, predicate, envelope, or signature entry carrying a field its section does not define is malformed, and a verifier MUST reject it (extensions arrive only as §11 provides).

Timestamps are RFC 3339 UTC at one-second precision (...Z). The engine's time quantum is one second: validity bounds are sealed truncated to whole seconds.

3. Actions and the action hash

A proposed action:

{
  "agent_id": "shopping-agent",
  "category": "groceries",
  "amount_minor": 4200,
  "currency": "USD",
  "counterparty": "Instacart",
  "description": "Weekly groceries"
}

action_hash = lowercase hex of SHA-256 over the canonical JSON (§2.1) of the action object. The action hash binds decisions, approvals, and receipts to exactly one proposed action.

4. Envelopes and signatures

Records that must be independently verifiable (receipts, checkpoints) travel in a DSSE-style envelope:

{
  "payloadType": "application/vnd.see.statement+json",
  "payload": "<base64 of the exact statement bytes>",
  "signatures": [
    {"keyid": "<key id>", "alg": "ed25519", "sig": "<base64>"},
    {"keyid": "<key id>", "alg": "ecdsa-p256-sha256", "sig": "<base64>"}
  ]
}

The envelope carries exactly the three fields shown, and each signature entry exactly keyid, alg, sig. The alg declared in a signature entry MUST equal the algorithm of the key it names in the trusted key set: a verifier MUST NOT silently substitute the key's registered algorithm for a different declared one — a mismatched declaration is a malformed envelope, not a verified signature.

The signed bytes are the DSSE Pre-Auth-Encoding (PAE):

PAE = "DSSEv1" SP len(payloadType) SP payloadType SP len(payload) SP payload

where len(...) is the decimal byte length in ASCII and SP is one space (0x20).

4.1 Signature scheme (dual-signature policy)

An envelope is authentic only if it carries at least one valid signature of EACH algorithm, from keys present in the trusted key set, and no relied-upon key is marked compromised.

4.2 Key registry entries

{
  "key_id": "dev-ed25519-1",
  "alg": "ed25519",
  "public_key": "<base64>",
  "valid_from": "2026-01-01T00:00:00Z",
  "valid_to": null,
  "status": "active"            
}

status is one of active, verify-only, compromised. A verifier MUST fail any signature whose key is compromised, and MUST fail signatures naming keys absent from the trusted set. Where a verifier takes keys from a bundle itself (§8), it MUST warn that key provenance is the bundle's own claim; production verifiers pin a published registry.

5. Predicate contents

5.1 Mandate version

{
  "schema_version": 1,
  "tenant_id": "ten_…",
  "mandate_id": "mnd_…",
  "version": 3,
  "terms": {
    "agent_id": "shopping-agent",
    "categories": ["groceries", "household"],
    "per_purchase_cap_minor": 15000,
    "period_cap_minor": 60000,
    "period_days": 30,
    "currency": "USD",
    "not_before": "2026-10-29T00:00:00Z",
    "expires_at": "2027-10-29T00:00:00Z",
    "human_approval_threshold_minor": 5000,
    "status": "active"            
  },
  "created_at": "2026-10-29T12:00:00Z"
}

human_approval_threshold_minor may be null (never escalate) or 0 (escalate every purchase). status ∈ active | paused | revoked.

5.2 Decision

{
  "schema_version": 1, "tenant_id": "…", "preauth_id": "pa_…",
  "mandate_id": "mnd_…", "mandate_version": 3,
  "action": { "...": "§3" }, "action_hash": "<hex>",
  "decision": {"outcome": "allow", "reason_code": "ok",
               "mandate_id": "mnd_…", "mandate_version": 3},
  "decided_at": "2026-10-29T12:00:00Z"
}

Decision rules (deterministic; evaluated in this order): agent match → mandate status active → not_before ≤ now < expires_at → currency match → positive amount → category allowed → per-purchase cap → period cap (completed spend plus outstanding reservations) → human-approval threshold → allow. Reason codes are stable strings (ok, agent_mismatch, mandate_not_active, mandate_not_yet_valid, mandate_expired, currency_mismatch, invalid_amount, category_not_allowed, over_per_purchase_cap, over_period_cap, over_human_approval_threshold).

5.3 Human approval

Current form (schema_version: 2):

{"schema_version": 2, "tenant_id": "…", "preauth_id": "pa_…",
 "mandate_id": "mnd_…", "mandate_version": 3,
 "action_hash": "<hex>", "approved_at": "…",
 "approver": {"kind": "account", "id": "acc_…", "label": "owner@example.com"}}

Historical form (schema_version: 1, sealed before attribution existed): approver is the string "tenant-api-key" and no mandate_id / mandate_version is present. These statements remain valid history; verifiers MUST accept them and MUST report that the approving principal cannot be distinguished further from the record.

5.4 Receipt

{
  "schema_version": 1, "receipt_id": "rcpt_…", "tenant_id": "…",
  "preauth_id": "pa_…", "mandate_id": "mnd_…", "mandate_version": 3,
  "mandate_version_at_seal": 4,
  "mandate_status_at_seal": "active",
  "action": { "...": "§3" }, "action_hash": "<hex>",
  "decision": { "...": "§5.2 decision object" },
  "completion": {
    "completed_amount_minor": 4200,
    "counterparty_receipt_ref": "order-9",
    "note": null,
    "completed_at_claimed": "2026-10-29T12:00:05Z"
  },
  "sealed_at": "2026-10-29T12:00:05Z",
  "time_attestation": {
    "authority": "rfc3161:timestamp.digicert.com",
    "trusted": true,
    "attested_at": "2026-10-29T12:00:05Z",
    "token": "<base64 DER RFC 3161 TimeStampToken, or null>"
  }
}

Notes: completed_at_claimed is the actor's claim; sealed_at is the server's sealing time — the two are never conflated. The time attestation (§7) covers the digest SHA-256(action_hash as UTF-8). The citation (mandate_id, mandate_version) names the version the CHECK was judged under. mandate_version_at_seal and mandate_status_at_seal name the version in force — and its status — when the receipt was sealed; the two claims are distinct and both are inside the signed predicate. Receipts sealed before mandate_version_at_seal existed do not carry it; verifiers MUST then report the seal-time version as not inspectable rather than guessing (§10 step 7).

5.5 Checkpoint

{"schema_version": 1, "checkpoint_id": "chk_…", "tenant_id": "…",
 "tree_size": 42, "merkle_root": "<hex>", "head_entry_hash": "<hex>",
 "created_at": "…"}

5.6 Abort

An abort is a principal's durable refusal of a pending or authorized preauthorization — the counterpart of an approval — and is sealed the same way:

{"schema_version": 1, "tenant_id": "…", "preauth_id": "pa_…",
 "mandate_id": "mnd_…", "mandate_version": 3,
 "action_hash": "<hex>", "from_state": "pending_approval",
 "aborted_at": "…",
 "aborted_by": {"kind": "account", "id": "acc_…", "label": "owner@example.com"}}

from_state is pending_approval or authorized; mandate_version is the version in force at the abort; aborted_by follows the §5.3 principal rules. Only durable acts seal: transient refusals (a gate declining because the mandate is paused, out of scope, or over cap) change no state and MUST NOT be recorded as aborts.

6. The ledger chain and Merkle tree

Per tenant, entries are numbered seq = 1, 2, 3, … with no gaps. Each entry commits to its predecessor:

entry_hash = SHA-256( bytes_from_hex(prev_hash) || payload_bytes )   (hex)
prev_hash of seq 1 = 64 '0' characters (32 zero bytes)

The Merkle tree is over the entry hashes, RFC 6962-style with domain separation:

leaf = SHA-256( 0x00 || bytes_from_hex(entry_hash) )
node = SHA-256( 0x01 || left || right )

An odd node at any level is promoted unchanged (never duplicated). The root of an empty tree is SHA-256 of the empty string. An inclusion proof is {"leaf_index": seq-1, "tree_size": N, "audit_path": [<hex>, …]} — sibling hashes from leaf to root, omitting levels where the node was an odd tail.

7. Time attestation (RFC 3161)

When time_attestation.trusted is true, token is a DER-encoded RFC 3161 TimeStampToken (CMS SignedData) whose message imprint is the digest stated in §5.4. Verification procedure:

  1. Parse the token; extract TSTInfo, the message imprint, the digest algorithm, the signer certificate, the signed attributes, and the signature.
  2. The imprint MUST equal the attested digest.
  3. The messageDigest signed attribute MUST equal the hash of the TSTInfo bytes under the token's own digest algorithm (SHA-256, SHA-384, and SHA-512 all occur in production TSAs — the algorithm is read from the token, never assumed).
  4. The signature MUST verify over the DER encoding of the SET OF signed attributes (tag 0x31 in place of the context-specific 0xA0) using the embedded signer certificate.
  5. Certificate chain-to-root trust is the verifier's policy decision and MUST be reported, not silently assumed.

When trusted is false the authority is the operator's local clock and the token is null; verifiers MUST label this "untrusted time".

8. The receipt bundle (verification package)

A bundle is the self-contained package a stranger verifies:

Current form (bundle_version: 3):

{
  "bundle_version": 3,
  "receipt": {"receipt_id": "rcpt_…", "envelope": { "...": "§4" }},
  "mandate": {"mandate_id": "mnd_…", "version": 3,
              "terms": { "...": "§5.1 terms" }, "created_at": "…",
              "statement": {"payload": "<base64 of the exact mandate-version statement bytes (§5.1)>",
                            "chain": {"seq": 9, "entry_hash": "<hex>", "prev_hash": "<hex>"},
                            "inclusion_proof": null | { "...": "§6" }}},
  "mandate_at_seal": null | {"mandate_id": "mnd_…", "version": 4,
              "terms": { "...": "§5.1 terms" }, "created_at": "…",
              "statement": { "...": "same shape as the cited block's statement" }},
  "approval": null | {
    "payload": "<base64 of the exact approval statement bytes (§5.3)>",
    "chain": {"seq": 16, "entry_hash": "<hex>", "prev_hash": "<hex>"},
    "inclusion_proof": null | { "...": "§6" }
  },
  "chain": {"tenant_id": "…", "seq": 17, "entry_hash": "<hex>",
            "prev_hash": "<hex>"},
  "checkpoint": null | {
    "checkpoint_id": "chk_…", "envelope": { "...": "§4" },
    "inclusion_proof": { "...": "§6" },
    "anchor": null | { "...": "§9" }
  },
  "time_attestation": { "...": "§5.4 time_attestation" },
  "keys": [ { "...": "§4.2 entries" } ]
}

9. External anchors

An anchor witnesses one checkpoint: the anchored digest is SHA-256(checkpoint payload bytes), hex. Only this digest ever leaves the operator (hash-only disclosure). The anchor object:

{"kind": "opentimestamps" | "rfc3161_dual",
 "state": "anchored" | "pending" | "failed" | "not_configured",
 "detail": "<human-readable account>",
 "proof": { "...": "below" } | null}

9.1 OpenTimestamps proof

{"digest": "<hex>", "calendars": ["https://…", …],
 "ots_b64": "<base64 of the detached .ots serialization>"}

The detached serialization is the standard OpenTimestamps format for the anchored digest (SHA-256 file hash). State derivation: if the proof contains a Bitcoin block header attestation, the state is anchored (a verifier SHOULD note that confirming the header chain itself requires a Bitcoin header source); if it contains only calendar pending attestations, the state is pending — witnessed by public calendars, Bitcoin confirmation outstanding. pending MUST NOT be presented as anchored.

9.2 Dual RFC 3161 proof

{"digest": "<hex>",
 "tokens": [{"authority": "rfc3161:<host>", "attested_at": "…",
             "token": "<base64 DER TimeStampToken>"}, …]}

State is anchored only when two administratively independent authorities' tokens both verify under §7 against the anchored digest.

9.3 Overclaim rule

A verifier MUST derive the state from the proof and compare it with the claimed state. A claim stronger than the proof (e.g. anchored over a calendar-pending OTS proof, or over TSA tokens that do not verify) is a verification failure, not a warning.

10. Verification procedure (receipt bundle)

An independent verifier MUST, in any order that fails closed:

  1. Envelope signatures (before parsing): base64-decode the receipt payload; build the PAE (§4); verify the dual-signature policy (§4.1) against the trusted key set. Any failure ⇒ NOT authentic.
  2. Payload structure: parse the payload; require the in-toto statement type, a supported schema_version, and the receipt predicate URN (a statement of any other kind in the receipt slot is a type confusion and fails here). Require the closed §5.4 field sets for the predicate and its action, decision, and completion objects, and integer minor units for every amount (§2.1).
  3. Chain linkage: recompute SHA-256(prev_hash || payload) and require equality with chain.entry_hash.
  4. Checkpoint inclusion: if a checkpoint block is present, verify its envelope signatures (step 1 rules), then recompute the Merkle root (§6) from chain.entry_hash + the inclusion proof and require equality with the checkpoint predicate's merkle_root; require the checkpoint's tenant to match the chain block's tenant and leaf_index == seq - 1. Absence of a checkpoint is a warning (signatures + linkage only), not a failure. In all cases the receipt statement's own tenant_id MUST equal the chain block's tenant — a receipt is only meaningful inside its own tenant's chain, and a relabeled chain block fails.
  5. Mandate citation: the bundle's mandate block MUST match the receipt's cited (mandate_id, mandate_version) and carry the terms, so what was authorized can be inspected. When the block carries a statement (REQUIRED in bundle_version ≥ 3), the verifier MUST validate the terms binding: decode the statement bytes; require the §5.1 mandate-version predicate type; require the statement to bind to the block's (mandate_id, version) and to the chain block's tenant (predicate fields AND the subject name mandate/<mandate_id>/v<version>); recompute the subject digest over the canonical (§2.1) bytes of the statement's terms and require equality with the subject digest; require the block's carried terms to EQUAL the statement's terms (a package that shows different terms than the chain sealed is defective); and recompute the chain link SHA-256(prev_hash || statement bytes) against the statement block's entry_hash. Any of these failing is a verification FAILURE. The terms the step-7 verdicts judge are then the STATEMENT's terms. (Recomputing the subject digest is the one place a verifier canonicalizes an object: the digest is over the terms object, not over transmitted bytes — the DSSE exact-bytes rule for signatures is untouched.)
  6. Approval proof: if the receipt's decision outcome is needs_human_approval, the bundle MUST let the verifier establish that a human approved. For a bundle_version ≥ 2 package the approval block MUST be present (absence is a FAILURE — the package asserts an approval it cannot show); a version-1 package cannot carry one, and the verifier MUST warn that approval is unprovable from it. When a block is present the verifier MUST: decode the statement bytes; require the §5.3 predicate type; require the statement to bind to the receipt's preauth_id, action_hash, and tenant (predicate fields AND the subject name/digest); recompute the chain link SHA-256(prev_hash || statement bytes) against the block's entry_hash (altered bytes MUST fail); apply the ANCHORING rule below; and report the attribution — for schema_version 2 statements the approver MUST be a concrete principal object (§5.3) and the statement MUST name the mandate_version it approved against (a missing one is a FAILURE); for schema_version 1 statements the verifier MUST warn that the approver cannot be distinguished further. ANCHORING: the approval statement carries no signature of its own, so checkpoint inclusion is the only step that ties its bytes to a dual-signed root — chain linkage alone is arithmetic the package's presenter can recompute over a fabricated statement. The verifier MUST therefore classify the approval proof as exactly one of: anchored — the bundle's checkpoint (itself verified per steps 1 and 4) covers the approval entry's sequence AND the carried inclusion proof recomputes to that checkpoint's root; only then is the approval PROVEN, and only then may the step's other verdicts (presence, binding, linkage, attribution, version) be reported as established. unanchored — a statement is present but no verified checkpoint covers it (no checkpoint in the package, or one covering neither the receipt nor the approval): the verifier MUST report every approval verdict as unproven (warning level), never as established — a genuine unanchored statement and a fabricated one are indistinguishable, and the verdict must give them the same profile. absent — no statement is carried. Two shapes are FAILURES, not unanchored: when the checkpoint covers the approval entry's sequence, a missing or non-verifying inclusion proof MUST fail; and a claimed approval sequence beyond the tree size of a checkpoint that covers the RECEIPT MUST fail — a genuine approval always precedes its receipt in the chain, so that shape cannot occur in an honest package. The receipt's own authenticity (step 10) does not depend on the approval's anchor: the receipt is dual-signed, the approval is not.
  7. Scope verdicts (two, reported distinctly): re-apply the §5.2 scope rules (agent, currency, positive amount, category, per-purchase cap — NOT the period cap, which needs live usage and is not judged offline) to the receipt's action, (a) under the CITED version's terms and (b) under the SEAL-TIME version's terms. For (b): if mandate_version_at_seal equals the cited version, the cited terms are the seal-time terms. If it differs, a bundle_version ≥ 2 package MUST carry the seal-time terms in mandate_at_seal, matching (mandate_id, mandate_version_at_seal) — absence or mismatch is a FAILURE; a version-1 package MUST warn instead. If the receipt carries no mandate_version_at_seal (sealed before the field existed), the verifier MUST warn that the seal-time version is not inspectable and MUST NOT fabricate verdict (b). TERMS ANCHORING: a version statement carries no signature of its own, so — under the same rule as step 6 — the verifier MUST classify the terms proof as exactly one of: anchored — for every version a verdict rests on, the bundle's checkpoint (itself verified per steps 1 and 4) covers the statement's entry AND the carried inclusion proof recomputes to that checkpoint's root; only then are the terms PROVEN and the verdicts established. unanchored — statements are carried but no verified checkpoint covers them: the verdicts MUST be reported as unproven (warning level), never as established — a genuine unanchored statement and a fabricated one are indistinguishable. absent — the package's format carries no statements (bundle_version 1–2): the verdicts judge the terms as carried and the report MUST state that the terms are unbound. Two shapes are FAILURES, not unanchored: when the checkpoint covers a statement's entry, a missing or non-verifying inclusion proof MUST fail; and a claimed statement position beyond the tree size of a checkpoint that covers the RECEIPT MUST fail — a version statement always precedes the receipts that cite its version, so that shape cannot occur in an honest package.
  8. Anchor: if an anchor block is present, verify it per §9, including the overclaim rule. Absence is a warning.
  9. Time attestation: if present and trusted, verify per §7 against SHA-256(action_hash as UTF-8). A trusted attestation with no token is a warning; an untrusted one is a warning that names it.
  10. Report: authenticity = verified iff no step failed. Currency is NOT established offline: the report MUST state "currency: not checked — offline". (The operator's online verify additionally reports the mandate's live status; a receipt may be authentic while its mandate is now paused or revoked.) The report MUST also state the step-6 approval proof status (anchored / unanchored / absent) distinctly from authenticity; when a receipt required approval and the proof is not anchored, the report's headline MUST carry that gap — an unqualified "OK" over an unproven approval misstates the evidence. The report MUST likewise state the step-7 terms proof status (anchored / unanchored / absent) distinctly; when scope verdicts were reported and the terms proof is not anchored, the headline MUST carry that gap too.

A full-chain audit additionally verifies: sequence contiguity, linkage of every entry, every checkpoint root over its window, and — the anti-forgery rule — that every receipt entry's payload is byte-identical to a validly dual-signed envelope: a correctly-hashed but unsigned entry is a forgery, however well it links.

11. Versioning and openness