affaan-m/ECC · error · ContractError
receipt evidence_artifacts entries must be objects
Error message
receipt evidence_artifacts entries must be objects
What it means
After confirming evidence_artifacts is a list, validate_artifact_receipt requires every entry to be an object (dict). Any list containing a non-dict element (string, number, None, nested list) raises this ContractError so per-artifact digest and provenance binding can proceed.
Solutions
- Convert each entry to an object with the required fields (path and digest/provenance per the receipt schema).
- Fix the receipt writer to serialize full artifact records, not bare path strings.
- Validate receipt entries with a schema/type check before writing the receipt file.
- Regenerate the receipt with the current Tasteforge version so all entries conform.
Example fix
// before
"evidence_artifacts": ["artifact.png", "artifact.wav"]
// after
"evidence_artifacts": [{"path": "artifact.png", "sha256": "<64-hex>"}, {"path": "artifact.wav", "sha256": "<64-hex>"}] Defensive patterns
Strategy: type-guard
Validate before calling
bad = [e for e in receipt.get("evidence_artifacts", []) if not isinstance(e, dict)]
if bad:
raise TypeError(f"non-object artifact entries: {bad!r}") Type guard
def entries_are_objects(receipt: dict) -> bool:
entries = receipt.get("evidence_artifacts")
return isinstance(entries, list) and all(isinstance(e, dict) for e in entries) Try / catch
try:
validate_artifact_receipt(out_dir, receipt)
except ContractError as e:
if "entries must be objects" in str(e):
receipt = promote_paths_to_entries(receipt, out_dir) # wrap bare paths in objects with digests
validate_artifact_receipt(out_dir, receipt)
else:
raise Prevention
- Serialize artifact records (path + sha256), never bare filename strings.
- Use a single receipt-builder helper so entry shapes stay consistent.
- Type-check receipt entries before writing; reject str/None entries at production time.
When it happens
Trigger: receipt = {'evidence_artifacts': ['out.png', 'out.wav']} (strings instead of objects); entries containing None; or a mixed list where one entry is a dict and another is a str, caught by the all(isinstance(entry, dict)) check.
Common situations: Serializing just artifact filenames instead of artifact records; a producer appending paths and objects inconsistently; JSON schema changes between receipt versions; manually appending a quick entry to a receipt for debugging and forgetting to promote it to an object.
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
- receipt evidence_artifacts must be a list
- artifact path must be a non-empty relative path
- artifact byte size does not match receipt
- artifact has invalid modality binding
- artifact has invalid reference path
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/dc706bfd282b9dfd.
Report an issue: GitHub.
Appendix: source
Thrown at skills/taste-application/scripts/tasteforge/contract.py:314
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))
if key == "references":
source_duration = source.get("source_duration")View on GitHub (pinned to 8321021c54)