affaan-m/ECC · error · ValueError
Artifact changed or binding invalid
Error message
Artifact changed or binding invalid: {actual["path"]} What it means
After fingerprinting the artifact at the binding's path, the library compares the actual fingerprint (bytes and hashes) against the recorded values. Any mismatch — or a non-int 'bytes' field — throws this error, meaning the artifact was modified, replaced, or the binding was recorded incorrectly.
Solutions
- Restore the original artifact file that matches the recorded binding (or re-record the binding against the current file with the fingerprint helper)
- Ensure the 'bytes' field in the binding is an integer, not a string
- Re-run the provider request to regenerate a pristine external result and rebind
- Check for tools (editors, formatters, sync clients) that silently modify files and exclude artifacts from them
Example fix
// before
"evidence": {"path": "a1.png", "bytes": "1024"}
// after
"evidence": {"path": "a1.png", "bytes": 1024, "sha256": "<actual hash>"} Defensive patterns
Strategy: try-catch
Validate before calling
import hashlib, os
for a in assets:
ev = ((a.get("provider_provenance") or {}).get("evidence") or {})
p = ev.get("path")
if p and os.path.exists(p):
actual = hashlib.sha256(open(p, "rb").read()).hexdigest()
if actual != ev.get("sha256"):
raise ValueError(f"artifact drifted before ingestion: {p}") Try / catch
try:
ingest_assets(assets)
except ValueError as e:
if str(e).startswith("Artifact changed or binding invalid"):
restore_original_artifact(path_from_message(e)); retry()
else:
raise Prevention
- Never edit artifacts after recording their binding — regenerate and rebind instead
- Store bytes as an int in bindings
- Exclude artifact directories from formatters, sync tools, and git autocrlf
- Record bindings immediately after the provider returns, before any post-processing
When it happens
Trigger: Editing or re-saving the artifact after the binding was recorded; recording bytes as a string instead of int; copying the manifest between machines where the artifact differs; partial/corrupted file transfer.
Common situations: Post-processing (crop/convert) an external result after recording provenance; git line-ending or encoding normalization altering files; regenerated artifacts overwriting the original without updating the manifest.
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
- application bundle differs from its bound evidence
- approval evidence must bind exact source, candidate and…
- approved text hash does not match
- artifact byte count mismatch
- artifact byte size does not match receipt
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/328f308cf1f1a4ea.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/assets.py:132
provider = _text(source.get('provider'), 'provider')
identifiers = {key: _text(source[key], key) for key in ('request_id', 'workflow_id')
if key in source}
if not identifiers:
raise ValueError('Provider provenance requires request_id or workflow_id')
if verify:
evidence = _verify_binding(source.get('evidence'), base)
else:
evidence = _fingerprint(_path(source.get('evidence_path'), base))
return dict(provider=provider, **identifiers, evidence=evidence,
verification='supplied_local_evidence_only')
def _verify_binding(value: Any, base: Path, modality: str | None = None) -> dict[str, Any]:
if not isinstance(value, dict):
raise ValueError('Missing artifact binding')
actual = _fingerprint(_path(value.get('path'), base), modality)
if type(value.get('bytes')) is not int or any(value.get(k) != v for k, v in actual.items()):
raise ValueError(f'Artifact changed or binding invalid: {actual["path"]}')
return actual
_TASTE_FIELDS = ('genre_number', 'genre_slug', 'style_fingerprint', 'reference_sha256')
def _bundle(value: Any, base: Path, verify: bool) -> tuple[dict[str, Any], dict[tuple[str, str], Any]]:
from .contract import validate_bundle
if verify:
binding = _verify_binding(value, base)
root = Path(binding['path']).parent
else:
root = _path(value, base)
binding = _fingerprint(root / 'receipt.json')
validate_bundle(root)
requests = {}
for modality in sorted(MODALITIES):View on GitHub (pinned to 8321021c54)