{"record":{"id":"1c3201be68b0c1ba","repo":"Hmbown/CodeWhale","slug":"receipt-source-sha-must-be-an-exact-lowercase-git","errorCode":null,"errorMessage":"receipt source_sha must be an exact lowercase Git SHA","messagePattern":"receipt source_sha must be an exact lowercase Git SHA","errorType":"exception","errorClass":"PersistenceBacklogError","httpStatus":null,"severity":"error","filePath":"scripts/check-persistence-backlog-budget.py","lineNumber":156,"sourceCode":"    *,\n    expected_source: dict[str, Any] | None = None,\n    require_clean_source: bool = False,\n) -> None:\n    missing = [field for field in REQUIRED_RECEIPT_FIELDS if field not in receipt]\n    if missing:\n        raise PersistenceBacklogError(\n            \"receipt missing required field(s): \" + \", \".join(missing)\n        )\n    if receipt[\"document_kind\"] != RECEIPT_KIND:\n        raise PersistenceBacklogError(f\"receipt document_kind must be {RECEIPT_KIND}\")\n    if receipt[\"schema_version\"] != SCHEMA_VERSION:\n        raise PersistenceBacklogError(\"receipt schema_version changed\")\n    for field, expected in FIXTURE.items():\n        validate_frozen_field(field, receipt[field], expected)\n    if not isinstance(receipt[\"source_sha\"], str) or not SOURCE_SHA_PATTERN.fullmatch(\n        receipt[\"source_sha\"]\n    ):\n        raise PersistenceBacklogError(\"receipt source_sha must be an exact lowercase Git SHA\")\n    if type(receipt[\"source_dirty\"]) is not bool:\n        raise PersistenceBacklogError(\"receipt source_dirty must be boolean\")\n    for field, prefix in ((\"rustc_version\", \"rustc \"), (\"cargo_version\", \"cargo \")):\n        if not isinstance(receipt[field], str) or not receipt[field].startswith(prefix):\n            raise PersistenceBacklogError(f\"receipt {field} must be a version string\")\n    validate_frozen_field(\"build_profile\", receipt[\"build_profile\"], \"test\")\n    validate_frozen_field(\"sample_count\", receipt[\"sample_count\"], 1)\n    if expected_source is not None:\n        for field in (\n            \"source_sha\",\n            \"source_dirty\",\n            \"rustc_version\",\n            \"cargo_version\",\n            \"build_profile\",\n            \"sample_count\",\n        ):\n            if receipt[field] != expected_source[field]:\n                raise PersistenceBacklogError(","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/scripts/check-persistence-backlog-budget.py#L138-L174","documentation":"Raised in validate_receipt (scripts/check-persistence-backlog-budget.py:153-156) when source_sha is not a string or does not fully match SOURCE_SHA_PATTERN ([0-9a-f]{40}). The checker only accepts an exact 40-character lowercase hex SHA-1 commit id, since that string is compared verbatim against `git rev-parse HEAD` for provenance pinning.","triggerScenarios":"source_sha recorded as uppercase hex, a short SHA, an empty string or placeholder (\"unknown\", \"dev\"), a non-string value, or a 64-hex SHA from a repository using git's SHA-256 object format.","commonSituations":"Hand-filling provenance fields; tools that abbreviate SHAs; experimental SHA-256 repos; env-var plumbing (CODEWHALE_TEST_PERSISTENCE_BACKLOG_SOURCE_SHA) that dropped or altered the value.","solutions":["Regenerate the receipt with scripts/measure-persistence-backlog.py so source_sha is captured verbatim from `git rev-parse HEAD`.","Never abbreviate or recase the SHA when moving receipt data.","Use a standard SHA-1 object-format repository for measurements (the 40-hex pattern rejects SHA-256 repos)."],"exampleFix":"// receipt (before)\n\"source_sha\": \"9F86D0818...\"\n// receipt (after)\n\"source_sha\": \"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b\"","handlingStrategy":"type-guard","validationCode":"import re\nsha = receipt.get(\"source_sha\")\nif not isinstance(sha, str) or not re.fullmatch(r\"[0-9a-f]{40}\", sha):\n    sys.exit(\"source_sha malformed; recapture from `git rev-parse HEAD` verbatim\")","typeGuard":"def is_exact_sha(value) -> bool:\n    return isinstance(value, str) and re.fullmatch(r\"[0-9a-f]{40}\", value) is not None","tryCatchPattern":"try:\n    validate_receipt(receipt)\nexcept PersistenceBacklogError as e:\n    if \"lowercase Git SHA\" in str(e):\n        raise RuntimeError(\"provenance SHA mangled (SHA-256 repo or reformatting); re-measure\") from e\n    raise","preventionTips":["Never abbreviate, recase, or synthesize the SHA; capture `git rev-parse HEAD` output verbatim.","Run measurements in SHA-1 object-format repositories (the pattern rejects 64-hex).","Keep env plumbing (CODEWHALE_TEST_PERSISTENCE_BACKLOG_SOURCE_SHA) byte-exact."],"tags":["validation","git","sha","provenance"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}