{"record":{"id":"6e6635751fefafbb","repo":"nexu-io/open-design","slug":"running-left-mirror-source-must-be-decoded-running","errorCode":null,"errorMessage":"running-left mirror source must be decoded/running-right.png","messagePattern":"running-left mirror source must be decoded/running-right\\.png","errorType":"exception","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"skills/hatch-pet/scripts/finalize_pet_run.py","lineNumber":104,"sourceCode":"    decision = job.get(\"mirror_decision\")\n    if not isinstance(decision, dict) or decision.get(\"approved\") is not True:\n        raise SystemExit(\n            \"running-left mirror job is missing an approved mirror_decision; \"\n            \"use derive_running_left_from_running_right.py after visual review\"\n        )\n\n    expected_source_hash = job.get(\"source_sha256\")\n    expected_output_hash = job.get(\"output_sha256\")\n    if not isinstance(expected_source_hash, str) or not expected_source_hash:\n        raise SystemExit(\"running-left mirror job is missing source_sha256\")\n    if not isinstance(expected_output_hash, str) or not expected_output_hash:\n        raise SystemExit(\"running-left mirror job is missing output_sha256\")\n    if not source.is_file():\n        raise SystemExit(f\"running-left mirror source image no longer exists: {source}\")\n    if not output.is_file():\n        raise SystemExit(f\"running-left mirrored output is missing: {output}\")\n    if source.name != \"running-right.png\" or source.parent.name != \"decoded\":\n        raise SystemExit(\"running-left mirror source must be decoded/running-right.png\")\n    if output.name != \"running-left.png\" or output.parent.name != \"decoded\":\n        raise SystemExit(\"running-left mirror output must be decoded/running-left.png\")\n    if file_sha256(source) != expected_source_hash:\n        raise SystemExit(\"running-left mirror source hash does not match imagegen-jobs.json\")\n    if file_sha256(output) != expected_output_hash:\n        raise SystemExit(\n            \"running-left mirrored output hash does not match imagegen-jobs.json; \"\n            \"rerun derive_running_left_from_running_right.py\"\n        )\n    with Image.open(source) as source_image, Image.open(output) as output_image:\n        expected = ImageOps.mirror(source_image.convert(\"RGBA\"))\n        actual = output_image.convert(\"RGBA\")\n        if expected.size != actual.size or expected.tobytes() != actual.tobytes():\n            raise SystemExit(\n                \"running-left mirrored output is not an exact horizontal mirror of running-right\"\n            )\n\n","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/hatch-pet/scripts/finalize_pet_run.py#L86-L122","documentation":"Raised by validate_mirror_hash when the running-left mirror job's source_path does not point at decoded/running-right.png. The deterministic mirror is only valid when it derives from the canonical right-facing strip, so the script enforces both filename and parent directory name.","triggerScenarios":"job['source_path'] for the running-left job resolves to a file whose .name != 'running-right.png' or whose parent .name != 'decoded'. Happens when the manifest was hand-edited or the source was copied to a different name/location.","commonSituations":"Editing imagegen-jobs.json to repoint the mirror source; renaming the decoded directory; running a derive script that wrote running-right.png into raw/ instead of decoded/.","solutions":["Set the running-left job's source_path to decoded/running-right.png (relative to run_dir) in imagegen-jobs.json.","Ensure the decoded/ directory exists and contains running-right.png produced by the base/running-right imagegen job.","Re-derive running-left via derive_running_left_from_running_right.py, which records the correct source path."],"exampleFix":"// before (imagegen-jobs.json)\n\"source_path\": \"raw/running-right.png\"\n// after\n\"source_path\": \"decoded/running-right.png\"","handlingStrategy":"validation","validationCode":"source = (run_dir / job['source_path']).resolve()\nif source.name != 'running-right.png' or source.parent.name != 'decoded':\n    raise ValueError('mirror source must be decoded/running-right.png')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never hand-edit the running-left job's source_path; let the derive script record it.","Keep the decoded/ directory name stable; do not rename it.","Validate manifest path conventions with a lint pass before finalizing."],"tags":["manifest","mirror","path-validation","pet-pipeline"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}