{"record":{"id":"0ae48bc5efedcd13","repo":"affaan-m/ECC","slug":"receipt-source-cannot-be-securely-read-source-path","errorCode":null,"errorMessage":"receipt source cannot be securely read: {source_path}","messagePattern":"receipt source cannot be securely read: (.+?)","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":362,"sourceCode":"        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:\n            metadata = path.lstat()\n        except FileNotFoundError:\n            if source_policy == \"require_available\":\n                raise ContractError(f\"receipt source is unavailable: {source_path}\") from None\n            continue\n        if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISREG(metadata.st_mode):\n            raise ContractError(f\"receipt source is not a safe regular file: {source_path}\")\n        try:\n            actual_digest = _sha256(path)\n        except FileNotFoundError:\n            if source_policy == \"require_available\":\n                raise ContractError(f\"receipt source is unavailable: {source_path}\") from None\n            continue\n        except OSError:\n            raise ContractError(f\"receipt source cannot be securely read: {source_path}\") from None\n        if actual_digest != expected_digest:\n            raise ContractError(f\"receipt source SHA-256 changed after generation: {source_path}\")\n\n    emitted = {\n        path.relative_to(out_dir).as_posix()\n        for path in out_dir.rglob(\"*\")\n        if path.is_file() and path.name != \"receipt.json\"\n    }\n    bound_paths: list[str] = []\n    for entry in entries:\n        relative = entry.get(\"path\")\n        if not isinstance(relative, str) or not relative:\n            raise ContractError(\"artifact path must be a non-empty relative path\")\n        bound_paths.append(relative)\n    if len(bound_paths) != len(set(bound_paths)):\n        raise ContractError(\"receipt contains duplicate artifact paths\")\n    missing = emitted - set(bound_paths)\n    extra = set(bound_paths) - emitted","sourceCodeStart":344,"sourceCodeEnd":380,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L344-L380","documentation":"If reading a receipt source for hashing fails with a generic OSError (permission denied, I/O error, too many open files, etc.), the validator converts it to ContractError('receipt source cannot be securely read: <path>'). The word 'securely' signals the validator refuses to proceed without a trustworthy digest; it never skips unreadable sources regardless of policy.","triggerScenarios":"Source file lacks read permission for the validating user; disk I/O error; process hit the open-file limit; encrypted/locked file; source on a flaky network mount returning EIO during validation.","commonSituations":"Files checked in with 0600 owned by another user (CI runs as different uid); umask/ACL changes after generation; headless CI lacking access to a mounted secret-backed source; ulimit -n too low with many sources.","solutions":["Fix filesystem permissions so the validating user can read the source (chmod/chown or run as the owning user)","Check the underlying cause (dmesg/disk health for EIO; ulimit -n for EMFILE) and remediate","Copy sources to a local readable location and update the receipt paths + digests","Re-run validation after access is restored — this error is never bypassed by allow_unavailable"],"exampleFix":"# before\n-rw------- 1 other dev src/data.csv\n# after\nchmod o+r src/data.csv   # or run validation as the file owner","handlingStrategy":"try-catch","validationCode":"import os\n\ndef readable_sources(receipt):\n    unreadable = [s['path'] for s in sources\n                  if not os.access(s['path'], os.R_OK)]\n    return unreadable  # must be empty","typeGuard":"def is_readable_file(path: str) -> bool:\n    p = Path(path)\n    return p.is_file() and os.access(p, os.R_OK)","tryCatchPattern":"try:\n    validate_artifact_receipt(receipt, out_dir)\nexcept ContractError as e:\n    if 'cannot be securely read' in str(e):\n        path = str(e).rsplit(': ', 1)[1]\n        # fix permissions / ulimit / disk error, then retry; never bypass\n    raise","preventionTips":["Ensure the validating user has read permission on every source (chmod/chown)","Raise ulimit -n when validating receipts with many sources","Run CI as the same user that owns checked-in restricted files","Monitor disk health; EIO on the source volume surfaces here"],"tags":["filesystem","permissions","receipt"],"backgroundTag":"file-read-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"}