# task-2517 — canonical_workspace_resolver (P0 — runtime determinism 핵심)

- 작업 유형: **runtime infrastructure hardening + 회귀 테스트** (정책 문서 X)
- 작업 레벨: **Lv.3+** (시스템 runtime 단일화)
- 우선순위: **★★★ P0 highest-priority** (현재 전체 시스템의 최상위 hardening)
- Track: **canonical_workspace_resolver / runtime_determinism / cwd_ambiguity_removal / phase2_hardening**
- parallel_policy: **serial_only** (회장 명시 — task-2518/2519 wiring/merge는 본 task 완료 후)
- 일시: 2026-05-09
- 회장 결정: 2026-05-09 직접 — Phase 2 Runtime Infrastructure Hardening 전환

## ⚠️ 본 task의 본질 — 회장 명시

> 5 모듈 구현 자체는 완료되었다. 이제 병목은 기능 부족이 아니라
> **runtime determinism / repository governance / lifecycle consistency**다.
>
> 본 task는 P0 최우선 serial. **현재 전체 시스템의 highest-priority runtime hardening task**.
>
> task-2517 완료 전에는 finish-task / scope-guard / qc / smoke / merge execution의
> workspace ambiguity가 계속 발생할 수 있다.
>
> **앞으로는 "문서 보강"보다 "runtime determinism 확보 코드" 우선순위를 높인다.**

## 시스템 상태

- ✅ **CODE_AUTOMATION_SYSTEM_PHASE_1_COMPLETE** (5 모듈 + wiring + e2e)
- 🟡 **Phase 2 Runtime Infrastructure Hardening 시작** (본 task = P0 #1)
- ⚠ task-2516+1 진행 중 (dev3, 병렬 — expected_files disjoint)
- 🔒 task-2518/2519 wiring/merge는 본 task 완료 후

## task-2516+1에서 노출된 4 ambiguity (구조적 제거 대상)

회장 명시 — 본 task가 구조적으로 제거해야 할 4 ambiguity:
1. **wrong cwd** (작업 위치 모호, 어느 worktree/main에서 명령 실행 중인지 불명확)
2. **stale main** (origin/main 최신 HEAD vs 로컬 main 불일치, fetch 누락)
3. **dirty workspace false detection** (다른 task의 unstaged 파일이 본 task scope 검증에 영향)
4. **finish-task context mismatch** (finish-task 호출 시점의 worktree와 검증 시점 불일치)

## dependency (모두 충족)

- ✅ task-2509.merged (PR #58)
- ✅ task-2509+1.merged (PR #59)
- ✅ task-2509+2.merged (PR #60, automation_contracts.py freeze)
- ✅ task-2510.merged (PR #61, replacement_pr_runner)
- ✅ task-2511.merged (PR #62, auto_gemini_triage)
- ✅ task-2512.merged (PR #64, post_merge_smoke_runner)
- ✅ task-2513.merged (PR #63, critical_escalation_reporter)
- ✅ task-2514.merged (PR #65, 5 모듈 wiring)
- ✅ task-2515.merged (commit `05259f81`, e2e replay harness)

## Merge Topology Gate metadata

```yaml
expected_files:
  - "utils/canonical_workspace_resolver.py"  # NEW
  - "tests/regression/test_canonical_workspace_resolver_2517.py"  # NEW

risk_area: "runtime_determinism / canonical_workspace / cwd_ambiguity / scope_gate / finish_task / smoke / qc"

dependency:
  - "task-2509.merged"
  - "task-2509+2.merged"
  - "task-2514.merged"
  - "task-2515.merged"

parallel_policy: "serial_only"

merge_queue_position: 15

stale_recheck_required: true

cherry_pick_allowed: false
```

## 본 task의 책임 6건 (회장 §목적)

1. **stale workspace 제거** — 이전 task worktree/branch 잔재 자동 탐지 + 필요 시 보존하되 현재 task와 격리
2. **cwd ambiguity 제거** — 어느 시점에 어느 worktree/main에서 명령 실행 중인지 deterministic 식별
3. **PROJECT_PATH/worktree/main 결정 deterministic화** — 환경변수 + git rev-parse + git worktree list 통합
4. **diff base 단일화** — `origin/main` HEAD를 모든 scope/diff 검증의 single source of truth
5. **scope gate / finish-task / guard / smoke / qc 단일 workspace** — 6 hook 모두 동일 canonical workspace 사용
6. **task-2516+1 4 ambiguity 구조적 제거** — wrong cwd / stale main / dirty workspace false detection / finish-task context mismatch 모두 회귀 fixture로 PASS

## 필수 구현 7건

### 1. CanonicalWorkspace dataclass

```python
@dataclass(frozen=True)
class CanonicalWorkspace:
    task_id: str                    # task-NNNN[+M] 형식
    workspace_root: Path            # 항상 main repo root (e.g., /home/jay/workspace)
    worktree_path: Path             # task별 worktree (e.g., .worktrees/task-NNNN-devX)
    branch_name: str                # task/task-NNNN-devX
    main_head_sha: str              # origin/main HEAD SHA (fetch 후 lock)
    base_sha: str                   # diff base (origin/main HEAD)
    cwd: Path                       # 현재 명령 실행 cwd (worktree_path와 일치 필수)
    is_main: bool                   # cwd가 main인지 worktree인지
    is_clean: bool                  # 본 task scope 안에서만 dirty 평가
```

### 2. resolve_canonical_workspace(task_id) entry

- `git rev-parse --show-toplevel`로 cwd 정규화
- `git worktree list --porcelain`으로 worktree 목록 추출
- task_id별 worktree 매칭 (없으면 worktree_path만 산출, 생성은 caller 책임)
- `git fetch origin main` 선행 + `git rev-parse origin/main`으로 main_head_sha lock
- 환경변수 `WORKSPACE_ROOT` / `PROJECT_PATH` 우선순위 명시 + 충돌 시 git 우선

### 3. assert_cwd_matches_workspace(ws)

- 현재 cwd가 ws.worktree_path 또는 ws.workspace_root와 일치하지 않으면 RuntimeError
- task-2516+1 §wrong cwd 직접 해소

### 4. assert_main_fresh(ws)

- ws.main_head_sha가 origin/main HEAD와 일치 (stale 5초 이상 차이 시 FAIL)
- task-2516+1 §stale main 직접 해소

### 5. evaluate_scope_dirty(ws, expected_files)

- expected_files 안에서만 dirty 평가
- 다른 task의 unstaged 파일은 NOT in scope → 무시
- task-2516+1 §dirty workspace false detection 직접 해소

### 6. assert_finish_task_context(ws, finish_target)

- finish-task 호출 시점의 worktree와 검증 시점 워크트리 일치 검증
- HEAD SHA + branch_name + worktree_path 3축 lock
- task-2516+1 §finish-task context mismatch 직접 해소

### 7. 6 hook 통합 인터페이스

- `scope-guard` / `finish-task` / `guard (pre-commit/pre-push)` / `smoke` / `qc` / `merge_execution` 모두
  `resolve_canonical_workspace()` 호출로 시작
- 6 hook이 동일 CanonicalWorkspace 인스턴스 공유 (caller가 한 번만 resolve)
- 본 task는 인터페이스만 제공, 실제 hook 통합은 task-2518/2519 wiring 단계 또는 후속

## CLI entrypoint

`python3 utils/canonical_workspace_resolver.py --task-id <id> --json`
- 출력: `CanonicalWorkspace` JSON
- `--assert-cwd` / `--assert-fresh` / `--scope expected_files=...` 옵션
- `--no-fetch` (테스트용, fetch skip)

## 필수 회귀 테스트 14건

`tests/regression/test_canonical_workspace_resolver_2517.py`:

### 정상 흐름 (4건)
1. main repo에서 resolve → workspace_root == cwd, is_main=True
2. worktree에서 resolve → worktree_path == cwd, branch_name 매칭
3. main_head_sha lock (fetch 후 origin/main HEAD 정확 매칭)
4. 환경변수 WORKSPACE_ROOT 우선순위 (git rev-parse와 충돌 시 git 우선)

### 4 ambiguity 회귀 (4건 — 회장 §직접 해소 대상)
5. **wrong cwd** — 잘못된 cwd → assert_cwd_matches_workspace RuntimeError
6. **stale main** — fetch 후 origin/main 변경 → assert_main_fresh FAIL
7. **dirty workspace false detection** — expected_files 외 파일 dirty → evaluate_scope_dirty IS_CLEAN
8. **finish-task context mismatch** — HEAD SHA 변경 후 finish_target 불일치 → assert_finish_task_context RuntimeError

### Edge case (3건)
9. worktree 없을 때 resolve (worktree_path만 산출, 생성 X)
10. 환경변수 PROJECT_PATH stale 시 git 우선 동작
11. CanonicalWorkspace JSON serialization (round-trip 무결성)

### 6 hook 통합 (3건)
12. scope-guard hook이 resolve_canonical_workspace 호출 → 동일 ws 공유
13. finish-task hook이 동일 ws 공유 → context mismatch 0
14. smoke/qc hook이 동일 main_head_sha 사용

## 금지 행위 (회장 명시 — 절대 준수)

- **새 abstraction 과다 생성 금지** (CanonicalWorkspace dataclass 1개 + resolver 함수만, helper module 추가 X)
- **enum redesign 금지** (`utils/automation_contracts.py` 미변경)
- **contract schema 변경 금지**
- **policy md 보강 금지** (회장 명시 — runtime determinism 코드 우선)
- **dispatch.py 수정 금지**
- **5 모듈 본체 수정 금지** (`merge_queue_executor.py` / `replacement_pr_runner.py` / `auto_gemini_triage.py` / `post_merge_smoke_runner.py` / `critical_escalation_reporter.py` / `merge_topology_gate.py` 모두 미변경)
- **task-2516/2516+1 영역 침범 금지** (W1 fix는 task-2516+1 영역)
- **task-2518/2519 wiring 직접 시도 금지** (본 task = 인터페이스만 제공, wiring은 별도 task)
- **force push / rebase / admin override / manual .done 금지**
- **required CI bypass 금지**
- **PR #52/#49/#50/#51 수정 금지**
- **expected_files 외 수정 금지** (정확히 2 파일)
- **자동 cherry-pick 구현 금지**
- **live pilot 직접 시도 금지**
- **Critical 7종 외 회장 보고 금지**
- **amendment 무시 / mid-dispatch correction 무시 금지**

## allowed_resources

```yaml
allowed_resources:
  read_only_paths:
    - "memory/tasks/task-2509*"
    - "memory/tasks/task-2510*"
    - "memory/tasks/task-2511*"
    - "memory/tasks/task-2512*"
    - "memory/tasks/task-2513*"
    - "memory/tasks/task-2514*"
    - "memory/tasks/task-2515*"
    - "memory/tasks/task-2516*"
    - "memory/tasks/task-2517*"
    - "memory/feedback/feedback_critical_escalation_only_260508.md"
    - "utils/automation_contracts.py"          # READ ONLY
    - "utils/merge_queue_executor.py"          # READ ONLY (hook 통합 분석용)
    - "utils/replacement_pr_runner.py"         # READ ONLY (worktree 패턴 분석용)
    - "utils/auto_gemini_triage.py"            # READ ONLY
    - "utils/post_merge_smoke_runner.py"       # READ ONLY
    - "utils/critical_escalation_reporter.py"  # READ ONLY
    - "utils/merge_topology_gate.py"           # READ ONLY
    - "scripts/start_task_guard.py"            # READ ONLY (guard hook 분석)
    - "scripts/finish-task.sh"                 # READ ONLY (finish-task hook 분석)
    - "memory/orchestration-audit/merge-queue.jsonl"
    - "memory/task-timers.json"
    - ".env.keys"
  paths:
    - "memory/tasks/task-2517*"
    - "memory/reports/task-2517*"
    - "memory/events/task-2517*"
    - "utils/canonical_workspace_resolver.py"                          # NEW
    - "tests/regression/test_canonical_workspace_resolver_2517.py"     # NEW
  forbidden_actions:
    - "새 abstraction 과다 생성"
    - "enum redesign"
    - "contract schema 변경"
    - "policy md 보강"
    - "dispatch.py 수정"
    - "5 모듈 본체 수정"
    - "task-2516/2516+1 영역 침범 (replacement_pr_runner.py 수정)"
    - "task-2518/2519 wiring 직접 시도"
    - "force push"
    - "rebase"
    - "admin override (gh pr merge --admin)"
    - "manual .done 생성"
    - "required CI bypass"
    - "PR #52/#49/#50/#51 수정"
    - "expected_files 외 수정"
    - "자동 cherry-pick 구현"
    - "live pilot 직접 시도"
    - "Critical 7종 외 회장 보고"
    - "amendment 무시 / mid-dispatch correction 무시"
```

## 완료 조건 (회장 명시)

1. ✅ `utils/canonical_workspace_resolver.py` 신규 (CanonicalWorkspace + 7건 구현)
2. ✅ 회귀 14/14 PASS (정상 4 + 4 ambiguity 직접 해소 4 + edge 3 + 6 hook 통합 3)
3. ✅ task-2516+1 §4 ambiguity 모두 회귀 fixture로 PASS
4. ✅ 6 hook(scope-guard / finish-task / guard / smoke / qc / merge_execution) 통합 인터페이스 정의
5. ✅ Critical 7종 외 회장 보고 0건
6. ✅ Merge Topology Gate 자기참조 PASS (effective diff = 정확히 2 파일)
7. ✅ CI 11/11 SUCCESS
8. ✅ forbidden path 0
9. ✅ 5 모듈 본체 / dispatch.py / automation_contracts 변경 0건
10. ✅ amendment 보호 의무 명시 + 적용 evidence

## 시스템 3문서 참조

- 정책 본체: `memory/feedback/feedback_critical_escalation_only_260508.md`
- 5 모듈 (READ ONLY): `utils/{merge_queue_executor, replacement_pr_runner, auto_gemini_triage, post_merge_smoke_runner, critical_escalation_reporter}.py`
- task-2516+1 4 ambiguity 사례 (회귀 fixture 입력)

## 후행 (회장 명시 P1/P2)

본 task 완료 후:
- **P1 (병렬 가능)**:
  - task-2518 — lifecycle_reconciliation_manager
  - task-2519 — repository_policy_adapter
  - 단, spec/테스트 설계까지만 병렬, **wiring/merge는 본 task 완료 후 serial**
- **P2**:
  - task-2520 — low-risk live pilot
  - task-2521 — automation observability/dashboard

## affected_files (auto-detected)
- utils/canonical_workspace_resolver.py (NEW)
- tests/regression/test_canonical_workspace_resolver_2517.py (NEW)

## goal_assertions (auto-generated)
- `python3 utils/canonical_workspace_resolver.py --task-id task-2517 --json`
- `pytest tests/regression/test_canonical_workspace_resolver_2517.py -q` → 14/14 PASS
- `python3 -c "from utils.canonical_workspace_resolver import CanonicalWorkspace, resolve_canonical_workspace; print('import ok')"`