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

  1. Restore the original artifact file that matches the recorded binding (or re-record the binding against the current file with the fingerprint helper)
  2. Ensure the 'bytes' field in the binding is an integer, not a string
  3. Re-run the provider request to regenerate a pristine external result and rebind
  4. 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

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


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)