{"record":{"id":"53685040c803e767","repo":"affaan-m/ECC","slug":"receipt-must-declare-an-explicit-source-availability-policy","errorCode":null,"errorMessage":"receipt must declare an explicit source availability policy","messagePattern":"receipt must declare an explicit source availability policy","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":344,"sourceCode":"            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:\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","sourceCodeStart":326,"sourceCodeEnd":362,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L326-L362","documentation":"When a receipt declares any provenance sources (references or evidence_files are non-empty), it must also set \"source_availability_policy\" to exactly \"allow_unavailable\" or \"require_available\". If sources exist and the policy is missing or any other value, ContractError('receipt must declare an explicit source availability policy') is raised, forcing an explicit decision about how missing sources are handled.","triggerScenarios":"A receipt listing references/evidence_files but omitting \"source_availability_policy\"; the key set to null, \"\", or a typo like \"allow-unavailable\"/\"RequireAvailable\"; sources added to an older receipt that never carried the policy field.","commonSituations":"Upgrading receipts from an older tasteforge format without the policy field; typos or casing mistakes in the policy string; template-generated receipts that only set the policy conditionally.","solutions":["Add \"source_availability_policy\": \"require_available\" (fail when sources are missing) or \"allow_unavailable\" (skip missing sources) to the receipt","Correct the spelling/casing to one of the two exact allowed strings","Regenerate the receipt with current tasteforge tooling so the field is emitted"],"exampleFix":"// before\n{\"references\": [...]}  // no policy\n// after\n{\"references\": [...], \"source_availability_policy\": \"require_available\"}","handlingStrategy":"validation","validationCode":"POLICIES = {'allow_unavailable', 'require_available'}\n\ndef policy_ok(receipt):\n    has_sources = bool(receipt.get('references')) or bool(receipt.get('evidence_files'))\n    return not has_sources or receipt.get('source_availability_policy') in POLICIES","typeGuard":"def declares_policy(receipt) -> bool:\n    return receipt.get('source_availability_policy') in {'allow_unavailable', 'require_available'}","tryCatchPattern":"try:\n    validate_artifact_receipt(receipt, out_dir)\nexcept ContractError as e:\n    if 'source availability policy' in str(e):\n        receipt['source_availability_policy'] = 'require_available'\n        validate_artifact_receipt(receipt, out_dir)\n    raise","preventionTips":["Always set source_availability_policy when emitting sources","Use require_available for reproducible bundles; allow_unavailable only intentionally","Copy the exact strings — no hyphens or different casing","Add the policy field to your receipt JSON schema as an enum"],"tags":["validation","receipt","config"],"backgroundTag":"invalid-enum-value","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"}