---
task_id: task-2700
type: context
scope: task
created: 2026-05-27
updated: 2026-05-27
status: completed
---

# 맥락 노트: task-2700

**task**: task-2700

---

## 결정 근거

### 핵심 결정 1 — dispatch.py 핵심 변경 회피, 외부 pre-flight 모듈 설계
- 회장 doctrine: dispatch.py 전역 변경 금지. divergence HOLD 가 dispatch.py 핵심 수정을 요구하면 별도 회장 승인(HOLD_FOR_CHAIR).
- 결정: HOLD 로직을 `utils/divergence_guard.py` 독립 모듈로 구현. dispatch 측은 이 모듈을 import/CLI 로 호출만(또는 hook 연결). dispatch/__init__.py 코어 미수정.
- 기각된 대안: dispatch/__init__.py 내부에 divergence 체크 인라인 삽입 → doctrine 위반 + 4336줄 코어 회귀 위험.

### 핵심 결정 2 — divergence 측정 명령 + ahead/behind 의미 확정
- 회장 지정 명령: `git rev-list --left-right --count origin/main...HEAD`.
- 실측 검증(2026-05-27 20:55): 출력 `68\t6`, 교차검증 `origin/main..HEAD`=ahead 6, `HEAD..origin/main`=behind 68.
- **확정: left-right 출력의 left = behind(origin/main side), right = ahead(HEAD side)**. 모듈은 `behind, ahead = map(int, out.split())` 로 파싱.
- 현재 repo 도 diverged(ahead 6/behind 68) → 지금 coding dispatch 하면 HOLD 되는 것이 정상(설계 자체검증).

### 핵심 결정 3 — fail-closed HOLD, bypass flag 금지
- divergence 측정 실패(origin 도달 불가/fetch 실패 포함) 시에도 HOLD (fail-closed). 우회 플래그 미제공.
- coding/security/callback/finish-task 종류 task 만 HOLD 대상. read_only/diagnosis/문서 task 는 통과(과잉 차단 방지).

### 핵심 결정 4 — EXTERNAL_DIRTY_BLOCKER 는 차단 유지 + 분류만 분리 (own FAIL 불변)
- 요구 8: unrelated dirty 는 task 실패 아님 → `.failed`(task 책임) 대신 `EXTERNAL_DIRTY_BLOCKER` 마커(환경 책임)로 분류.
- 요구 11: own dirty(expected_files ∩ dirty ≠ 0)는 기존 FAIL 경로 유지.
- 차단 자체(no .done)는 fail-closed 로 유지 — bypass 아님. "누가 책임인가(분류)"만 분리. finish-task.sh 는 additive 확장(코어 보존).

### 핵심 결정 5 — worktree origin/main SHA 강제 + base SHA marker + spawn 검증
- 현 `cmd_create` 는 `git worktree add -b branch wt_path` (로컬 HEAD base) → stale base 사고 근원.
- 결정: `git fetch origin` 후 origin/main SHA 해석 → `git worktree add -b branch wt_path <origin_main_sha>`. base SHA 를 `memory/events/<task_id>.worktree-base.json` 마커에 기록.
- `verify_spawn_base`: 마커 base_sha 가 현재 origin/main SHA 와 불일치(stale)면 FAIL → PR #158 류 stale base PR 사전 차단.
- 하위호환: origin remote 없는 로컬 repo 는 graceful fallback(기존 동작) — 단 enforce 플래그 default True.

## 3 Step Why 자문 (G1)

- **1st Why** — "왜 이 설계(외부 pre-flight 모듈 + origin/main SHA worktree + dirty 분리 진단)가 필요한가?"
  → **A**: task-2699 처럼 로컬 main divergence + stale base worktree + 외부 dirty 가 동시에 발생하면 봇 산출이 정상이어도 PR CONFLICTING + GIT-GATE 차단 + callback 미발사로 작업이 환경 때문에 죽는다. divergence 를 dispatch 전에 fail-closed 로 잡고, worktree base 를 origin/main 으로 강제하고, dirty 책임을 분리하면 이 3중 실패를 원천 차단한다.
- **2nd Why** — "왜 A(외부 모듈 + fail-closed)가 최선의 접근인가? 대안은?"
  → **B**: 대안① dispatch.py 코어 인라인 = doctrine 위반 + 4336줄 회귀 위험. 대안② 사후 복구(fresh re-extract) = 사고 발생 후 수습이라 재발 방지 아님. 대안③ HOLD 를 warn-only = 봇이 무시하고 진행해 동일 사고 재현. → fail-closed 외부 모듈은 (a) 코어 불변 (b) 사전 예방 (c) 우회 불가 세 조건을 모두 만족하므로 최선.
- **3rd Why** — "왜 B(fail-closed 외부 모듈)가 다른 대안보다 나은가?"
  → **C**: fail-closed 는 측정 실패 시에도 안전측(HOLD)으로 귀결돼 "측정 못 해서 그냥 통과"하는 silent 사고를 막는다. 외부 모듈은 import/CLI/hook 어디서든 재사용 가능해 dispatch·finish·worktree 세 지점에 동일 로직을 코어 수정 없이 주입한다. 분리 진단(own vs external)은 봇을 부당하게 FAIL 처리(요구 11 위반)하지 않으면서 환경 블로커를 정확히 지목해 운영자가 올바른 복구(origin sync)를 하게 한다.
- **A-B-C 일관성 점검**: A(3중 실패 차단 필요) → B(코어불변·사전예방·우회불가 필요) → C(fail-closed·재사용·정확귀책) — 논리 일관 ✔. 설계 확정.

## G2 검증 결정 (마아트 독립검증 대응)

- 마아트 판정: CONDITIONAL PASS (High 0, Medium 1, Low 2).
- **L1 (--fail-open CLI 플래그)** → 제거 결정: 회장 "divergence HOLD bypass flag 금지(fail-closed 유지)"의 직접 위반 surface. CLI는 항상 fail-closed 강제. 내부 `fail_closed` 파라미터는 테스트/API용으로만 유지(운영 노출 X). 회귀 테스트 `cli_rejects_fail_open_bypass_flag` 추가.
- **M1 (dispatch 통합 hookup 누락)** → pre-flight 훅 스크립트로 대응: dispatch.py 코어 변경 금지 doctrine상 의도된 갭. `scripts/pre_dispatch_divergence_guard.sh`로 "dispatch 전" 측정+snapshot 진입점 제공(요구 1/2/6). 라이브 dispatch 경로(hooks/wrapper) 연결은 dispatch.py/hooks 영역이라 회장 승인(HOLD_FOR_CHAIR)으로 분리 — 이번 범위는 진입점 제공까지.
- **L2 (merge-base WARN-only)** → 유지: 스펙 "검증"에 차단 미명시 + 자기 worktree가 의도적 로컬 HEAD 기반(stale 가능)이라 차단 시 자기모순. WARN+마커로 가시화가 적정.

## 머지 전략 결정 (stale-base PR 회피)

- 워크스페이스 repo divergence(작업 시점 ahead6/behind68) + 대상 파일(finish-task.sh 355줄·worktree_manager.py 33줄)이 로컬 HEAD에서 origin/main보다 최신 → worktree를 **로컬 HEAD 기반**으로 생성(예외). origin/main 기반이면 최신 코어 손실.
- 결과적으로 origin/main 대비 diff가 479파일로 부풀려짐 = 이 task가 예방하려는 stale-base PR 안티패턴. 따라서 **PR 생성 안 함**. 순수 변경분은 `git diff f14b3850..HEAD`=7파일 +1798/-7.
- 회장 권장 경로: 로컬 main divergence 해소(원격 sync) 후 origin/main 기준 재적용(task-2699 박제 Option-1 fresh re-extract 패턴).

## 참조 자료

- task-2699 박제: `memory/events/task-2699-callback-missing-main-dirty-deepcheck-plan-260527.json`
- 기존 GIT-GATE: `scripts/finish-task.sh` L553-619
- 기존 scope guard: `scripts/task-scope-guard.sh`
- worktree create 버그 지점: `scripts/worktree_manager.py` L337-348 (`worktree add -b` 로컬 HEAD base)
- capability snapshot 구조: `memory/capabilities/<task_id>.json` (allowed_resources.paths)

## 주의사항

- ★ finish-task.sh 는 live 완료 경로 — additive only. 기존 PASS 동작 변경 금지, 새 분류/마커만 추가.
- ★ dispatch/__init__.py 미접촉 (보고 필드 9: 핵심 변경 0 보고).
- ★ own dirty FAIL 완화 절대 금지(요구 11). bypass flag 금지(fail-closed 불변).
- ★ 외부 AI(Codex/Gemini) 호출 전 sanitize 게이트: 코드/문서 PII 마스킹.
- ★ 이 작업 자체도 현재 main divergence 상태(ahead6/behind68)에서 진행 — worktree 산출은 PR 로, 머지는 회장 결재(no_merge_chair_approval_required).
