{"record":{"id":"ee8a02630d1e4cb5","repo":"affaan-m/ECC","slug":"artifact-relative-sha-256-does-not-match-receipt","errorCode":null,"errorMessage":"artifact {relative} SHA-256 does not match receipt","messagePattern":"artifact (.+?) SHA-256 does not match receipt","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":405,"sourceCode":"        relative = entry.get(\"path\")\n        assert isinstance(relative, str)\n        path = (out_dir / relative).resolve()\n        try:\n            path.relative_to(out_dir)\n        except ValueError as error:\n            raise ContractError(f\"artifact path escapes output directory: {relative}\") from error\n        if entry.get(\"provider_execution\") is not False:\n            raise ContractError(f\"artifact {relative} permits provider execution\")\n        if not isinstance(entry.get(\"genre_numbers\"), list):\n            raise ContractError(f\"artifact {relative} lacks genre binding\")\n        modalities = entry.get(\"modalities\")\n        if (not isinstance(modalities, list)\n                or any(modality not in _REQUIRED_MODALITIES for modality in modalities)):\n            raise ContractError(f\"artifact {relative} has invalid modality binding\")\n        if entry.get(\"bytes\") != path.stat().st_size:\n            raise ContractError(f\"artifact {relative} byte size does not match receipt\")\n        if entry.get(\"sha256\") != _sha256(path):\n            raise ContractError(f\"artifact {relative} SHA-256 does not match receipt\")\n        provenance = entry.get(\"provenance\")\n        if not isinstance(provenance, list) or not provenance:\n            raise ContractError(f\"artifact {relative} lacks exact reference/time provenance\")\n        for source in provenance:\n            if not isinstance(source.get(\"reference_path\"), str) or not source[\"reference_path\"]:\n                raise ContractError(f\"artifact {relative} has invalid reference path\")\n            digest = source.get(\"reference_sha256\")\n            if not isinstance(digest, str) or len(digest) != 64:\n                raise ContractError(f\"artifact {relative} has invalid reference SHA-256\")\n            if (source[\"reference_path\"], digest) not in known_sources:\n                raise ContractError(f\"artifact {relative} cites an unknown provenance source\")\n            times = source.get(\"reference_times\")\n            basis = source.get(\"time_basis\")\n            if not isinstance(times, list) or basis not in {\"media_seconds\", \"whole_file\"}:\n                raise ContractError(f\"artifact {relative} has invalid reference/time provenance\")\n            if basis == \"media_seconds\" and not times:\n                raise ContractError(f\"artifact {relative} lacks media reference times\")\n            if basis == \"whole_file\" and times:","sourceCodeStart":387,"sourceCodeEnd":423,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L387-L423","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Regenerate the artifact and the receipt together using the tasteforge pipeline so the digest is recomputed from the final bytes","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","Restore the artifact to its original bytes (e.g. from git) so it matches the receipt","Run validate_bundle in a clean checkout to confirm which side (artifact or receipt) drifted"],"exampleFix":"import hashlib\n# before: receipt entry stale after artifact was touched\nreceipt['artifacts']['cover.png']['sha256'] = 'abc123...'\n# after: recompute digest from actual file bytes\nreceipt['artifacts']['cover.png']['sha256'] = hashlib.sha256(\n    Path('out/cover.png').read_bytes()\n).hexdigest()","handlingStrategy":"validation","validationCode":"import hashlib, pathlib\nassert receipt_entry['sha256'] == hashlib.sha256(pathlib.Path(artifact_path).read_bytes()).hexdigest()","typeGuard":"def digest_matches(path, expected: str) -> bool:\n    return hashlib.sha256(pathlib.Path(path).read_bytes()).hexdigest() == expected","tryCatchPattern":"try:\n    validate_artifact_receipt(out_dir)\nexcept ContractError as e:\n    if 'SHA-256 does not match' in str(e):\n        regenerate_artifacts_and_receipt(out_dir)\n    else:\n        raise","preventionTips":["Never edit emitted artifacts after receipt generation","Regenerate receipt and artifacts in the same pipeline run","Verify digests in CI before accepting bundles","Store artifacts in git and restore them rather than hand-copying files"],"tags":["integrity","checksum","tampering"],"backgroundTag":"checksum-mismatch","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}