# task-2729+3 보고서 — FINISH_TASK_MERGE_POLICY_ENFORCEMENT (Option A, env-first 재설계)

## 결론
attempt-4 = 회장 승인 **A안** 적용. PR#176→#177→#178 (arg-parsing 계열 3연속 HIGH)을 종결하기 위해
fresh origin/main(14ff8339) base 로 **신규 PR #179** 재설계, **PR#178 close(supersede)**.
- PR #179: https://github.com/Jeon-Jonghyuk/dev_workspace/pull/179 (OPEN, base main)
- PR #178: CLOSED (supersede 코멘트, 본 브랜치 push 0)
- 브랜치: task/task-2729+3-dev1-r4 (base 14ff8339)

핵심 변경: finish-task.sh 의 finalize-only 판정을 **환경변수 `FINALIZE_ONLY=1` 주 경로(env-first)** 로
삼고, 위치인자 위치무관 파싱을 **bash array(`_POS_ARGS`) → 인덱스 카운터(`_pos_idx`)+`case`** 로 교체.
PR#176~178 계열 HIGH 의 근본 원인(old bash ≤3.2 + `set -u` 에서 빈 배열 인덱스 참조 시 'unbound
variable' 즉시 종료)을 **구조적으로 제거**(배열 미사용).

## 변경 파일 (expected_files = 3, lock 준수)
1. `scripts/finish-task.sh` — ARGPARSE(env-first/카운터) + resolver gate(fail-CLOSED) + merge block 스킵 + 2.3 PR-GATE 스킵.
2. `scripts/harness/v36/merge_policy_resolver.py` (신규) — task md allowed_resources 의 merge_policy 파싱(순수함수, fail-CLOSED 신호=빈문자열).
3. `tests/regression/test_finish_task_merge_policy_enforcement.py` (신규) — 회장 verbatim 회귀 10 + 보강(19 PASS).

## side-effect 경로 (AUDIT 근거)
- merge block: `if [ -n "$PROJECT_PATH" ]` 게이트 후 `worktree_manager.py finish ... --action auto` 실행 = 실 merge + PR gate + owner_gemini trigger(전부 worktree_manager 내부). `.merge-done` 생성.
- PROJECT_PATH auto-보정으로 caller 가 finalize-only 의도해도 merge 무성 개방 위험.
- finish-task.sh 가 merge_policy 미참조 → task md `merge_policy=none` 무효였음(STRICT_MODE_MERGE_POLICY_GAP).

## before / after
### before (origin/main 14ff8339)
```bash
set -euo pipefail
TASK_ID="$1"                 # 무인자 시 set -u 로 즉시 종료
TEAM_SHORT="${2:-""}"
PROJECT_PATH="${3:-""}"
...
# 2. 머지
if [ -f "$MERGE_DONE_FILE" ]; then ...
elif [ -z "$PROJECT_PATH" ]; then ...
else  # PROJECT_PATH 있으면 worktree_manager finish 실행(merge+PR gate+gemini)
```
- merge_policy 미참조. finalize-only 개념 없음. PROJECT_PATH 있으면 무조건 merge side-effect.

### after
```bash
set -euo pipefail
# env-first: 주 경로 FINALIZE_ONLY=1, 위치인자 --finalize-only 는 보조호환
_FINALIZE_ONLY=0; TASK_ID=""; TEAM_SHORT=""; PROJECT_PATH=""; _pos_idx=0
for _arg in "$@"; do
  if [ "$_arg" = "--finalize-only" ]; then _FINALIZE_ONLY=1
  else case "$_pos_idx" in 0) TASK_ID="$_arg";; 1) TEAM_SHORT="$_arg";; 2) PROJECT_PATH="$_arg";; esac
       _pos_idx=$((_pos_idx + 1)); fi
done
if [ "${FINALIZE_ONLY:-0}" = "1" ]; then _FINALIZE_ONLY=1; fi
...
# resolver gate (fail-CLOSED): merge_policy=none/빈값/실패 → _FINALIZE_ONLY=1
# merge block 첫 분기: _FINALIZE_ONLY=1 → worktree_manager finish/PR gate/gemini 미실행, .finalize-only 마커, .merge-done 미생성
# 2.3 PR-GATE: _FINALIZE_ONLY=1 → 스킵(open PR 정상)
```
- **배열 미사용** → 무인자 / `--finalize-only` 단독에서도 `set -u` unbound 0.
- 위치무관: `--finalize-only` 가 $1/$2/$3/$4 어디 있어도 positional 오염 0.
- fail-CLOSED: resolver/task-md 부재·실행실패·빈값 → merge 진행 금지(안전기본값=skip), stderr 노출(fail-OPEN 금지).
- 기본(tiered/auto/미지정 + FINALIZE_ONLY 미설정) = 기존 동작 100% 보존.

## 회귀 증거 (19 PASS / 회장 verbatim 10 매핑)
| # | 회장 요구 | 테스트 | 결과 |
|---|---|---|---|
| 1 | FINALIZE_ONLY=1+PROJECT_PATH → worktree_manager finish 미호출 | reg01, reg01b | PASS |
| 2 | 동 → PR gate/owner_gemini 미호출 | reg02 | PASS |
| 3 | merge_policy=none+PROJECT_PATH → side-effect 0 | reg03 | PASS |
| 4 | --finalize-only $1/$2/$3 위치무관 오염0 | reg04 | PASS |
| 5 | --finalize-only 단독 → set -u/old bash unbound 0 | reg05, reg56(no-array) | PASS |
| 6 | 무인자 → unbound 0 | reg06, reg56 | PASS |
| 7 | resolver 실패 → fail-open 금지 | reg07/07b/07c | PASS |
| 8 | QC/scope/callback/.done 유지 | reg08 | PASS |
| 9 | 기존 task 동작 유지 | reg09 | PASS |
| 10 | ACTIVE=false·ANU key 0·forbidden 0 | reg10a/10b/10c | PASS |
| + | py_compile / bash -n PASS | test_py_compile_resolver, test_bash_n_finish_task, resolver unit | PASS |

수동 edge 검증(ARGPARSE 추출 실행, `set -euo pipefail`):
```
no-args:      RESULT||||0   rc=0   (unbound 0)
flag-alone:   RESULT||||1   rc=0   (unbound 0)
normal:       RESULT|mytask|dev1|/proj|0
flag@1/2/3/4: RESULT|mytask|dev1|/proj|1   (위치무관, 오염 0)
env FO=1:     RESULT|mytask|dev1|/proj|1
```

## 안전성 확인
- diff = 정확히 3 expected files. orphan submodule(gptaku_plugins) 미포함.
- raw ANU key(c119085…) 0, ACTIVE=true mutation 0(테스트 assertion 토큰 제외), 실 merge/force/rebase/admin 0.
- bash -n PASS, py_compile PASS.
- PR#174 코드 변경 0, PR#178 push 0(close 만), 수동 .done 0, fake schedule_id 0.

## 후속
PASS 시 ANU 가 P0-A(PR#174) finalize 재시도: PROJECT_PATH 있어도 merge_policy=none / FINALIZE_ONLY=1 로
merge side-effect 0 → robust PASS → P0-A 완료 → P0-B 자동 진행. merge 는 회장 승인 전 금지.

## ★ 중단 조건
attempt-4 에서도 동일 finish-task harness 계열 **fresh 유효 HIGH** 재발 시 즉시 중단·재설계 보고(추가 patch 금지).
