{"record":{"id":"a908d14f251aea9a","repo":"affaan-m/ECC","slug":"artifact-path-must-be-a-non-empty-relative-path","errorCode":null,"errorMessage":"artifact path must be a non-empty relative path","messagePattern":"artifact path must be a non-empty relative path","errorType":"validation","errorClass":"ContractError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/contract.py","lineNumber":375,"sourceCode":"        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\n    if missing:\n        raise ContractError(f\"unbound emitted artifact: {sorted(missing)}\")\n    if extra:\n        raise ContractError(f\"receipt binds missing artifact: {sorted(extra)}\")\n\n    for entry in entries:\n        relative = entry.get(\"path\")\n        assert isinstance(relative, str)\n        path = (out_dir / relative).resolve()\n        try:\n            path.relative_to(out_dir)\n        except ValueError as error:\n            raise ContractError(f\"artifact path escapes output directory: {relative}\") from error","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/contract.py#L357-L393","documentation":"Each artifact entry in receipt.json must carry a 'path' that is a non-empty string. This ContractError is raised when an entry's path is missing, null, an empty string, or not a string at all. The library requires a well-formed relative path to bind the receipt entry to an emitted file under the output directory.","triggerScenarios":"Calling validate_artifact_receipt()/validate_bundle() with a hand-edited or programmatically generated receipt.json whose artifacts[] entry lacks 'path', has path: \"\", or path set to a non-string value (number, null, object).","commonSituations":"Manually pruning or editing receipt.json entries and accidentally deleting or emptying the path field; a custom generation script writing malformed entries; JSON schema drift between tool versions.","solutions":["Fix the receipt entry so every artifact has a non-empty string 'path' relative to the output directory.","Regenerate the receipt with tasteforge instead of editing it by hand.","Validate receipt.json structure (e.g. jq '.artifacts[] | select(.path == null or .path == \"\")') before running the validator."],"exampleFix":"// before\n{\"artifacts\": [{\"bytes\": 120, \"sha256\": \"abc...\"}]}\n// after\n{\"artifacts\": [{\"path\": \"images/hero.png\", \"bytes\": 120, \"sha256\": \"abc...\"}]}","handlingStrategy":"validation","validationCode":"for i, entry in enumerate(receipt.get('artifacts', [])):\n    p = entry.get('path')\n    if not isinstance(p, str) or not p:\n        raise SystemExit(f'artifact[{i}].path must be a non-empty string')","typeGuard":"def has_valid_path(entry: dict) -> bool:\n    return isinstance(entry.get('path'), str) and len(entry['path']) > 0","tryCatchPattern":"try:\n    validate_artifact_receipt(out_dir, entries)\nexcept ContractError as e:\n    if 'non-empty relative path' in str(e):\n        fix_receipt_paths(receipt_path)  # repair or regenerate\n    else:\n        raise","preventionTips":["Never hand-edit receipt.json; regenerate it with the tool.","Schema-check receipts programmatically before validation.","Keep a JSON schema for the receipt format and lint against it in CI."],"tags":["validation","receipt","schema"],"backgroundTag":"schema-validation-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"}