affaan-m/ECC · error · ContractError

artifact SHA-256 does not match receipt

Error message

artifact {relative} SHA-256 does not match receipt

What it means

tasteforge's validate_artifact_receipt verifies each emitted artifact against the receipt manifest. This error means the SHA-256 recorded in the receipt for an artifact does not match the actual bytes on disk, so the artifact was modified after the receipt was generated. The library treats this as proof of tampering or a stale receipt and refuses the bundle.

Solutions

  1. Regenerate the artifact and the receipt together using the tasteforge pipeline so the digest is recomputed from the final bytes
  2. Recompute the correct SHA-256 (hashlib.sha256 of the file bytes) and update the receipt entry only if you intentionally changed the artifact and provenance still holds
  3. Restore the artifact to its original bytes (e.g. from git) so it matches the receipt
  4. Run validate_bundle in a clean checkout to confirm which side (artifact or receipt) drifted

Example fix

import hashlib
# before: receipt entry stale after artifact was touched
receipt['artifacts']['cover.png']['sha256'] = 'abc123...'
# after: recompute digest from actual file bytes
receipt['artifacts']['cover.png']['sha256'] = hashlib.sha256(
    Path('out/cover.png').read_bytes()
).hexdigest()
Defensive patterns

Strategy: validation

Validate before calling

import hashlib, pathlib
assert receipt_entry['sha256'] == hashlib.sha256(pathlib.Path(artifact_path).read_bytes()).hexdigest()

Type guard

def digest_matches(path, expected: str) -> bool:
    return hashlib.sha256(pathlib.Path(path).read_bytes()).hexdigest() == expected

Try / catch

try:
    validate_artifact_receipt(out_dir)
except ContractError as e:
    if 'SHA-256 does not match' in str(e):
        regenerate_artifacts_and_receipt(out_dir)
    else:
        raise

Prevention

When it happens

Trigger: Calling validate_artifact_receipt (directly or via validate_bundle) when receipt['artifacts'][name]['sha256'] differs from _sha256(path) — e.g. the artifact file was edited, reformatted, or re-saved after receipt creation, or the receipt was regenerated with different content while the old file remains.

Common situations: Hand-editing a generated image/audio artifact, running an editor or image optimizer that rewrites files post-build, copying artifacts between machines with byte-altering conversion, regenerating artifacts without refreshing the receipt, or editing a receipt entry by hand without recomputing the digest.

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


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/ee8a02630d1e4cb5. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/contract.py:405

        relative = entry.get("path")
        assert isinstance(relative, str)
        path = (out_dir / relative).resolve()
        try:
            path.relative_to(out_dir)
        except ValueError as error:
            raise ContractError(f"artifact path escapes output directory: {relative}") from error
        if entry.get("provider_execution") is not False:
            raise ContractError(f"artifact {relative} permits provider execution")
        if not isinstance(entry.get("genre_numbers"), list):
            raise ContractError(f"artifact {relative} lacks genre binding")
        modalities = entry.get("modalities")
        if (not isinstance(modalities, list)
                or any(modality not in _REQUIRED_MODALITIES for modality in modalities)):
            raise ContractError(f"artifact {relative} has invalid modality binding")
        if entry.get("bytes") != path.stat().st_size:
            raise ContractError(f"artifact {relative} byte size does not match receipt")
        if entry.get("sha256") != _sha256(path):
            raise ContractError(f"artifact {relative} SHA-256 does not match receipt")
        provenance = entry.get("provenance")
        if not isinstance(provenance, list) or not provenance:
            raise ContractError(f"artifact {relative} lacks exact reference/time provenance")
        for source in provenance:
            if not isinstance(source.get("reference_path"), str) or not source["reference_path"]:
                raise ContractError(f"artifact {relative} has invalid reference path")
            digest = source.get("reference_sha256")
            if not isinstance(digest, str) or len(digest) != 64:
                raise ContractError(f"artifact {relative} has invalid reference SHA-256")
            if (source["reference_path"], digest) not in known_sources:
                raise ContractError(f"artifact {relative} cites an unknown provenance source")
            times = source.get("reference_times")
            basis = source.get("time_basis")
            if not isinstance(times, list) or basis not in {"media_seconds", "whole_file"}:
                raise ContractError(f"artifact {relative} has invalid reference/time provenance")
            if basis == "media_seconds" and not times:
                raise ContractError(f"artifact {relative} lacks media reference times")
            if basis == "whole_file" and times:

View on GitHub (pinned to 8321021c54)