affaan-m/ECC · error · ContractError

receipt evidence_artifacts must be a list

Error message

receipt evidence_artifacts must be a list

What it means

validate_artifact_receipt checks that a receipt's 'evidence_artifacts' key is a list before binding each emitted artifact to its provenance. A missing, None, or non-list value raises this ContractError, since artifact binding cannot proceed.

Solutions

  1. Ensure 'evidence_artifacts' is a JSON array of artifact entry objects in the receipt.
  2. Fix the receipt producer so it always emits the list, even when empty (use [] rather than omitting the key).
  3. Check receipt file version/schema matches what this validator expects; regenerate the receipt with the current tool version.
  4. Confirm the file loaded as 'receipt' is actually a Tasteforge receipt document.

Example fix

// before
receipt = {"evidence_artifacts": {"out.png": {"sha256": "..."}}}
// after
receipt = {"evidence_artifacts": [{"path": "out.png", "sha256": "..."}]}
Defensive patterns

Strategy: type-guard

Validate before calling

def receipt_loadable(data):
    return isinstance(data, dict) and isinstance(data.get("evidence_artifacts"), list)

Type guard

def is_receipt(data: object) -> bool:
    return isinstance(data, dict) and isinstance(data.get("evidence_artifacts"), list)

Try / catch

try:
    validate_artifact_receipt(out_dir, receipt)
except ContractError as e:
    if "evidence_artifacts must be a list" in str(e):
        receipt = rebuild_receipt(out_dir)  # regenerate from emitted artifacts
        validate_artifact_receipt(out_dir, receipt)
    else:
        raise

Prevention

When it happens

Trigger: Passing receipt = {} (no key), {'evidence_artifacts': None}, {'evidence_artifacts': {...}} (dict instead of list), or a string value to validate_artifact_receipt via validate_bundle.

Common situations: A receipt writer crashing before populating evidence_artifacts; hand-rolled receipt serialization emitting a dict keyed by artifact path instead of a list; schema drift between receipt producer and validator versions; loading the wrong JSON (a summary file instead of a receipt).

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

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

                or payload.get("provider_calls") != 0
                or payload.get("provider_execution") is not False):
            raise ContractError(f"{modality} manifest crosses the dry-run boundary")
        for request in payload["requests"]:
            if (request.get("dry_run") is not True
                    or request.get("submit") is not False
                    or type(request.get("provider_calls")) is not int
                    or request.get("provider_calls") != 0
                    or request.get("provider_execution") is not False
                    or request.get("provider_call_mode") != "disabled"):
                raise ContractError(f"{modality} request crosses the dry-run boundary")


def validate_artifact_receipt(out_dir: str | Path, receipt: dict[str, Any]) -> None:
    """Verify that the receipt binds every emitted artifact and its provenance."""
    out_dir = Path(out_dir).resolve()
    entries = receipt.get("evidence_artifacts")
    if not isinstance(entries, list):
        raise ContractError("receipt evidence_artifacts must be a list")
    if not all(isinstance(entry, dict) for entry in entries):
        raise ContractError("receipt evidence_artifacts entries must be objects")
    known_sources: set[tuple[str, str]] = set()
    source_durations: dict[tuple[str, str], float] = {}
    for key in ("references", "evidence_files"):
        sources = receipt.get(key, [])
        if not isinstance(sources, list):
            raise ContractError(f"receipt {key} must be a list")
        for source in sources:
            if not isinstance(source, dict):
                raise ContractError(f"receipt {key} contains an invalid source")
            source_path = source.get("path")
            expected_digest = source.get("sha256")
            if (not isinstance(source_path, str) or not source_path
                    or not isinstance(expected_digest, str)
                    or not re.fullmatch(r"[0-9a-f]{64}", expected_digest)):
                raise ContractError("receipt has an invalid source identity")
            known_sources.add((source_path, expected_digest))

View on GitHub (pinned to 8321021c54)