Zum Inhalt springen
Loslegen

Spec: Signed Import Receipt v1

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

A Signed Import Receipt is the portable proof of one import: which source produced it, every recorded decision along the way (with its actor), the outcome, and a signature that lets a third party verify all of that offline — no Mildport service, no call home, no account.

Schema: import-receipt.v1.schema.json · current version: receiptVersion: 1

A receipt travels as an envelope of three members:

{
"receipt": { "receiptVersion": 1, "...": "the plain receipt document" },
"signature": {
"protected": "<base64url JWS protected header>",
"signature": "<base64url JWS signature>"
},
"keyId": "<key id, duplicated from the header for grep-ability>"
}

The receipt member stays plain, readable JSON — an auditor reads the document; the signature travels alongside it. The receipt is redaction-safe by construction: source and deliveries are metadata, and ledger evidence was bounded and redacted when it was first recorded — a receipt is safe to email by default.

The exhaustive shape is the schema; the semantics of the main fields:

Field Semantics
receiptVersion Always 1 for this revision. Verifiers check it before anything else.
receiptId rcpt_ + URL-safe identifier — the receipt’s own identity.
recordId / tenantId Which import run, for which tenant.
source Source identity as metadata: detected format, external id, page count, per-sheet dimensions. Names and shapes only — never cell values.
decisions.questionCounts How many questions ended in each state.
decisions.mapping Each answered mapping question: header → column key, which actor answered, when.
decisions.references Each answered reference question: field, chosen target, affected rows, actor, when.
decisions.ledger The full audited question trail — every judgment call as a typed question with its evidence and resolution. The schema carries the complete shape.
outcome submit or discard, the row count, and the deliveries that resulted.
issuedAt When the receipt was issued.
calibration (optional) Identity of the signed calibration artifact that priced this import’s confidences, with its own key id — an auditor can demand and verify it independently.
certificate (optional) A statistical verification certificate — see below.

transforms, facts, validation, and changesetId are reserved: the schema owns their names and shapes now so consumers can rely on them later, but they are optional and may be absent.

When a stratified sample review completed, the receipt carries a certificate stating the sample design as part of the claim: per-stratum populations, recorded oversampling weights, sample sizes and defect counts, the selection seed (so an auditor can regenerate the exact sampled rows), and the exact Clopper–Pearson one-sided upper bound over the whole sample.

A conforming renderer never displays this as “verified”. The honest sentence — the only claim the certificate supports — is: “at most defectRateUpperBound defect rate, at confidence confidence.”

The signature does not cover the pretty-printed document — it covers the receipt’s canonical form. Any implementation must produce byte-identical canonical JSON:

  1. Objects: keys sorted lexicographically, recursively. Properties whose value is undefined (absent) are dropped — matching standard JSON.stringify semantics.
  2. Arrays: order preserved exactly.
  3. Scalars: standard JSON rendering (the output is JSON.stringify of the sorted structure, no added whitespace).
  4. Rejections, not coercions: non-finite numbers, circular references, and anything JSON cannot represent faithfully (functions, symbols, bigints) are errors. Objects must be plain — a value that would “roughly” serialize (a date object, a map) is rejected rather than silently flattened. A signed artifact is never approximately itself.

The scheme is a detached JWS (RFC 7515 flattened serialization) with the unencoded payload option (RFC 7797):

  • Protected header: { "alg": "ES256", "kid": "<key id>", "b64": false, "crit": ["b64"] }.
  • Payload: the UTF-8 bytes of the receipt’s canonical JSON — not base64url-encoded (that is what b64: false means), and not embedded in the envelope; verifiers recompute it from the receipt member.
  • Algorithm: ES256 (ECDSA over P-256, SHA-256), the same algorithm the product uses for license signing. The JWS signature is the raw 64-byte R || S concatenation, base64url encoded, per RFC 7518. Public keys are exchanged as SPKI PEM.
  1. Parse the envelope; reject unknown receiptVersion.
  2. Recompute the canonical JSON of the receipt member (rules above).
  3. Verify the flattened JWS { protected, signature } against the canonical bytes as the unencoded detached payload, accepting only alg: ES256.
  4. Compare the header’s kid with the envelope’s keyId, and check it identifies a key you trust.

A single flipped byte anywhere in the document fails step 3 — that is the point of recomputing the canonical form instead of trusting an embedded payload.

The public key comes from the operator of the instance that issued the receipt — the same trust anchoring as offline license verification. For receipts issued by Mildport’s managed cloud, the keys are published. Verification never phones home.

Two ready-made implementations of this procedure exist: verify in your browser (zero egress, on that page) or the MIT CLI.


This specification page and the linked schema are licensed CC BY 4.0 — see the spec overview for scope.