# 설계: START_TASK_GUARD_CANONICAL_BRANCH_ASSUMPTION_BLOCKER 해소 (task-2729+10)

- 작성일: 2026-06-06 (r2 개정: 2026-06-07)
- 작성: 페룬(개발6팀장) / 구현 스바로그(BE) · 검증 벨레스(QA)
- r2 개정: 오딘(개발2팀장) / 구현 토르(BE) · 검증 헤임달(테스터)
- 상태: completed (r2 — Gemini HIGH remediation 반영)
- 근거 backlog: `memory/events/start_task_guard_canonical_branch_assumption_blocker_backlog_260606.json`

## 0. r2 개정 — Gemini HIGH remediation (Option A replacement)

PR #185(head 8bed1fee)는 G1/G2 self-validated 이나 Gemini fresh **HIGH** 로 CLOSED(SUPERSEDED). same-PR push 금지 → fresh origin/main(8c5af051) 새 브랜치 `task/task-2729+10-r2-dev2` 로 교정.

- **HIGH (`finish-task.sh` worktree 탐지)**: `ls -d "$WORKSPACE/.worktrees/${TASK_ID}-"* >/dev/null 2>&1` 는 `nullglob` ON + 매칭 없을 때 빈 glob → `ls -d` 가 `.` 나열·return 0 → `_WT_ISOLATED=1` 오설정 → 무관 dirty 잘못 면제(fail-closed 위반).
  - **fix**: `ls -d` + nullglob 의존 제거. `_WT_ISOLATED=0` 기본값 + `for _wt in "$WORKSPACE/.worktrees/${TASK_ID}-"*; do [ -e "$_wt" ] && _WT_ISOLATED=1 && break; done`. nullglob ON/OFF 무관하게 매칭 worktree 없으면 반드시 0.
- **G2 면제 조건 (강화 유지)**: worktree 실제 존재(`[ -e ]`) + EXTERNAL_DIRTY_BLOCKER 일 때만 면제. OWN_DIRTY_FAIL/UNKNOWN/비격리 → fail-closed 유지(불변).
- **MEDIUM (test:356)**: 회귀 스니펫을 루프 방식으로 동기화 + nullglob 회귀 케이스(`test_g2_nullglob_no_worktree_blocks`) 추가 → HIGH 재발 방지.
- 회귀: 6 passed(기존 5 + nullglob 1). L1: OLD(`ls -d`)=1 vs NEW(loop)=0 대조로 fail-closed 실증. invariant(raw key0/ACTIVE=false/systemctl0/spawn0) 충족. canonical 무손상.

## 1. 문제 (Situation)
정상 격리 worktree task 가 canonical workspace(`/home/jay/workspace`) 의 branch 상태 때문에 false-block 된다.
canonical 이 `task/task-2716-...-dev4` 에 parked(non-main) + dirty 인 동안:
- `start_task_guard` 검증 #7 이 canonical 의 branch 가 `main` 이고 HEAD==origin/main 임을 요구 → parked 상태에서 FAIL.
- → `.tasks/locks/<task>.lock` 미생성 → pre-commit 차단 → commit 불가 → finish-task GIT-GATE(EXTERNAL_DIRTY_BLOCKER) → `.done` 0 → callback 미발사.
- 실증 반복: task-2729+5/+7/+8 매 dispatch 마다 ANU 가 lock 수동 복구.

## 2. 근본 원인 (Complication)
- 단일 root: **canonical workspace 가 task-2716 dirty branch 에 parked**.
- symptom_1 (stale base): #182 BASE_SOURCE_ISOLATION 으로 이미 해소(worktree fresh origin/main base).
- symptom_2 (commit/finalize block): **미해소** — guard #7 의 "canonical=main" 가정 + finish-task EXTERNAL_DIRTY_BLOCKER.

## 3. 방향 비교 (Question)
- **OPT-W (worktree-root 기준 검증, 채택)**: guard 가 worktree 자체(전용 경로 + branch + base 가 origin/main fresh 후손)를 검증. canonical branch/dirty 무관. #182 로 fresh base 보장되므로 worktree-root 검증만으로 충분·안전. canonical 무손상.
- OPT-M (canonical-main 독립 worktree 운용): 추가 worktree 운영 오버헤드·결선 복잡. 비채택.
- → **OPT-W 채택.**

## 4. 구현 (Answer)

### G1 — `scripts/start_task_guard.py` 검증 #7 재작성 (worktree-root 기준)
- 제거: `WORKSPACE_ROOT`(canonical) 의 `branch --show-current == "main"` 요구 및 canonical HEAD/origin 비교.
- 신규: **worktree(cwd)** 의 HEAD 와 origin/main 을 3-state 비교 (refs 는 worktree 가 canonical 과 공유):
  - A. `HEAD == origin/main` → PASS (fresh).
  - B. `git merge-base --is-ancestor origin/main HEAD` 성공 → PASS (origin/main 이 HEAD 의 ancestor = fresh base + own commits, ahead-only).
  - C. 그 외 (HEAD 가 origin/main 의 ancestor=behind/stale, 또는 diverged) → FAIL (`state: stale_or_diverged`).
- 효과: canonical 의 branch/parked/dirty 상태와 **완전 무관**하게 lock 정상 생성. stale/diverged base 는 여전히 차단(방어 유지).
- 검증 #1~#6, #8, #9 불변.

### G2 — `scripts/finish-task.sh` GIT-GATE EXTERNAL_DIRTY 면제 (worktree 격리 한정)
- dirty 블록 진입 직후 worktree 격리 감지: `.worktrees/<TASK_ID>-*` 존재 시 `_WT_ISOLATED=1`.
- 최종 `exit 1` 을 조건부화:
  - `classification == EXTERNAL_DIRTY_BLOCKER` AND `_WT_ISOLATED == 1` → **면제**(비차단) + `<task>.external-dirty-exempt.json` 마커. canonical 의 무관 dirty(다른 task)로 false-block 0.
  - 그 외(OWN_DIRTY_FAIL / UNKNOWN / 비격리) → 기존 `exit 1` (**fail-closed 유지**).
- 근거: backlog option C — "worktree isolation 인지 시 EXTERNAL_DIRTY_BLOCKER false-positive 면제".

### G3 (검토 결과: 변경 불요)
- pre-commit 의 lock 검증은 유지. G1 으로 lock 이 정상 생성되면 pre-commit 통과 → G3 수정 불요. (목표대로 `scripts/git-hooks/pre-commit` 수정 0)

## 5. 검증 (isolated temp git repo + production L1)
회귀 테스트 `tests/regression/test_start_task_guard_worktree_root_2729p10.py` (5 PASS):
1. TC1(필수검증 2): canonical=task-2716 parked+dirty + fresh worktree → #7 PASS + lock 생성.
2. TC2(필수검증 4): stale base worktree(behind) → #7 FAIL + lock 미생성 (방어 유지).
3. TC3(무회귀): canonical=main 정상 케이스 → #7 PASS.
4. TC4: `classify_blocker` 무관 dirty → EXTERNAL_DIRTY_BLOCKER.
5. TC5: G2 면제 결정(bash) — 격리+EXTERNAL→EXEMPT / 비격리→BLOCK / OWN_DIRTY→BLOCK.

기존 회귀 `test_guard7_local_operational_patch.py` 5 passed/2 skipped(불변) — **무영향**.

Production L1 (실제 parked canonical refs): canonical=`task/task-2716...`(non-main) 상태에서 수정 가드를 본 task worktree 에 실행 → 9개 검증 전부 PASS(#7: ahead-only fresh base) + lock 정상 생성. (수동 ANU lock 복구 불요 실증)

## 6. canonical 무손상
- 모든 git 검증은 worktree 또는 isolated temp repo 기준. canonical `/home/jay/workspace` 에 reset/clean/stash/checkout -f/branch 전환 **0회**. task-2716 branch 수정 0.

## 7. 산출 판정
- **DISPATCH_READY** — guard 가정 hardening 완료. 후속 task 의 commit/finalize/callback 정상화(ANU 수동 lock 복구 불요).
