{"record":{"id":"49ceb658a4c57b5f","repo":"affaan-m/ECC","slug":"receipt-key-contains-an-invalid-source","errorCode":null,"errorMessage":"receipt {key} contains an invalid source","messagePattern":"receipt (.+?) contains an invalid source","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":323,"sourceCode":"\n\ndef validate_artifact_receipt(out_dir: str | Path, receipt: dict[str, Any]) -> None:\n    \"\"\"Verify that the receipt binds every emitted artifact and its provenance.\"\"\"\n    out_dir = Path(out_dir).resolve()\n    entries = receipt.get(\"evidence_artifacts\")\n    if not isinstance(entries, list):\n        raise ContractError(\"receipt evidence_artifacts must be a list\")\n    if not all(isinstance(entry, dict) for entry in entries):\n        raise ContractError(\"receipt evidence_artifacts entries must be objects\")\n    known_sources: set[tuple[str, str]] = set()\n    source_durations: dict[tuple[str, str], float] = {}\n    for key in (\"references\", \"evidence_files\"):\n        sources = receipt.get(key, [])\n        if not isinstance(sources, list):\n            raise ContractError(f\"receipt {key} must be a list\")\n        for source in sources:\n            if not isinstance(source, dict):\n                raise ContractError(f\"receipt {key} contains an invalid source\")\n            source_path = source.get(\"path\")\n            expected_digest = source.get(\"sha256\")\n            if (not isinstance(source_path, str) or not source_path\n                    or not isinstance(expected_digest, str)\n                    or not re.fullmatch(r\"[0-9a-f]{64}\", expected_digest)):\n                raise ContractError(\"receipt has an invalid source identity\")\n            known_sources.add((source_path, expected_digest))\n            if key == \"references\":\n                source_duration = source.get(\"source_duration\")\n                if not _is_finite_real(source_duration):\n                    raise ContractError(\"receipt reference has an invalid finite source duration\")\n                source_duration = cast(float, source_duration)\n                if float(source_duration) <= 0:\n                    raise ContractError(\"receipt reference has an invalid finite source duration\")\n                source_durations[(source_path, expected_digest)] = float(source_duration)\n                _validate_probe_evidence(\n                    source.get(\"probe\"), float(source_duration), label=\"receipt reference\"\n                )","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L305-L341","documentation":"Every entry inside a receipt's \"references\" or \"evidence_files\" list must be an object (dict) describing one source file. If any element is a scalar, string, list, or null, validate_artifact_receipt raises ContractError('receipt {key} contains an invalid source'). The check guarantees the validator can safely read each entry's \"path\" and \"sha256\" keys.","triggerScenarios":"A receipt whose references/evidence_files list contains non-object elements — e.g. bare path strings like [\"src/data.csv\"], nested lists, numbers, or a null entry accidentally left in the array.","commonSituations":"Receipts authored by hand where sources were written as path strings instead of descriptor objects; generators that appended raw paths; template errors leaving placeholder null entries; JSON produced by joining strings with commas then splitting.","solutions":["Replace each list element with an object of the form {\"path\": <str>, \"sha256\": <64-hex str>}, optionally with \"source_duration\" and \"probe\" for references","Remove null or placeholder entries from the list","Regenerate the receipt via tasteforge so entries are emitted in the correct shape"],"exampleFix":"// before\n\"evidence_files\": [\"evidence/notes.txt\"]\n// after\n\"evidence_files\": [{\"path\": \"evidence/notes.txt\", \"sha256\": \"<64 hex chars>\"}]","handlingStrategy":"type-guard","validationCode":"def sources_ok(receipt, key):\n    return all(isinstance(s, dict) for s in receipt.get(key, []))","typeGuard":"def is_source(entry) -> bool:\n    return isinstance(entry, dict) and isinstance(entry.get('path'), str)\n\nreceipt[key] = [s for s in raw if is_source(s)]","tryCatchPattern":"try:\n    validate_artifact_receipt(receipt, out_dir)\nexcept ContractError as e:\n    if 'contains an invalid source' in str(e):\n        receipt[key] = [s for s in receipt[key] if isinstance(s, dict)]\n    raise","preventionTips":["Represent each source as an object with path/sha256 keys, never a bare string","Filter or reject non-dict entries at receipt-build time","Add a unit test asserting every source entry is a dict"],"tags":["validation","schema","receipt"],"backgroundTag":"schema-validation-failed","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}