affaan-m/ECC · error · ContractError

receipt source cannot be securely read

Error message

receipt source cannot be securely read: {source_path}

What it means

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.

Solutions

  1. Fix filesystem permissions so the validating user can read the source (chmod/chown or run as the owning user)
  2. Check the underlying cause (dmesg/disk health for EIO; ulimit -n for EMFILE) and remediate
  3. Copy sources to a local readable location and update the receipt paths + digests
  4. Re-run validation after access is restored — this error is never bypassed by allow_unavailable

Example fix

# before
-rw------- 1 other dev src/data.csv
# after
chmod o+r src/data.csv   # or run validation as the file owner
Defensive patterns

Strategy: try-catch

Validate before calling

import os

def readable_sources(receipt):
    unreadable = [s['path'] for s in sources
                  if not os.access(s['path'], os.R_OK)]
    return unreadable  # must be empty

Type guard

def is_readable_file(path: str) -> bool:
    p = Path(path)
    return p.is_file() and os.access(p, os.R_OK)

Try / catch

try:
    validate_artifact_receipt(receipt, out_dir)
except ContractError as e:
    if 'cannot be securely read' in str(e):
        path = str(e).rsplit(': ', 1)[1]
        # fix permissions / ulimit / disk error, then retry; never bypass
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/0ae48bc5efedcd13. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/contract.py:362

        raise ContractError("receipt must declare an explicit source availability policy")
    for source_path, expected_digest in sorted(known_sources):
        path = Path(source_path)
        try:
            metadata = path.lstat()
        except FileNotFoundError:
            if source_policy == "require_available":
                raise ContractError(f"receipt source is unavailable: {source_path}") from None
            continue
        if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISREG(metadata.st_mode):
            raise ContractError(f"receipt source is not a safe regular file: {source_path}")
        try:
            actual_digest = _sha256(path)
        except FileNotFoundError:
            if source_policy == "require_available":
                raise ContractError(f"receipt source is unavailable: {source_path}") from None
            continue
        except OSError:
            raise ContractError(f"receipt source cannot be securely read: {source_path}") from None
        if actual_digest != expected_digest:
            raise ContractError(f"receipt source SHA-256 changed after generation: {source_path}")

    emitted = {
        path.relative_to(out_dir).as_posix()
        for path in out_dir.rglob("*")
        if path.is_file() and path.name != "receipt.json"
    }
    bound_paths: list[str] = []
    for entry in entries:
        relative = entry.get("path")
        if not isinstance(relative, str) or not relative:
            raise ContractError("artifact path must be a non-empty relative path")
        bound_paths.append(relative)
    if len(bound_paths) != len(set(bound_paths)):
        raise ContractError("receipt contains duplicate artifact paths")
    missing = emitted - set(bound_paths)
    extra = set(bound_paths) - emitted

View on GitHub (pinned to 8321021c54)