Sealgraf Open the app

Sealgraf Receipt Format — Specification v1

Status: OPEN EVIDENCE FORMAT. This specification defines the portable evidence format and the procedure for independently verifying that evidence. By the founder's decision (2026-10-29, refined 2026-10-31), anyone may implement compatible verifiers and archives against this document without permission, licence, or a relationship with the operator. This document does NOT grant producer conformance: whether third parties may produce evidence in this format is a separate decision, not made here. For verification the document is complete: 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).


Implementation independence

This specification defines the externally observable evidence format and the procedures required to verify that evidence. It does not specify or disclose the internal architecture by which a producer evaluates policies, coordinates concurrent authorization requests, reserves capacity, persists state, manages transactions, performs retries, operates signing infrastructure, or guarantees availability. Conformance with this specification does not imply implementation equivalence with Signed Evidence Engine.

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 format'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.

The terms MAY also carry step_up_line_minor (integer minor units): the owner's step-up line — the amount at or above which an approval records step_up_required in its §5.3 auth block. The field is OPTIONAL and is omitted entirely when no line is set, so terms sealed before it existed never carry it. When present it MUST be an integer ≥ 0 or null; a verifier MUST reject any other shape (non-integer, negative, boolean) as a malformed terms object, and MUST NOT judge the value itself — the line is a recorded slip fact, like the caps, not a verdict input beyond the scope rules that cite it.

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"
}

The decision object records the outcome of the check: an outcome (allow / deny / needs_human_approval), a reason_code, and the mandate version the check was judged under. 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). The codes are recorded data — the vocabulary a verifier reads from the statement, including outcomes it does not itself recompute.

Some of the rules behind a decision are re-applied by the verifier from the bundle itself (§10 step 7). These scope rules compare the action against the cited mandate terms: agent match, currency match, positive amount (in integer minor units, §2.1), category allowed, and the per-purchase cap. A verifier re-applies exactly these — under the cited terms and under the seal-time terms — and a receipt whose action violates one of them under the terms it cites fails verification.

The remaining outcomes are recorded, not recomputed. Whether the mandate was active and inside its validity window at the moment of the check, whether the spend fit the mandate's period cap, and whether it crossed the human-approval threshold all turn on the state of the ledger at decision time — state a bundle does not carry — so this document does not state how a producer evaluates them (see Implementation independence). The sealed decision statement records the outcome; the verifier checks the recorded decision's consistency with the cited mandate terms wherever the bundle carries the inputs, and does not recompute period usage. Where the recorded outcome is needs_human_approval, the verifier establishes from the bundle that the approval itself exists and binds to this spend (§10 step 6).

5.3 Human approval

Current form (schema_version: 4): the version-3 shape plus an optional auth block recording how the approver authenticated (see "Auth freshness (schema_version 4)" below):

{"schema_version": 4, "tenant_id": "…", "preauth_id": "pa_…",
 "mandate_id": "mnd_…", "mandate_version": 3,
 "action_hash": "<hex>", "approved_at": "…",
 "approver": {"kind": "account", "id": "acc_…", "label": "owner"},
 "auth": {"method": "session", "session_age_seconds": 173214,
          "step_up_required": false, "step_up_satisfied": false}}

Form sealed before freshness recording (schema_version: 3):

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

Historical form (schema_version: 2, sealed before de-identification): the same shape, but label is free text — for an account, the owner's email address; for a key, the key's label. These statements remain valid history and verifiers MUST accept them. They are also why the privacy policy keeps an honest exception for names written inside records sealed before the de-identified form existed: a sealed version-2 approval cannot be edited without breaking the record it is part of.

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.

Auth freshness (schema_version 4)

A version-4 approval statement MAY carry an auth block — the record of how the approver authenticated at the moment of the approval. The block is part of the sealed predicate bytes: adding, removing, or altering it after sealing breaks the statement's chain linkage exactly like any other mutation. Its fields are a closed set:

Recorded, not judged. Freshness is data inside the sealed record, and verifiers treat it the way they treat period-cap outcomes: they DISPLAY it and apply NO verdict of their own. A verifier MUST NOT fail, warn-escalate, or otherwise change any verdict because of a freshness VALUE — a session age of ten years verifies identically to a session age of ten seconds, and an unsatisfied step-up verifies identically to a satisfied one. Whether a recorded freshness is good enough is the reader's policy, not the format's. Two shape rules do bind verifiers:

The auth field belongs to schema_version 4 only: a version 1–3 statement carrying one is outside its closed schema and MUST be refused like any other unknown predicate field. Versions 1–3 remain valid history with no flag day — producers never rewrite sealed records, verifiers accept {1, 2, 3, 4}, and a bundle's approval proof (anchored / unanchored / absent, §10 step 6) is computed exactly as before, freshness or none.

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 appear as aborts: a transient refusal — a check declining because the mandate is paused, out of scope, or over cap — changes no state and never appears as an abort statement.

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 and 3 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 3 statements the approver's label MUST be exactly the §5.3 role string for its kind (any other label is a FAILURE — the de-identified form is a property of the format, not of the producer's habits); for schema_version 1 statements the verifier MUST warn that the approver cannot be distinguished further. For schema_version 4 statements the verifier MUST additionally apply the §5.3 auth-freshness rules: validate the SHAPE of a present auth block (a malformed block is a FAILURE of that statement), report an absent one as "not recorded" (never a failure), display the recorded facts, and apply NO verdict to freshness values — the approval proof classification below is computed exactly as for earlier versions, freshness or none. 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 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