affaan-m/ECC · error · ContractError
receipt SHA-256 does not match its canonical content
Error message
receipt SHA-256 does not match its canonical content
What it means
The receipt itself is self-verifying: receipt_sha256 must equal the SHA-256 of the receipt's canonical JSON (all other keys, sorted keys, compact separators). This error is raised when the claimed digest does not match that recomputation, meaning the receipt's content was altered after signing or the digest was computed non-canonically.
Solutions
- Recompute the digest over the receipt minus receipt_sha256 using json.dumps(payload, sort_keys=True, separators=(',',':')).encode('utf-8'), hash with sha256, and write the hex digest back
- Regenerate the receipt via the tasteforge pipeline so signing is canonical
- If you edited receipt content intentionally, re-sign after every change — never patch content and digest independently
- Avoid reformatting/pretty-printing receipt files through external JSON tools without re-signing
Example fix
import hashlib, json
# before: digest stale after editing receipt fields
receipt['receipt_sha256'] = 'old...'
# after
payload = dict(receipt); payload.pop('receipt_sha256', None)
receipt['receipt_sha256'] = hashlib.sha256(
json.dumps(payload, sort_keys=True, separators=(',', ':')).encode('utf-8')
).hexdigest() Defensive patterns
Strategy: validation
Validate before calling
import hashlib, json
payload = dict(receipt); payload.pop('receipt_sha256', None)
actual = hashlib.sha256(json.dumps(payload, sort_keys=True, separators=(',', ':')).encode('utf-8')).hexdigest()
assert receipt.get('receipt_sha256') == actual Type guard
def receipt_digest_is_current(receipt: dict) -> bool:
payload = {k: v for k, v in receipt.items() if k != 'receipt_sha256'}
actual = hashlib.sha256(json.dumps(payload, sort_keys=True, separators=(',', ':')).encode('utf-8')).hexdigest()
return receipt.get('receipt_sha256') == actual Try / catch
try:
validate_artifact_receipt(out_dir)
except ContractError as e:
if 'receipt SHA-256 does not match' in str(e):
re_sign_receipt(receipt) # recompute canonical digest over full payload
else:
raise Prevention
- Re-sign the receipt after every content change — content and digest together
- Always sign with sort_keys=True and compact separators; no pretty-printed digests
- Sign as the last step of receipt generation, after all fields are final
- Never round-trip receipts through reformatting tools without re-signing
When it happens
Trigger: Editing any receipt field (artifacts, provenance, durations) after the digest was written; computing the digest with json.dumps defaults (spaces, insertion order) instead of sort_keys=True with separators=(',',':'); round-tripping the receipt through a tool that reorders or reformats content.
Common situations: Hand-editing receipts to fix a field without re-signing, scripts that pretty-print JSON then reuse the old digest, keys added/removed by schema migrations, digest computed over a Python dict before a key pop of receipt_sha256.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- artifact SHA-256 does not match receipt
- anchor evidence source duration is not bound to its receipt…
- application bundle differs from its bound evidence
- approved text hash does not match
- artifact has invalid reference SHA-256
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/90d77b7b17b7cb6b.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/contract.py:441
if basis == "whole_file" and times:
raise ContractError(f"artifact {relative} whole-file provenance must not invent times")
if basis == "media_seconds":
expected_duration = source_durations.get((source["reference_path"], digest))
if expected_duration is None or source.get("source_duration") != expected_duration:
raise ContractError(f"artifact {relative} has an unbound source duration")
for time in times:
_validate_media_time(
time, expected_duration,
label=f"artifact {relative} media reference time",
)
digest_payload = dict(receipt)
claimed_digest = digest_payload.pop("receipt_sha256", None)
actual_digest = hashlib.sha256(
json.dumps(digest_payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
).hexdigest()
if claimed_digest != actual_digest:
raise ContractError("receipt SHA-256 does not match its canonical content")
def validate_bundle(out_dir: str | Path) -> None:
"""Validate required multimodal files and cross-artifact invariants."""
out_dir = Path(out_dir)
_validate_output_tree(out_dir)
specs = [json.loads(path.read_text(encoding="utf-8"))
for path in sorted((out_dir / "genres").glob("*.json"))]
validate_genre_specs(specs)
validate_manifests(out_dir / "manifests")
provenance_path = out_dir / "provenance.json"
if not provenance_path.is_file():
raise ContractError("missing provenance")
validate_provenance(json.loads(provenance_path.read_text(encoding="utf-8")))
receipt_path = out_dir / "receipt.json"
if not receipt_path.is_file():View on GitHub (pinned to 8321021c54)