1. The Receipt Envelope
A Decision Receipt on the wire is exposed as{ context, result, chain_id, chain_step }. The cryptographic
centre of gravity is result (the EvaluationResult).
1.1 v1 vs v2 — which fields exist
Top-level (alongside the receipt, not inside
result): id (UUID),
tenant_id, actor (DecisionActor JSON), chain_id, chain_step,
parent_decision_id, created_at (recorded-at, distinct from
result.timestamp).
Migration semantics. v1 receipts continue to verify forever. There is no
cut-over date. Cross-version fallback is rejected — a v2 receipt must
verify under the v2 path; a v1 receipt must verify under the v1 path.
1.2 Decision Context (the input)
Type:DecisionContext.
context.evidence[] is the legacy reference-only evidence list. The richer
Evidence Manifest (§5) is a separate first-class concept and its digest is
stored on result, not context.
1.3 Source Artifact (context.source_artifact)
Type: SourceArtifact. Reference-based: stores
{ type, hash, hash_algorithm: 'SHA-256', reference_id?, filename?, byte_size? }.
Only hash is bound into integrity_hash. Type, filename and size are
convenience metadata and intentionally outside the hash.
This is the older single-artifact binding (“Trust Without Disclosure”). It
co-exists with the Evidence Manifest in v2; deprecation is out of scope.
1.4 Actor (the actor field)
Type: DecisionActor.
metadata.actor_id on context is bound into the integrity hash via
context_hash; the structured actor row is stored alongside but not
part of the integrity hash.
1.5 Action (result.action)
Type: ReceiptAction. Shape: { type, reference_id, metadata? }.
type and reference_id are bound into integrity_hash. metadata is
not.
2. Cryptographic Model
2.1 Canonicalisation profile
meshqu-canonical/v0 — frozen. Alphabetic key order, JS Array.sort. RFC 8785
alignment is a planned P1 follow-up that will introduce
meshqu-canonical/v1.
2.2 Integrity hash — exact payloads
v1 input —sha256(canonicalJson(payload)) where payload is, alphabetised:
transparency_anchor (computed after signing),
chain fields, evaluation_time_ms, rules_evaluated / rules_na, the
actor row, action.metadata, source artifact metadata.
2.3 Signing — what is signed
- v1 signs the bare 64-char hex
integrity_hashstring, UTF-8 encoded. - v2 signs the canonical envelope, alphabetised:
signature_kid, signature_algorithm and
receipt_schema_version into the signed bytes — closing the gap where v1
stored these alongside but not inside the signature. The verifier
reconstructs envelope bytes using the receipt’s stored signature_kid /
signature_algorithm (not hardcoded) so any tampering of those fields fails
the signature check.
Algorithm: Ed25519 only in v1+v2. Other algorithms require a manifest-version
bump.
2.4 Policy snapshot digest (v2)
computePolicySnapshotDigest(snapshot) =
sha256(canonicalJson({ id, rules, policy_versions, created_at })).
This binds the content of the policy, not just its UUID. Approval-receipt
digests live on policy_versions[i].approval_receipt_digest, so approval
lineage is bound transitively through this single digest — there is no
separate top-level approval digest on the receipt.
2.5 Evidence manifest digest (v2, optional)
sha256(canonicalJson(manifest minus manifest_digest)). Self-describing
pattern (mirrors the bundle manifest). MeshQu computes and verifies this
digest as part of v2 receipt and bundle handling.
2.6 Active-rule projection (used everywhere rules are hashed)
projectActiveRules strips inactive rules and all metadata, keeping only
{ code, condition, severity, when? }. This single helper is used by:
computeSnapshotHash(snapshot repo + evaluator)PolicyApprovalReceipt.policy_rules_hashPolicySnapshot.policy_versions[i].policy_rules_hash- the bundle verifier when comparing per-version hashes
3. Policy Snapshot
A frozen, immutable copy of the rules that governed a decision. Identifies the policy run by both UUID and content digest in v2.id(UUID, on the receipt aspolicy_snapshot_id)rules[]— fullPolicyRule[]. Bundled inpolicy_snapshot.json; not stored on the receipt itself.policy_versions[]— list of{ policy_id, version, policy_rules_hash, approval_receipt_digest? }created_at(ISO 8601)evaluated_rules_hashon the receipt =sha256(canonicalJson(projectActiveRules(rules)))policy_snapshot_digeston the receipt (v2) =sha256(canonicalJson({ id, rules, policy_versions, created_at }))
snapshot_replay.
4. Approval Receipts (lineage)
Status: Implemented end-to-end (APR-008 / APR-009 / APR-010, May 2026). Issuance, storage, bundle export, Node verifier and browser verifier all verify approval lineage. Pinned by golden fixtures, an 18-row mutation matrix and Node ↔ browser parity tests.- Policy approval receipts are a first-class artifact. Each receipt holds a
ratifier_signature(Ed25519, kid + algorithm),policy_rules_hash(viaprojectActiveRules) and a self-describingapproval_receipt_digest. - A bundle ships these in
policy_approval_receipts.json. - The verifier sub-claim
approval_lineageactivates when the receipt is v2 and the bundled snapshot has at least one non-nullpolicy_versions[i].approval_receipt_digest. For every such version the verifier checks:- The receipt is present in the bundled file (keyed by
policy_version_id). computeApprovalReceiptDigest(receipt)equals the snapshot entry’sapproval_receipt_digest.receipt.policy_rules_hashequalsentry.policy_rules_hashby string equality (do not re-hashsnapshot.rules— APR-006 precomputed per-version hashes that cover only that version’s rules, and the snapshot entry is itself bound bypolicy_snapshot_digest).receipt.ratifier_signature.kidresolves in the caller-suppliedratifierTrustedRoots.- The ratifier Ed25519 signature verifies.
- The receipt is present in the bundled file (keyed by
- Reports
not_applicablefor v1 receipts and v2 receipts whose snapshot has no non-nullapproval_receipt_digestentries. - The same approval-lineage checks run in both the Node verifier and the browser verifier, kept at parity (APR-009).
5. Evidence Manifest (v2 only)
Type:EvidenceManifest. Strictly additive — does not modify v1 receipts or
source_artifact.
EvidenceItem:
custodianTrustedRoots.
The manifest can carry a mix of signed and unsigned items. The manifest digest
covers all items; the per-item signature is an additional, independent
attestation by the custodian.
Reference-based — the artifact bytes are NOT stored. Only digests,
references, and metadata.
6. Transparency Anchor (Rekor)
Type:TransparencyAnchor. Stamped onto the receipt after signing, so it
is not part of integrity_hash.
- Anchoring. Implemented. Receipt is submitted to Rekor; anchor data captured.
- Offline SET verification. Implemented. The verifier reconstructs the
canonical message Rekor signed (
{body, integratedTime, logID, logIndex}) and checks the SET against a trusted Rekor public key. - Offline subject-digest binding. Implemented when
dsse_envelopeis present. The verifier decodes the DSSE envelope, reads the in-toto Statement and assertssubject.digest.sha256 == receipt.integrity_hash. - Inclusion-proof verification. Accepted as
transparency.inclusion_proof_unverifiedfor legacy anchors lacking the new fields; emitstransparency.inclusion_proof_invalidwhen present and broken.
- Online Rekor lookups (live witness fetching): Planned. Only offline SET verification today.
dsse_envelope / signed_entry_timestamp /
integrated_time / log_id) report transparency: not_applicable.
7. Decision Chains
A chain is an ordered group of receipts that together prove a workflow.7.1 Linkage on the receipt
ChainLinkage — top-level fields on the receipt:
chain_id(UUID, caller-provided)chain_step(1-based, auto-assigned if omitted)parent_decision_id(UUID, optional — explicit causal lineage)
7.2 Chain proofs
The chain-proof artifact (chain_proof.json in a bundle). Each non-first
step gets:
chain_integrity_hash= sha256 over{ chain_id, step, decision_integrity_hash, previous_chain_hash }chain_signature(Ed25519 over the chain integrity hash)
chain_integrity_hash changes.
7.3 Chain seal
The chain-seal artifact (chain_seal.json in a bundle):
seal_hash= sha256 over{ chain_id, sealed_at, steps_count, ALL receipt_hashes (ordered) }seal_signature(Ed25519 over seal hash)sealed_at(ISO 8601)
7.4 Verification result type
ChainVerificationResult exposes chain_proof: 'full' | 'partial' | 'none' | 'failed', per-step status, warnings (gap, parent_not_in_chain,
parent_step_not_lower, chain_id_mismatch, proof_boundary) and seal
status.
8. Decision Outcomes (post-decision tracking)
Separate from the receipt itself. A decision-outcome record carries:9. Audit Trail
Append-only, hash-chained. Each event carries:previous_hash- per-event
integrity_hash= sha256 over{ action, entity_type, entity_id, actor_id, tenant_id, changes, timestamp, previous_hash }
decision_recorded audit event is written on every record.
10. Verification Bundle
A self-describing tar / JSON archive that lets a third party verify a receipt offline. See also the standalone Verification Bundle page; this section focuses on the contract relevant to the receipt.10.1 Manifest (bundle_manifest.json)
10.2 Files in a bundle
trusted_keys.json is pinned at export time. Rotating signing keys does
not retroactively invalidate already-issued bundles.
10.3 Verifier sub-claims
Every relevant sub-claim is reported (not just the first failure). Each hasstatus: 'valid' | 'invalid' | 'not_applicable' and a
failure_codes: string[].
10.4 Failure codes (full list)
11. Public API Surface
The endpoints exposed by the MeshQu API:
Public receipt response shape:
{ context, result, chain_id, chain_step }.
The API normalises legacy policy_snapshot_hash → evaluated_rules_hash on
the way out.
12. Persistence Model (summary)
MeshQu persists each of these as a distinct artifact, surfaced through the REST API and verification bundles:- Receipts — immutable once written; hash, signature and timestamp fields cannot be updated or deleted after they are recorded.
- Chain proofs — per-step chain integrity hashes and signatures.
- Chain seals — a completeness seal per chain.
- Decision outcomes — post-decision status (override / escalate / etc).
- Policy snapshots — frozen policy bundles.
- Policy approval receipts — ratifier-signed approval records.
- Audit events — an append-only, hash-chained audit log.
13. Key Management
- Signing key kid format:
msk_v{N}(e.g.msk_v1). - Algorithm: Ed25519 only.
- Key rotation does not retroactively invalidate bundles — each bundle
pins its trusted keys at export time in
trusted_keys.json. - The /verify demo uses bundled
msk_demo_v1; production verification usestrusted_keys.jsonfrom the bundle. - Custodian (evidence) keys and ratifier (approval) keys are supplied to the
verifier out-of-band via
custodianTrustedRoots/ratifierTrustedRoots. MeshQu never holds them.
14. What Is and Isn’t Bound — quick reference
Bound into v1integrity_hash: decision, violations, context (minus
source_artifact metadata), source_artifact.hash, action.{type, reference_id}, policy_snapshot_id, evaluated_rules_hash, timestamp.
Additionally bound into v2 integrity_hash: receipt_schema_version,
policy_snapshot_digest, evidence_manifest_digest (or null).
Additionally bound into the v2 signed envelope (not the integrity hash):
signature_kid, signature_algorithm, timestamp.
Stored on the receipt but NOT hashed: transparency_anchor, chain
linkage, evaluation_time_ms, rules_evaluated, rules_na, na_rules,
action.metadata, source artifact metadata (type / filename / byte_size),
the structured actor row, decision outcomes.
Bound transitively via policy_snapshot_digest: all
policy_versions[i].approval_receipt_digest entries (when present), all
policy_versions[i].policy_rules_hash entries — therefore approval lineage.
15. Status Matrix
16. Glossary — safe public terms (and what to avoid)
17. The two highest-risk public-copy traps
-
Implying evidence content custody. The product is reference-based. Any
public surface that says “MeshQu stores the policy, context, evidence and
outcome” overshoots. Use evidence references / evidence digests. The
single artefact on the receipt that binds content (
source_artifact.hash) binds only the hash — type, filename and size are not bound. - Implying live transparency-log verification. Anchoring to Rekor and offline SET verification are real. Online inclusion-proof lookup against a live witness is not implemented yet. Don’t say “checked against the live transparency log” or “live witness verification”. “Anchored to a public transparency log; verifiable offline against a pinned log key” is accurate.