{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://excellent.so/receipt-schema.json",
  "title": "Excellent receipt pack",
  "description": "The offline receipt pack Excellent writes when a verification run finishes, and the exact document `excellent receipt verify <pack.json> --pubkey <key.pem>` reads. A pack is self-contained: it carries the signed receipt, the binding that says what the receipt is about, the trust root that names the signing key, and the ledger position the run occupied. Signing is Ed25519 over the payload hash; HMAC-SHA256 appears only on receipts minted before the switch, which are readable but never authoritative. The signature does not cover the bytes of this file. It covers a canonical rendering of a fixed field list — see `$defs.envelope.properties.payloadHash` — so reformatting a pack does not invalidate it and changing its content does. Derived from packages/core/src/verification in the Excellent source tree; every field below exists there.",
  "type": "object",
  "required": ["schemaVersion", "generatedAt", "envelope", "trustRoot", "ledgerAnchor", "ledger", "auditLog", "legacyReadOnly"],
  "additionalProperties": true,
  "properties": {
    "schemaVersion": {
      "const": 1,
      "description": "Pack format version. Only 1 exists. A verifier that does not recognise this value must refuse the pack rather than guess."
    },
    "generatedAt": {
      "type": "string",
      "format": "date-time",
      "description": "When this pack was assembled for export. Not when the receipt was issued — that is `envelope.receipt.issuedAt` — and not signed."
    },
    "envelope": { "$ref": "#/$defs/envelope" },
    "trustRoot": { "$ref": "#/$defs/trustRoot" },
    "ledgerAnchor": {
      "oneOf": [{ "$ref": "#/$defs/chainAnchor" }, { "type": "null" }],
      "description": "A commitment to the ledger's contents recorded somewhere outside the ledger. Null means the chain is unanchored, and an unanchored hash chain proves only that it agrees with itself: anyone who edits an entry and regenerates the chain produces one that verifies perfectly."
    },
    "ledger": {
      "type": "array",
      "items": { "$ref": "#/$defs/chainEntry" },
      "description": "The hash-chained verification ledger entries this pack covers, in order. The anchor commits to their hashes."
    },
    "auditLog": {
      "type": "array",
      "items": { "$ref": "#/$defs/chainEntry" },
      "description": "The hash-chained record of who touched the evidence, in order. Anchored together with the ledger: evidence and the record of who handled it are committed to as one."
    },
    "legacyReadOnly": {
      "type": "boolean",
      "description": "True marks a pack kept for historical reading only — typically an older HMAC-signed receipt, or one with no binding. Such a pack can still be checked for tamper-evidence but is never authoritative. False asserts the pack is a current v2 decision receipt, and the verifier then requires Ed25519 and a binding before it will look at anything else."
    }
  },
  "$defs": {
    "verificationId": {
      "type": "string",
      "minLength": 1,
      "maxLength": 160,
      "description": "An opaque identifier. Excellent does not constrain its shape beyond length."
    },
    "isoTimestamp": {
      "type": "string",
      "format": "date-time",
      "description": "RFC 3339 timestamp. An offset is required — a bare local time is rejected."
    },
    "status": {
      "enum": ["PASS", "MISMATCH", "FAIL", "UNKNOWN", "NOT_RUN", "PENDING"],
      "description": "The six verification states. PASS: the checks agreed the claim holds. MISMATCH: a check found the claim contradicted. FAIL: a check failed. UNKNOWN: nothing could be concluded. NOT_RUN: the check did not execute. PENDING: not yet resolved. A receipt's status is the AGGREGATE over its checks, which is why `decision` exists separately — see below."
    },
    "signature": {
      "type": "object",
      "required": ["keyId", "algorithm", "value"],
      "additionalProperties": false,
      "description": "Who signed, with what, and the signature itself.",
      "properties": {
        "keyId": {
          "type": "string",
          "minLength": 1,
          "description": "Names the signing key. It is an assertion by the pack, not proof: the key must be matched against a fingerprint obtained from somewhere this pack does not control."
        },
        "algorithm": {
          "enum": ["ed25519", "hmac-sha256"],
          "description": "`ed25519` is the current and only authoritative algorithm. `hmac-sha256` is a shared secret, so a receipt verifying under it proves the holder of that secret signed it — and Excellent holds it too. Such receipts are legacy read-only records; a pack cannot upgrade an HMAC key into an Ed25519 key by declaring it one."
        },
        "value": {
          "type": "string",
          "minLength": 1,
          "description": "For Ed25519: `ed25519:` followed by the base64url signature over the ASCII bytes of the `payloadHash` STRING, prefix included — not over the raw digest bytes, and not over the receipt JSON. For HMAC: `hmac-sha256:` followed by the hex MAC over the same string."
        }
      }
    },
    "decision": {
      "type": "object",
      "required": ["decided", "decidedBy", "predicateStatus", "undecidedReasonCode"],
      "additionalProperties": false,
      "description": "Whether the run decided anything about the claim, as distinct from how the evidence was handled. This field exists because `status` cannot separate the two: a run whose predicate was evaluated and came back false, and a run where nothing evaluated the predicate at all but one evidence artifact was unregistered, both aggregate to FAIL. Optional in the format and required at mint. Absence means 'this receipt does not disclose' — receipts signed before the field existed are append-only and cannot be retrofitted — and must never be read as 'undecided'.",
      "properties": {
        "decided": {
          "type": "boolean",
          "description": "True only when some check reached a status that DETERMINES the claim (PASS, FAIL or MISMATCH) through a named evaluator. False means the receipt's status is a statement about evidence handling, not about the claim."
        },
        "decidedBy": {
          "type": ["string", "null"],
          "minLength": 1,
          "description": "`ref@version` of the evaluator that decided, or null when none did."
        },
        "predicateStatus": {
          "oneOf": [{ "$ref": "#/$defs/status" }, { "type": "null" }],
          "description": "The predicate check's own status, unfolded from the aggregate. Null when the run carried no predicate check at all — the fact an auditor most needs and the aggregate most thoroughly hides."
        },
        "undecidedReasonCode": {
          "type": ["string", "null"],
          "minLength": 1,
          "description": "Why nothing decided, in the check's own reason code. Null when something did."
        }
      }
    },
    "receipt": {
      "type": "object",
      "required": ["schemaVersion", "id", "tenantId", "runId", "parentReceiptId", "status", "checkResultIds", "evidenceHashes", "policyVersionRefs", "engineVersion", "issuedAt", "signature"],
      "additionalProperties": true,
      "description": "The verdict itself. Keys not listed here are dropped when the receipt is parsed, so they are neither read nor covered by the signature — do not put meaning in them.",
      "properties": {
        "schemaVersion": { "const": 1, "description": "Receipt format version." },
        "id": { "$ref": "#/$defs/verificationId", "description": "This receipt's identifier." },
        "tenantId": { "$ref": "#/$defs/verificationId", "description": "The workspace the run belonged to." },
        "runId": { "$ref": "#/$defs/verificationId", "description": "The verification run this receipt reports on." },
        "parentReceiptId": {
          "oneOf": [{ "$ref": "#/$defs/verificationId" }, { "type": "null" }],
          "description": "The receipt this one supersedes, on a re-verification. Null on a first issue. Receipts are append-only: a verdict is never edited, it is superseded."
        },
        "status": { "$ref": "#/$defs/status" },
        "decision": { "$ref": "#/$defs/decision" },
        "checkResultIds": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/verificationId" },
          "description": "Every check folded into `status`, named by id only. The receipt says WHICH checks ran, never what any one of them concluded — that is what `decision` is for."
        },
        "evidenceHashes": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 8 },
          "description": "Content hashes of the evidence those checks read, so the evidence can be matched later without the receipt carrying it. Hashes, not the evidence itself: a receipt never ships a customer's files."
        },
        "policyVersionRefs": {
          "type": "array",
          "minItems": 1,
          "items": { "type": "string", "minLength": 1 },
          "description": "The exact policy versions the checks were judged under. If `binding.subject` is present, its `policyDigest` must appear in this list or the envelope is rejected."
        },
        "engineVersion": {
          "type": "string",
          "minLength": 1,
          "description": "The verification engine build that produced the verdict."
        },
        "issuedAt": { "$ref": "#/$defs/isoTimestamp", "description": "When the receipt was minted." },
        "signature": {
          "oneOf": [{ "$ref": "#/$defs/signature" }, { "type": "null" }],
          "description": "A copy of `envelope.signature`. Null only before the receipt is enveloped; inside a pack it must be present and must match the envelope's copy exactly, or the pack is rejected as `receipt-signature-mismatch`."
        }
      }
    },
    "subject": {
      "type": "object",
      "required": ["treeHash", "commitSha", "buildId", "imageDigest", "tenantId", "environmentId", "policyDigest"],
      "additionalProperties": false,
      "description": "What artifact the verdict is about. Every field is required and none may be blank, or the envelope is rejected as `subject-binding-mismatch`.",
      "properties": {
        "treeHash": { "type": "string", "description": "Hash of the source tree that was checked." },
        "commitSha": { "type": "string", "description": "The commit the tree came from." },
        "buildId": { "type": "string", "description": "The build that produced the artifact." },
        "imageDigest": { "type": "string", "description": "Digest of the built image or artifact." },
        "tenantId": { "type": "string", "description": "Must equal the receipt's `tenantId`." },
        "environmentId": { "type": "string", "description": "Where the run executed." },
        "policyDigest": { "type": "string", "description": "Must appear in the receipt's `policyVersionRefs`." }
      }
    },
    "binding": {
      "type": "object",
      "required": ["workItemId", "workSnapshotId", "attemptId", "contractId", "evidenceSnapshotId", "evaluatorRefs", "determinationId", "enforcementDecisionId", "reviewDecisionIds", "buildRef"],
      "additionalProperties": true,
      "description": "What the verdict is ABOUT. Without it a PASS is a verdict about nothing in particular, and an authoritative pack that omits it is rejected outright. Unlike the receipt, the binding is not filtered on parse: any extra keys you find here ARE covered by the signature and were part of what was signed.",
      "properties": {
        "predicate": {
          "const": "excellent.verification.agent-work-receipt.v1",
          "description": "What kind of statement this is. Defaults to this value when absent, and is written into the signed payload either way."
        },
        "workItemId": { "type": "string", "description": "The unit of work — the task the agent was given." },
        "workSnapshotId": { "type": "string", "description": "The frozen state of that work at the moment it was checked." },
        "attemptId": { "type": "string", "description": "Which attempt at the work this was. Agents retry; each attempt is its own record." },
        "runManifestId": { "type": "string", "description": "Optional. The manifest describing how the run was planned." },
        "contractId": { "type": "string", "description": "The verification contract — the agreement about what would have to be true for this work to count as done." },
        "evidenceSnapshotId": { "type": "string", "description": "The frozen set of evidence the checks read." },
        "evidenceGraphDigest": { "type": "string", "description": "Optional. Digest over the evidence graph — how the evidence artifacts relate to each other." },
        "obligationResultIds": { "type": "array", "items": { "type": "string" }, "description": "Optional. The contract obligations and how each came out. Absent and empty are indistinguishable in the signed payload: both canonicalize to []." },
        "evaluatorRefs": { "type": "array", "items": { "type": "string" }, "description": "Which evaluators judged the claim. Sorted before signing." },
        "determinationId": { "type": "string", "description": "The determination record — the formal conclusion drawn from the checks." },
        "validityStateId": { "type": "string", "description": "Optional. Whether that determination is still considered current." },
        "enforcementDecisionId": { "type": "string", "description": "What was decided to DO about the verdict — promote, block, hold." },
        "enforcementEffectIds": { "type": "array", "items": { "type": "string" }, "description": "Optional. What that decision actually caused. Sorted before signing; absent canonicalizes to []." },
        "reviewDecisionIds": { "type": "array", "items": { "type": "string" }, "description": "Human or automated review decisions applied to the verdict. Sorted before signing." },
        "outcomeObservationIds": { "type": "array", "items": { "type": "string" }, "description": "Optional. What was observed afterwards — whether the promoted change held up. Sorted before signing; absent canonicalizes to []." },
        "auditEventIds": { "type": "array", "items": { "type": "string" }, "description": "Optional. Audit events raised during the run. Sorted before signing; absent canonicalizes to []." },
        "buildRef": { "type": "string", "description": "The build the verification engine itself was running." },
        "subject": { "$ref": "#/$defs/subject" }
      }
    },
    "envelope": {
      "type": "object",
      "required": ["schemaVersion", "serviceVersion", "receipt", "binding", "payloadHash", "signature", "publicVerification"],
      "additionalProperties": false,
      "description": "The signed unit: a receipt, what it is about, the hash over both, and the signature over that hash.",
      "properties": {
        "schemaVersion": { "const": 1, "description": "Envelope format version." },
        "serviceVersion": {
          "type": "string",
          "description": "The receipt service build that minted the envelope. `2026-08-16.v1` at the time this schema was published."
        },
        "receipt": { "$ref": "#/$defs/receipt" },
        "binding": {
          "oneOf": [{ "$ref": "#/$defs/binding" }, { "type": "null" }],
          "description": "Null is permitted only on a `legacyReadOnly` pack."
        },
        "payloadHash": {
          "type": "string",
          "pattern": "^sha256:[0-9a-f]{64}$",
          "description": "`sha256:` plus the hex digest of the CANONICAL payload — not of this file. The canonical payload is a fixed field list, rendered with object keys sorted, arrays of ids sorted, no whitespace, and absent optional keys omitted rather than nulled. It contains, and only contains: predicate, schemaVersion, id, tenantId, runId, parentReceiptId, status, checkResultIds, evidenceHashes, policyVersionRefs, engineVersion, issuedAt, binding, and decision when the receipt carries one. Anything else in the file — including everything in `publicVerification` — is outside the signature and can be edited without breaking it, which is why a verifier re-derives those facts from the receipt rather than reading them here."
        },
        "signature": { "$ref": "#/$defs/signature" },
        "publicVerification": {
          "type": "object",
          "required": ["algorithm", "payloadHash", "signatureAlgorithm", "keyId", "receiptId", "runId", "tenantId", "issuedAt", "parentReceiptId"],
          "additionalProperties": false,
          "description": "A convenience restatement of what a third party needs in order to check the signature without parsing the receipt. It is NOT signed. A verifier must treat it as a claim to be cross-checked against the receipt, and reject the envelope when the two disagree (`public-metadata-mismatch`).",
          "properties": {
            "algorithm": { "const": "sha256", "description": "The hash algorithm behind `payloadHash`." },
            "payloadHash": { "type": "string", "description": "Must equal `envelope.payloadHash`." },
            "signatureAlgorithm": { "enum": ["ed25519", "hmac-sha256"], "description": "Must equal `envelope.signature.algorithm`." },
            "keyId": { "type": "string", "description": "Must equal `envelope.signature.keyId`." },
            "receiptId": { "type": "string", "description": "Must equal `envelope.receipt.id`." },
            "runId": { "type": "string", "description": "Must equal `envelope.receipt.runId`." },
            "tenantId": { "type": "string", "description": "Must equal `envelope.receipt.tenantId`." },
            "issuedAt": {
              "$ref": "#/$defs/isoTimestamp",
              "description": "Copied from `envelope.receipt.issuedAt` at mint. Worth knowing: this is the one field of `publicVerification` the envelope verifier does not currently cross-check, so read the date off the receipt, not off here."
            },
            "parentReceiptId": {
              "type": ["string", "null"],
              "description": "Must equal `envelope.receipt.parentReceiptId`, null included. An envelope that omits the key entirely fails the cross-check."
            }
          }
        }
      }
    },
    "trustRoot": {
      "type": "object",
      "required": ["tenantId", "generatedAt", "declaredKeys", "signedByKeyId"],
      "additionalProperties": false,
      "description": "The keys the pack says it was signed with. A pack cannot vouch for its own signing key, so this is a claim to be matched against a fingerprint you obtained elsewhere. With no such fingerprint a verifier may report the pack internally consistent, but not anchored.",
      "properties": {
        "tenantId": { "type": "string", "description": "The workspace the keys belong to." },
        "generatedAt": { "type": "string", "format": "date-time", "description": "When the trust material was exported." },
        "declaredKeys": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["keyId", "fingerprint"],
            "additionalProperties": false,
            "properties": {
              "keyId": { "type": "string", "description": "Identifier, matched against `signedByKeyId`." },
              "fingerprint": {
                "type": "string",
                "description": "`sha256:` plus the hex sha256 over the PUBLIC KEY FILE BYTES — the SPKI PEM text as written, header lines and trailing newline included. Hashing the decoded key instead gives a different value."
              },
              "algorithm": { "enum": ["ed25519", "hmac-sha256"], "description": "Optional. When present it must agree with `signatureAlgorithm` below; a disagreement rejects the pack." }
            }
          },
          "description": "Every key the pack declares. The signing key must be among them or the pack is rejected: an unidentifiable signing key is worse than a wrong one."
        },
        "signedByKeyId": { "type": "string", "description": "Which declared key covers this pack's receipts." },
        "signatureAlgorithm": { "enum": ["ed25519", "hmac-sha256"], "description": "Optional pack-level algorithm. Defaults to `hmac-sha256` when absent, which is the legacy reading." }
      }
    },
    "chainEntry": {
      "type": "object",
      "required": ["id", "entryHash"],
      "additionalProperties": false,
      "description": "One position in a hash chain.",
      "properties": {
        "id": { "type": "string", "description": "The entry's identifier." },
        "entryHash": { "type": "string", "description": "The entry's hash, which the anchor commits to." }
      }
    },
    "chainAnchor": {
      "type": "object",
      "required": ["schemaVersion", "id", "target", "anchoredAt", "ledgerHead", "ledgerLength", "ledgerEntryHashes", "auditLogHead", "auditLogLength", "auditLogEntryHashes"],
      "additionalProperties": false,
      "description": "A commitment to the ledger's contents, recorded outside the ledger. It makes a regenerated chain detectable. It does NOT make a rollback detectable on its own: a genuine anchor from an earlier honest state, presented with the ledger rewound to match it, is internally flawless. Only an anchor id and time the reader holds independently distinguishes the two.",
      "properties": {
        "schemaVersion": { "const": 1, "description": "Anchor format version." },
        "id": {
          "type": "string",
          "description": "`sha256:` plus the hex digest over `JSON.stringify([target, anchoredAt, ledgerEntryHashes, auditLogEntryHashes])`. Recomputable, so an edited anchor is caught before its contents are believed."
        },
        "target": {
          "enum": ["customer-worm-bucket", "rfc3161-tsa", "operator-held-file"],
          "description": "Where the commitment was recorded. `customer-worm-bucket`: write-once storage the customer controls. `rfc3161-tsa`: an RFC 3161 timestamp authority. `operator-held-file`: a file the operator keeps — the weakest of the three, because the operator also produced the pack."
        },
        "anchoredAt": { "$ref": "#/$defs/isoTimestamp", "description": "When the commitment was recorded." },
        "ledgerHead": { "type": "string", "description": "Hash of the last ledger entry the anchor commits to, or `sha256:empty`." },
        "ledgerLength": { "type": "integer", "minimum": 0, "description": "How many ledger entries the anchor commits to. Fewer entries present than this means the tail was removed." },
        "ledgerEntryHashes": { "type": "array", "items": { "type": "string" }, "description": "Every committed ledger entry hash, in order. Compared position by position against `ledger`." },
        "auditLogHead": { "type": "string", "description": "Hash of the last audit-log entry the anchor commits to, or `sha256:empty`." },
        "auditLogLength": { "type": "integer", "minimum": 0, "description": "How many audit-log entries the anchor commits to." },
        "auditLogEntryHashes": { "type": "array", "items": { "type": "string" }, "description": "Every committed audit-log entry hash, in order. Compared position by position against `auditLog`." }
      }
    }
  }
}
