affaan-m/ECC · error · ContractError
receipt must be a list
Error message
receipt {key} must be a list What it means
tasteforge's receipt validator requires each provenance section of a receipt ("references" and "evidence_files") to be a JSON list of source descriptors. When a receipt declares one of these keys with a non-list value (e.g. a string, object, or null that is explicitly present), ContractError('receipt {key} must be a list') is raised. This protects bundle validation from malformed receipts that would otherwise be iterated unsafely.
Solutions
- Convert the receipt's "references" and "evidence_files" values to JSON arrays, each element being an object with "path" and "sha256" fields
- Regenerate the receipt with the tasteforge tooling instead of editing it by hand
- If a single source was recorded, wrap it in a list: [{"path": ..., "sha256": ...}]
- Validate the receipt JSON against the expected shape before passing it to validate_bundle
Example fix
// before
"references": {"path": "src/data.csv", "sha256": "abc..."}
// after
"references": [{"path": "src/data.csv", "sha256": "abc..."}] Defensive patterns
Strategy: validation
Validate before calling
def receipt_list_ok(receipt, key):
v = receipt.get(key, [])
return isinstance(v, list)
# call before validate:
# assert receipt_list_ok(receipt, 'references') and receipt_list_ok(receipt, 'evidence_files') Type guard
def is_source_list(v) -> bool:
return isinstance(v, list) and all(isinstance(s, dict) for s in v) Try / catch
try:
validate_artifact_receipt(receipt, out_dir)
except ContractError as e:
if 'must be a list' in str(e):
key = str(e).split()[1]
receipt[key] = [receipt[key]] if isinstance(receipt[key], dict) else []
raise Prevention
- Always emit references/evidence_files as JSON arrays, even for a single source
- Validate receipt JSON with a schema (jsonschema) before validation
- Never hand-edit receipts; regenerate with tasteforge
- Round-trip receipts through json.loads to catch type drift
When it happens
Trigger: Calling validate_artifact_receipt / validate_bundle with a receipt dict where receipt["references"] or receipt["evidence_files"] is present but not a list — e.g. a dict keyed by filename, a single object instead of a one-element list, a comma-separated string, or null.
Common situations: Hand-written or hand-edited receipt JSON; a receipt generator upgraded from an older single-source format (one object) to the current list format; scripts building receipts programmatically that append objects to a dict instead of a list; YAML/JSON round-trips that converted a list to a mapping.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- artifact path must be a non-empty relative path
- receipt contains duplicate artifact paths
- receipt contains an invalid source
- Unsupported asset receipt schema
- -32602
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/8dac734b1990322b.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/contract.py:320
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))
if key == "references":
source_duration = source.get("source_duration")
if not _is_finite_real(source_duration):
raise ContractError("receipt reference has an invalid finite source duration")
source_duration = cast(float, source_duration)
if float(source_duration) <= 0:
raise ContractError("receipt reference has an invalid finite source duration")
source_durations[(source_path, expected_digest)] = float(source_duration)View on GitHub (pinned to 8321021c54)