{"record":{"id":"7b1e0df3dc597916","repo":"affaan-m/ECC","slug":"receipt-has-an-invalid-source-identity","errorCode":null,"errorMessage":"receipt has an invalid source identity","messagePattern":"receipt has an invalid source identity","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":329,"sourceCode":"    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                )\n    source_policy = receipt.get(\"source_availability_policy\")\n    if known_sources and source_policy not in {\"allow_unavailable\", \"require_available\"}:\n        raise ContractError(\"receipt must declare an explicit source availability policy\")\n    for source_path, expected_digest in sorted(known_sources):\n        path = Path(source_path)\n        try:","sourceCodeStart":311,"sourceCodeEnd":347,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L311-L347","documentation":"Each receipt source descriptor must carry a non-empty string \"path\" and a \"sha256\" value that is a string of exactly 64 lowercase hexadecimal characters. If either is missing, empty, or malformed, validate_artifact_receipt raises ContractError('receipt has an invalid source identity'). This identity (path, digest) is later used to locate and verify the source file on disk.","triggerScenarios":"A source entry with a missing/empty \"path\", a non-string or empty \"sha256\", an uppercase hex digest, a truncated digest (e.g. a 12-char short hash), or a digest prefixed with \"sha256:\".","commonSituations":"Digests computed with tools that output uppercase or prefixed hashes (sha256sum output is fine, but 'SHA256: ...' is not); receipts generated before the digest field was added; paths left blank by templating; using a git short SHA instead of the full sha256.","solutions":["Set \"path\" to a non-empty relative path string and \"sha256\" to the full 64-char lowercase hex digest of the file","Recompute the digest with `sha256sum <file>` (or hashlib.sha256(...).hexdigest()) and paste it verbatim","Lowercase/strip any prefix from an existing digest","Regenerate the receipt with tasteforge so identities are computed correctly"],"exampleFix":"// before\n{\"path\": \"\", \"sha256\": \"A3F...\"}   // empty path, uppercase\n// after\n{\"path\": \"src/data.csv\", \"sha256\": \"a3f...\"}  // full 64-char lowercase hex","handlingStrategy":"validation","validationCode":"import re\nDIGEST = re.compile(r\"[0-9a-f]{64}\")\n\ndef identity_ok(entry):\n    return bool(entry.get('path')) and isinstance(entry.get('path'), str) \\\n        and isinstance(entry.get('sha256'), str) and bool(DIGEST.fullmatch(entry['sha256']))","typeGuard":"def has_valid_identity(entry) -> bool:\n    p, d = entry.get('path'), entry.get('sha256')\n    return isinstance(p, str) and bool(p) and isinstance(d, str) \\\n        and re.fullmatch(r\"[0-9a-f]{64}\", d) is not None","tryCatchPattern":"try:\n    validate_artifact_receipt(receipt, out_dir)\nexcept ContractError as e:\n    if 'invalid source identity' in str(e):\n        for s in receipt['references'] + receipt['evidence_files']:\n            s['sha256'] = hashlib.sha256(Path(s['path']).read_bytes()).hexdigest()\n    raise","preventionTips":["Compute digests with hashlib.sha256(...).hexdigest() (already lowercase, full length)","Reject or normalize uppercase/prefixed digests at ingest","Never use git SHAs or truncated hashes in receipts","Lint receipts for 64-char lowercase hex before validation"],"tags":["validation","sha256","receipt"],"backgroundTag":"invalid-identifier-format","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"}