{
  "schema": "replacement_pr_v3_gap_closure.result.v1",
  "track": "Track F — replacement PR v3 gap closure plan",
  "ts_kst": "2026-05-21 05:02 KST",
  "mode": "read-only · dry-run · proposal-only",
  "8_chair_check_items": {
    "1_utils_replacement_pr_runner_empty_state_reconfirmed": {
      "verdict": "CONFIRMED_EMPTY",
      "evidence": "wc -l: 0 lines · stat: 0 bytes (mtime 2026-05-20 13:12:57 KST · task-2620 era)",
      "interpretation": "v3 utils 경로의 stub 파일. 실 구현 0. import 시 module exist 하나 ReplacementPRRunner symbol 미존재"
    },
    "2_anu_v2_replacement_pr_runner_legacy_inventory": {
      "verdict": "FULLY_IMPLEMENTED_V2",
      "lines": 442,
      "bytes": 18695,
      "mtime_kst": "2026-05-10 23:06:28",
      "lineage_anchor": "task-2537 (ANU v2 자동화 5 모듈 시리즈 2번째)",
      "key_artifacts": {
        "classes": [
          "ContaminationReport (L67) — extra_files/missing_files 판정",
          "PreservationRecord (L82) — original PR OPEN 유지 + audit jsonl",
          "ReplacementResult (L91) — clean_sha/clean_branch/merge_queue_ready",
          "ReplacementFailure (L108) — stage/reason/extra",
          "ReplacementPRRunner (L116) — 본체 클래스"
        ],
        "methods": [
          "detect_contamination (L146) — expected vs actual diff set 비교",
          "preserve_original_pr (L172) — gh/git 호출 0 · audit only · original OPEN 유지",
          "create_clean_replacement_pr (L195) — 6 stage (branch/fetch/checkout/commit/push/pr_create)",
          "classify_failure (L382) — Critical7 매핑 (REPLACEMENT_PR_ALSO_FAILED · EFFECTIVE_DIFF_CONTAMINATION_REPLACEMENT_FAILED)",
          "build_executor_contract (L402) — replacement_pr_required / replacement_pr_runner_input dict"
        ],
        "constants": ["REPLACEMENT_PR_CREATED (L41)", "REPLACEMENT_PR_FAILED (L42)", "ORIGINAL_PR_PRESERVED", "CRITICAL_REPLACEMENT_FAILED", "CRITICAL_DIFF_REPLACEMENT_FAILED"]
      },
      "design_principles_v2": [
        "one-way isolation: anu_v2/* 만 import (utils/dispatch/scripts/dashboard 의존성 0)",
        "외부 부수효과 모두 callable 주입 (gh_runner/git_runner/audit_writer)",
        "admin override / force / rebase / owner_pat fallback / manual .done 일체 금지",
        "BOT_GITHUB_TOKEN process-local env injection (env 격리)",
        "executor contract dict 키 2개만 약속 (replacement_pr_required / replacement_pr_runner_input)"
      ],
      "gemini_review_high_findings_already_absorbed": [
        "Gemini 2차 high #1: BOT identity git commit env 명시 주입 (GIT_AUTHOR_*, GIT_COMMITTER_*)",
        "Gemini 2차 high #2: refs/pull/{N}/head 선행 fetch (clone 자동 fetch X)",
        "Gemini 1차 medium: stage 개별 N회 → 일괄 1회 (subprocess overhead 절감)"
      ]
    },
    "3_v3_minimal_adapter_wrapper_scope": {
      "verdict": "PROPOSAL — utils/replacement_pr_runner.py = thin re-export shim",
      "minimum_scope": "from anu_v2.replacement_pr_runner import * (또는 명시 symbol 7개 + __all__)",
      "rationale": "v2 442 lines 가 본질 4축 완비. v3 era 의 변경 사항은 callback contract chaining 만이며, contract 는 build_executor_contract 메서드의 dict 형식 통일로 이미 inversion of control 형태. v3 변경 0, wrapper 만 결선",
      "alternative_proposals": {
        "A_thin_reexport_shim": {
          "scope_loc": "≤ 15 lines (re-export + __all__ + docstring)",
          "risk": "LOW",
          "v3_compat_evidence": "callback contract 결선은 dispatch/__init__.py task-2621 wiring 으로 이미 완료. replacement PR runner 는 본체 변경 0"
        },
        "B_v3_full_module_clone": {
          "scope_loc": "~450 lines · v3 namespace 분리",
          "risk": "MEDIUM (v2 ↔ v3 drift 발생 위험)",
          "v3_compat_evidence": "drift 차단 위해 v2 ↔ v3 sync test 별도 필요"
        }
      },
      "recommendation": "A — thin re-export shim. v2 가 sole source of truth · v3 utils namespace 호환만 보장."
    },
    "4_expected_files_forbidden_path_contamination_judgment_paths": {
      "verdict": "WIRED_via_v2_+_merge_queue_executor",
      "v2_internal": {
        "contamination": "anu_v2/replacement_pr_runner.py:detect_contamination (L146) — set 비교 (actual vs expected)",
        "extra_files": "scope 확장 → contaminated=True",
        "missing_files": "expected 누락 → contaminated=True"
      },
      "v3_dispatch_path_external": {
        "compare_effective_diff": "utils/merge_queue_executor.py:compare_effective_diff (L445)",
        "normalize_file_list": "utils/merge_queue_executor.py:_normalize_file_list (L441)",
        "detect_forbidden_paths": "utils/merge_queue_executor.py:detect_forbidden_paths (L473)"
      },
      "interpretation": "expected_files exact match + forbidden path + effective diff contamination 판정은 merge_queue_executor 가 sole 결정자. replacement_pr_runner 는 contamination 발견 시 정정 경로(clean PR 생성) 만 담당. 분리 결선 OK"
    },
    "5_replacement_pr_failure_critical7_fixture_status": {
      "verdict": "DESIGNED_+_v2_WIRED",
      "fixture_design": "memory/events/replacement_pr_failure_critical7_fixture.json (RPF1~RPF8)",
      "v2_critical7_mapping": {
        "downstream_stages_failure": "{branch, checkout, commit, push, pr_create} → CRITICAL_REPLACEMENT_FAILED (Critical7 #N)",
        "precondition_failure": "{bot_token, precondition, rev_parse, fetch} → CRITICAL_DIFF_REPLACEMENT_FAILED (Critical7 #N)"
      },
      "regression_test_proposal": "tests/regression/test_replacement_pr_failure_critical7.py (신규) — mock-based · 실 GitHub API 0 · subprocess 0"
    },
    "6_original_pr_preservation_evidence": {
      "verdict": "WIRED",
      "method": "preserve_original_pr (L172) — gh/git 호출 0 · audit jsonl append only · OPEN 유지 명시",
      "audit_path_pattern": "{audit_root}/replacement_pr_<pr>.jsonl (append-only)",
      "doctrine_link": "feedback_owner_trigger_only_capability_doctrine_260511 · original PR 변조 시도 → Critical7 ORIGINAL_PR_TAMPERING fixture RPF6"
    },
    "7_same_branch_push_blocked_evidence": {
      "verdict": "WIRED_TRIPLE_GUARD",
      "guards": [
        "anu_v2 design: clean_branch_name 새 branch 강제 (L247 git checkout -b)",
        "anu_v2 design: gh_args --head=clean_branch_name 명시 (L339-345)",
        "merge_queue_executor: assert_no_forbidden_git_flags (L303) — force/rebase/admin 차단"
      ],
      "fixture_link": "RPF5 — same-branch push 시도 → FAIL_CLOSED"
    },
    "8_dry_run_output_schema_proposal": {
      "verdict": "DEFINED",
      "schema_template": {
        "result_kind": "replacement_pr_dry_run.v1",
        "task_id": "string",
        "ts_kst": "ISO8601",
        "input": {
          "original_pr": "int",
          "expected_files": "list[str]",
          "actual_diff_files": "list[str]"
        },
        "contamination": {"contaminated": "bool", "extra_files": "list[str]", "missing_files": "list[str]"},
        "decision": {"replacement_pr_required": "bool", "expected_clean_branch_name": "string"},
        "simulated_stages": ["branch", "fetch", "checkout", "commit", "push", "pr_create"],
        "expected_outcome": "ReplacementResult | ReplacementFailure",
        "critical7_classification": "string | null",
        "scope_invariants_preserved": ["list"]
      },
      "actual_execution_target": "utils/replacement_pr_runner.py v3 shim (재export) 또는 anu_v2 직접 호출 · 둘 다 mock runner 주입 형태"
    }
  },
  "v3_gap_summary": {
    "v2_functional_coverage": "100% — 4본질축 모두 결선 (contamination 감지 / original 보존 / clean replacement 생성 / 실패 Critical7 매핑)",
    "v3_path_gap": "utils/replacement_pr_runner.py = 0 lines stub. v3 namespace 호환만 부재 · 본질 코드 부재 아님",
    "blast_radius_if_left_alone": "LOW — anu_v2 가 sole source 로 작동. dispatch/__init__.py task-2621 wiring 이 contract 결선 완료. live PR open 시점에 import 경로 명확화만 필요",
    "minimal_closure_cost": "≤ 15 LOC (thin re-export shim) + 1 unit test (import smoke)"
  },
  "scope_invariants_preserved": [
    "read-only audit", "dry-run", "proposal-only",
    "실 PR open 0", "branch/commit/push 0", "merge 0",
    "GitHub write 0", "credential raw exposure 0",
    "Track C 미접촉", "zombie cron 미접촉",
    "CLOSED_ALL_SETTLED 산출물 byte-0"
  ]
}
