{"record":{"id":"24c2d05ee326bd18","repo":"nexu-io/open-design","slug":"running-left-is-not-configured-for-conditional-mir","errorCode":null,"errorMessage":"running-left is not configured for conditional mirroring","messagePattern":"running-left is not configured for conditional mirroring","errorType":"exception","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"skills/hatch-pet/scripts/derive_running_left_from_running_right.py","lineNumber":91,"sourceCode":"    parser.add_argument(\"--force\", action=\"store_true\")\n    args = parser.parse_args()\n\n    if not args.confirm_appropriate_mirror:\n        raise SystemExit(\"refusing to mirror without --confirm-appropriate-mirror\")\n    if not args.decision_note.strip():\n        raise SystemExit(\"--decision-note must explain why mirroring is appropriate\")\n\n    run_dir = Path(args.run_dir).expanduser().resolve()\n    manifest_path = run_dir / \"imagegen-jobs.json\"\n    manifest = load_manifest(run_dir)\n    right_job = find_job(manifest, \"running-right\")\n    left_job = find_job(manifest, \"running-left\")\n\n    if right_job.get(\"status\") != \"complete\":\n        raise SystemExit(\"running-right must be complete before deriving running-left\")\n    mirror_policy = left_job.get(\"mirror_policy\")\n    if not isinstance(mirror_policy, dict) or mirror_policy.get(\"may_derive_from\") != \"running-right\":\n        raise SystemExit(\"running-left is not configured for conditional mirroring\")\n\n    source = run_dir / \"decoded\" / \"running-right.png\"\n    output = run_dir / \"decoded\" / \"running-left.png\"\n    if not source.is_file():\n        raise SystemExit(f\"running-right decoded strip not found: {source}\")\n    if output.exists() and not args.force:\n        raise SystemExit(f\"{output} already exists; pass --force to replace it\")\n\n    output.parent.mkdir(parents=True, exist_ok=True)\n    with Image.open(source) as image:\n        mirrored = ImageOps.mirror(image.convert(\"RGBA\"))\n        mirrored.save(output)\n\n    left_job[\"status\"] = \"complete\"\n    left_job[\"source_path\"] = manifest_relative(source, run_dir)\n    left_job[\"source_provenance\"] = \"deterministic-mirror\"\n    left_job[\"derived_from\"] = \"running-right\"\n    left_job[\"source_sha256\"] = file_sha256(source)","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/skills/hatch-pet/scripts/derive_running_left_from_running_right.py#L73-L109","documentation":"Mirroring is opt-in per pet. The running-left job in imagegen-jobs.json must carry a mirror_policy object whose may_derive_from equals the literal \"running-right\". Without this declaration the script treats mirroring as unapproved for this pet, because some pets are asymmetric (props, markings, handedness) and must be drawn independently.","triggerScenarios":"The running-left job has no mirror_policy key, mirror_policy is not a dict, or mirror_policy[\"may_derive_from\"] is set to a value other than \"running-right\".","commonSituations":"A pet run created before mirror_policy was introduced; a manifest hand-edited to strip the policy; an asymmetric pet that was never meant to mirror but someone is trying to shortcut it.","solutions":["Visually confirm the pet is actually symmetric enough that mirroring running-right produces a correct left strip.","If symmetric, add \"mirror_policy\": {\"may_derive_from\": \"running-right\"} to the running-left job in imagegen-jobs.json.","If asymmetric, abandon mirroring and generate running-left independently via $imagegen + record_imagegen_result.py.","Re-run the derive script."],"exampleFix":"// before (running-left job)\n{\n  \"id\": \"running-left\",\n  \"status\": \"pending\"\n}\n// after\n{\n  \"id\": \"running-left\",\n  \"status\": \"pending\",\n  \"mirror_policy\": { \"may_derive_from\": \"running-right\" }\n}","handlingStrategy":"type-guard","validationCode":"import json\nfrom pathlib import Path\n\nrun_dir = Path(\"<run_dir>\")\nmanifest = json.loads((run_dir / \"imagegen-jobs.json\").read_text())\nleft = next(j for j in manifest[\"jobs\"] if j.get(\"id\") == \"running-left\")\npolicy = left.get(\"mirror_policy\")\nassert isinstance(policy, dict) and policy.get(\"may_derive_from\") == \"running-right\", \\\n    \"running-left mirror_policy.may_derive_from must be 'running-right'\"","typeGuard":"def mirror_policy_ok(left_job: dict) -> bool:\n    policy = left_job.get(\"mirror_policy\")\n    return isinstance(policy, dict) and policy.get(\"may_derive_from\") == \"running-right\"","tryCatchPattern":null,"preventionTips":["Decide mirroring up front when creating the pet run and bake mirror_policy into the manifest template.","Do not remove mirror_policy when editing the manifest.","For asymmetric pets, never set may_derive_from; generate running-left via $imagegen instead."],"tags":["hatch-pet","manifest","mirror-policy","config"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}