{"record":{"id":"6625e82c38e2ba7b","repo":"affaan-m/ECC","slug":"resolved-sha-256-required","errorCode":null,"errorMessage":"resolved SHA-256 required","messagePattern":"resolved SHA-256 required","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/integration.py","lineNumber":82,"sourceCode":"    return n, d\n\n\ndef _range(value: Any, bounds: list[int] | None = None) -> list[int]:\n    if not isinstance(value, list) or len(value) != 2:\n        raise ValueError(\"range must contain two frame integers\")\n    start, end = (_integer(v) for v in value)\n    if start >= end or (bounds is not None and (start < bounds[0] or end > bounds[1])):\n        raise ValueError(\"frame range is empty or outside its bounds\")\n    return value\n\n\ndef _overlap(a: list[int], b: list[int]) -> bool:\n    return a[0] < b[1] and b[0] < a[1]\n\n\ndef _sha(value: Any) -> str:\n    if not isinstance(value, str) or not re.fullmatch(r\"[a-f0-9]{64}\", value):\n        raise ValueError(\"resolved SHA-256 required\")\n    return value\n\n\ndef _identity(info: os.stat_result) -> tuple:\n    return (info.st_dev, info.st_ino, info.st_size, info.st_mtime_ns, info.st_ctime_ns)\n\n\ndef _artifact(record: Any, *, parse_json: bool = False) -> Any:\n    \"\"\"Read stable regular bytes without following links or hydrating cloud files.\"\"\"\n    _object(record, {\"path\", \"bytes\", \"sha256\"})\n    return _read_local(_text(record[\"path\"]), parse_json=parse_json,\n                       expected_size=_integer(record[\"bytes\"], 1),\n                       expected_hash=_sha(record[\"sha256\"]))\n\n\ndef _parent_fd(path: Path) -> int:\n    flags = os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK | os.O_DIRECTORY\n    parent = os.open(path.anchor, flags)","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/integration.py#L64-L100","documentation":"`_sha` validates that a value is a 64-character lowercase hexadecimal string (i.e. a resolved SHA-256 digest) before the tasteforge pipeline accepts it. This library throws it because downstream integrity checks (`expected_hash` comparisons in `_read_local`) are meaningless unless the caller supplies a fully resolved digest, not a placeholder, tag, partial hash, or non-string. If the value fails the regex `[a-f0-9]{64}`, the function refuses to proceed.","triggerScenarios":"Calling `_artifact` (or any code path that funnels through `_sha`) with a value that is None, a non-string object, an uppercase hash, a 40-char SHA-1, a hex string of the wrong length, or a digest with a `sha256:` prefix.","commonSituations":"Passing a short commit SHA or container image tag instead of the full digest; copying a hash from a tool that uppercases hex; forgetting to compute the digest of an artifact before recording it in the application request; YAML/JSON config accidentally storing the hash as a number.","solutions":["Compute the actual SHA-256 of the artifact (e.g. `hashlib.sha256(open(p,'rb').read()).hexdigest()`) and pass that value instead of a tag or short hash.","Normalize the value: strip any `sha256:` prefix and call `.lower()` before passing it in.","Check the string length with `len(value) == 64` and that it matches `^[a-f0-9]{64}$` before calling the API.","If the value should be produced by an upstream step, fix that step to return a resolved digest rather than an unresolved reference."],"exampleFix":"// before\nrecord[\"sha\"] = \"sha256:9F86D081884C7D659A2FEAA0C55AD015...\"  # prefixed, uppercase\n// after\nimport hashlib\ndigest = hashlib.sha256(artifact_bytes).hexdigest()  # 64 lowercase hex chars\nrecord[\"sha\"] = digest","handlingStrategy":"validation","validationCode":"import re\nREQUIRED_SHA_RE = re.compile(r\"^[a-f0-9]{64}$\")\ndef assert_resolved_sha(value):\n    if not isinstance(value, str) or not REQUIRED_SHA_RE.fullmatch(value):\n        raise ValueError(f\"expected a resolved 64-char lowercase SHA-256, got {value!r}\")","typeGuard":"def is_resolved_sha256(value: object) -> bool:\n    return isinstance(value, str) and re.fullmatch(r\"[a-f0-9]{64}\", value) is not None","tryCatchPattern":"try:\n    artifact = _artifact(record)\nexcept ValueError as e:\n    if str(e) == \"resolved SHA-256 required\":\n        record[\"sha\"] = hashlib.sha256(artifact_bytes).hexdigest()\n        artifact = _artifact(record)\n    else:\n        raise","preventionTips":["Always generate hashes with hashlib.hexdigest() — never hand-write or truncate digests.","Keep a shared `compute_sha256(path)` helper so hash format is consistent everywhere.","Strip `sha256:` prefixes and lowercase hex at the config/CLI boundary before values enter the pipeline.","Validate hashes with a regex at every system boundary (config load, API input, request writer)."],"tags":["validation","hashing","sha256"],"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"}