# task-2726+3 보고서 — Round-3 bounded fix (STRICT mode SCOPE_BASE empty → fail-OPEN 제거, fail-CLOSED)

- 작업 ID: task-2726+3 (round-3, 회장 A안 — max_rounds=2 초과 명시 인가)
- 팀: dev3-team (다그다/팀장, 루/백엔드, 모리건/테스터)
- 작업 레벨: normal (Lv.2) · 게이트 Lv.2 (G1/G2/G3)
- 상태: **MERGE_APPROVAL_CANDIDATE (fix 완료·PR #171 갱신)** + **`.done` 보류 = EXTERNAL_DIRTY_BLOCKER(환경, ANU 회수 대상)**
- 일시: 2026-06-03 21:35~ KST

> ## ⚠ 완료 상태 (환경 블로커 — ANU/실장 조치 필요)
> 기술 산출물은 **완료**: fail-OPEN→fail-CLOSED 수정 + 35 회귀 PASS + L1 SMOKE PASS + PR #171 non-force push(head `9731cc4b`). scope 2파일(merge-base 기준) 검증.
> 단, `finish-task.sh`의 **`.done` 생성은 차단**됨 — 원인: **EXTERNAL_DIRTY_BLOCKER**. 공유 메인 워크스페이스(`/home/jay/workspace`, 현재 `task/task-2716-...dev4` 브랜치 체크아웃)에 **타 task(task-2510)의 stale uncommitted 파일 2건**(`utils/replacement_pr_runner.py`, `tests/regression/test_replacement_pr_runner_2510.py`)이 박혀 있어 GIT-GATE가 fail-closed(exit 1) 차단.
> - 이 파일들은 **내 task 책임 아님**(expected_files 외, dirty-registry가 EXTERNAL로 정확 분류). 타 작업 파일이라 **커밋/스태시/삭제 금지** 준수 → 손대지 않음.
> - `.done`/`.merge-done` **위조 금지**(시스템 contract: `safe_cron_dispatch` "manual forgery 금지"), `project_path` 지정 = 금지된 merge 유발 → 모두 회피.
> - **시스템 진단 일치**: `memory/events/task-2726+3.callback-cause.json` → cause=`FINISH_TASK_GIT_GATE_BLOCKED`, sub_cause=`EXTERNAL_DIRTY_BLOCKER`, **remediation="origin/main sync 또는 무관 dirty 정리 — task 재실행 불필요"**.
> - **ANU/실장 조치**: 공유 워크스페이스의 무관 dirty 2건 정리(또는 origin sync) → 멱등 마커(`.qc-done`+`.scope-guard-done` 이미 존재) 덕에 finish-task 재실행 시 GIT-GATE 통과 → `.done` + ANU callback 자동 완료.
> - merge 금지 준수(미수행) · STRICT 미활성화 · foreign 파일 미접촉.
- 상위 누적 보고서: `memory/reports/task-2726.md` (round-0/1/2/3 통합 이력)

---

## S (Situation)
PR #171(head `80416faa`) OWNER `/gemini review`(fresh, submittedAt 12:25:20Z > head 08:17:13Z)에서 fresh **HIGH 1건** 유효(코드 직접대조, FALSE_POSITIVE 아님):
`scripts/finish-task.sh` STRICT mode scope-gate(~556줄)에서 `SCOPE_BASE` 미감지(빈 값) 시 `: > "$SCOPE_DIFF_FILE"`(빈 diff 생성) → 이후 `if [ ! -s "$SCOPE_DIFF_FILE" ]` 분기로 **scope 검증 자동 스킵 = fail-OPEN**. task-2726 핵심 목표인 fail-closed 원칙 정면 위배 — 미검증 변경 통과 위험.

## C (Complication)
빈 diff 라인은 STRICT `if [ -n "$SCOPE_BASE" ]`의 `else` 분기 1줄. merge-base + `worktree-base.json` fallback이 **모두** 실패한 경우에만 도달. 단순 라인 삭제 시 `SCOPE_DIFF_FILE` 미생성으로 후속 로직 흐름이 달라질 수 있어, 빈 diff 통과를 **명시적 fail-closed(exit 1)** 로 전환하되 non-strict 레거시 경로와 worktree-base.json fallback은 불변 보존해야 함.

## Q (Question)
STRICT mode + SCOPE_BASE empty(merge-base + worktree-base.json fallback 모두 실패) 시, 빈 diff 검증 스킵(fail-OPEN)을 제거하고 actionable marker + exit 1 fail-CLOSED로 전환하되, (1) worktree-base.json fallback 경로 유지, (2) STRICT 유효 SCOPE_BASE 정상 동작 보존, (3) non-strict default-off 레거시 불변을 동시에 만족하려면?

## A (Answer)

### 수정/생성 파일 (expected_files 2파일 — 그 외 0)
- **수정** `scripts/finish-task.sh` (L552~561)
  - 기존 `else` 분기 `: > "$SCOPE_DIFF_FILE"` (빈 diff fail-OPEN) **제거**
  - 교체: 주석(회장 A안 근거 3줄) + `_emit_worktree_unresolved "$TASK_ID" "...SCOPE_BASE 미감지...fail-closed"`(actionable marker 발행) + `echo "[ERROR][STRICT] ... 미검증 변경 통과 차단 (fail-closed)" >&2` + `exit 1`
  - 유효 SCOPE_BASE 경로(L553 `diff --name-only "${SCOPE_BASE}..HEAD"`) 불변 · worktree-base.json fallback(L549~551) 불변 · non-strict else(L562~566 레거시 `${MAIN_BRANCH}..HEAD` ‖ `HEAD~1`) 불변
  - `_emit_worktree_unresolved` 함수는 기존 정의(L71) 재사용 — 신규 함수 없음
- **수정** `tests/regression/test_finish_task_worktree_isolation_2726.py`
  - 회귀7 `TestStrictScopeBaseEmptyFailClosed` 추가(5 테스트):
    1. `test_strict_empty_scope_base_no_empty_diff_failopen` — STRICT 블록 내 빈-diff(`: > "$SCOPE_DIFF_FILE"`) 부재
    2. `test_strict_empty_scope_base_exits_failclosed` — STRICT 블록 내 `exit 1` + fail-closed 신호(`fail-closed` ‖ `_emit_worktree_unresolved`) 동시 존재
    3. `test_strict_valid_scope_base_generates_diff` — 유효 SCOPE_BASE 경로(`${SCOPE_BASE}..HEAD`) 보존
    4. `test_nonstrict_legacy_path_preserved` — non-strict 레거시(`${MAIN_BRANCH}..HEAD` + `HEAD~1`) 보존
    5. `test_no_anu_key_literal` — finish-task.sh ANU key literal 노출 0 (테스트 소스도 literal 분할 재구성 → 노출 0)

## affected_files
- scripts/finish-task.sh
- tests/regression/test_finish_task_worktree_isolation_2726.py

## 테스트 결과 / 필수 회귀 (회장 10) — 전부 PASS
1. STRICT + SCOPE_BASE empty → fail-closed(exit 1 / 검증 스킵 안 함) ✓ (회귀+L1 실행 검증)
2. STRICT + valid SCOPE_BASE → 기존 정상 diff 생성 ✓
3. non-strict/default-off → 레거시 호환 동작 ✓
4. main/master default branch 회귀 유지 ✓ (회귀6 TestMainBranchDetectionMasterDefault 6건 무손상)
5. 기존 30 회귀 유지/증가 → **35 passed** (30 기존 + 5 신규, 무손상) ✓
6. origin/main 하드코딩 코드경로 0 ✓ (round-2 성과 보존)
7. PR scope = merge-base(`6a44d712`)..HEAD = expected_files **2파일** ✓ (`git diff origin/main`이 7파일 보인 것은 origin/main이 task-2728 머지로 우리 branch보다 앞섰기 때문 — PR 실제 diff는 merge-base 기준 2파일)
8. forbidden 0 ✓ (2파일만 변경) · 9. ANU key literal(`c119085…`) 0 ✓ · 10. shared main/dev4 branch 개입 0 ✓
- smoke: `python3 -m pytest tests/regression/test_finish_task_worktree_isolation_2726.py -q` → **35 passed** · `bash -n scripts/finish-task.sh` → rc=0 ✓
- goal_assertions(auto) 4건: pytest PASS · bash -n PASS · `STRICT` & `exit 1` in src → PASS · ANU key not in src → PASS

## L1 스모크테스트 결과 (필수)
- **서버 재시작**: 해당없음 (백엔드 bash CLI 함수)
- **API 응답 확인**: 해당없음
- **실동작 검증 (실제 실행)**: 실제 `scripts/finish-task.sh`에서 `_emit_worktree_unresolved` 함수 + STRICT scope-gate else 분기 로직을 controlled env(`/tmp/l1_2726`)로 추출·실행. SCOPE_BASE 빈 상태(merge-base 실패 + worktree-base.json 부재) 재현 →
  - 종료 코드 **1 (fail-closed)** — `REACHED_AFTER` 후속 코드 **미도달** 확인
  - `events/task-2726+3.worktree-unresolved.json` marker 파일 **생성**(actionable: classification=WORKTREE_UNRESOLVED, reason 포함)
  - `scope-diff.txt` 빈 파일 **미생성** → fail-OPEN 회귀 방지 확인
  - **→ L1 SMOKE PASS**
- **스크린샷**: 해당없음 (CLI)

## Git / 머지 판단
- **commit**: `9731cc4b` (← `d34db8ca` ← `d9496b15`, branch base `80416faa`에서 이어서, **non-force push** PR #171 갱신, amend 금지)
- **머지 필요**: Yes — 단 **회장 승인 전 merge 금지 (MERGE_APPROVAL_CANDIDATE)**
- **브랜치**: task/task-2726-dev3 · **워크트리**: /home/jay/workspace/.worktrees/task-2726-dev3 · **PR**: #171
- **머지 의견**: bounded surgical fix(2파일), 35 회귀 PASS, PR diff 2파일, forbidden/ANU key 0, STRICT default-off·worktree-base.json fallback·non-strict 레거시 보존, fail-OPEN → fail-CLOSED 전환으로 task-2726 핵심 목표 달성. 새 head OWNER `/gemini review`(request-only) → CI 11/11 + fresh unresolved HIGH/CRITICAL 0 + diff 2파일 + forbidden 0 + ANU key 0 시 MERGE_APPROVAL_CANDIDATE.

## 게이트 통과 (Lv.2)
- **G1 설계**: affected_files 2파일 확인, 다른 팀/파일 겹침 0 (PR scope merge-base 기준 2파일) ✓
- **G2 구현**: 팀 테스터(모리건) 회귀 5건 + 기존 30 = 35 PASS, L1 실동작 검증 ✓
- **G3 머지**: non-force push로 PR #171 갱신 → ANU normal callback cron이 새 head owner_gemini_trigger(request-only) → CI/Gemini watcher 위임 → MERGE_APPROVAL_CANDIDATE (merge 회장 승인 전 금지) ✓

## 모델 사용 기록 (round-3)
- 루(백엔드, finish-task.sh): **sonnet(general-purpose)** — bash else 분기 fail-closed 전환
- 모리건(테스터, test 파일): **sonnet(general-purpose)** — 회귀 5건 + ANU key literal 분할 재구성 (전략/분석 아님, haiku 미사용)
- 팀장(다그다, Opus): 설계/분배/검증/L1 smoke/통합·push만 수행 (직접 코딩 위임)

## 발견 이슈 및 해결
- (이슈) `git diff --name-only origin/main` 7파일 출력 → origin/main이 task-2728 머지로 우리 branch보다 앞선 분기 artifact. **해결**: merge-base(`6a44d712`)..HEAD 기준 PR 실제 scope = 2파일 확인. PR #171 diff는 merge-base 기준이므로 expected_files 일치.
- (이슈) 테스트 ANU key literal 노출 위험 → **해결**: `test_no_anu_key_literal`의 literal을 문자열 연결(`"c119085"+"..."`)로 분할, 소스 노출 0건.

## 비고 (doctrine 준수 — round-3)
- ★ **round-3 bounded fix(회장 A안, max_rounds=2 초과 명시 인가)**. 이후 같은 fail-open/SCOPE_BASE 계열 재발 → CHAIR_REQUIRED/LOOP_BOUNDARY.
- merge 미수행(회장 승인 전 금지) · 봇 `/gemini review` 미트리거(인간 OWNER 1회 doctrine, ANU owner_gemini_trigger request-only) · long polling 미실시(watcher 위임) · amend/force push 금지(non-force) 준수.
- FINISH_TASK_WORKTREE_STRICT 활성화 0 · shared main stash/reset/clean/delete 0 · dev4 미개입 · task-2725/2727 미혼입 · PR #169 미커밋 · systemctl/wake 0 · rebase/admin override 0.
- 새 HIGH/CRITICAL 발생 0.
- ANU normal callback cron 강제 등록(collector_role=ANU, 독립 ANU key) — 본 task 종료 시 finish-task.sh launcher 경유 등록. executor self-key 금지, sendfile-only 금지(NOT_REGISTERED fail-closed 방지).
