# task-2729+17 보고서 — GOAL-GATE Placeholder Hardening (ACTIVE=false)

## 메타
- 작업 ID: task-2729+17
- 팀: dev6-team (페룬/스바로그/벨레스)
- 레벨: Lv.3 (finalize 인프라 hardening — finish-task.sh GOAL-GATE 실행부)
- Base: origin/main `e8ad9ab3` (#190 포함), task-2716 divergence 0
- 브랜치: `task/task-2729+17-dev6`
- 워크트리: `/home/jay/workspace/.worktrees/task-2729+17-dev6`
- 결과: **PR_READY_CANDIDATE_GOAL_GATE_PLACEHOLDER_HARDENING_ACTIVE_FALSE** (로컬 verify, PR 미생성)

---

## S — Situation
finish-task.sh 의 GOAL-GATE 는 task md `## goal_assertions` 블록에서 백틱 명령을 추출해 `eval`로 실행한다(ALLOWED_CMDS 매칭 시). callback/finalize 직전 단계로, 여기서 막히면 세션 전체가 정지한다.

## C — Complication
task md 자동생성 `## goal_assertions` 에 리터럴 placeholder `python3 $QC_SCRIPT --gate --task-id ...` (미확장 `$QC_SCRIPT` + 리터럴 `...`)가 들어오면, GOAL-GATE 가 이를 `eval "python3 ... --gate --task-id ..."` 로 **그대로 실행 → 무한 행(hang)**. timeout guard 부재로 finalize 정지 + 세션 사망. task-2729+14/+15/+16 callback miss·세션 죽음의 직접 원인.

## Q — Question
생성측(dispatch.py) 무수정 원칙 하에, **finish-task.sh GOAL-GATE 실행부 단독(B안)**으로 placeholder hang 을 어떻게 fail-safe 하게 차단하면서, 정상 assertion 실행·backward-compat·fail-closed 를 보존할 것인가?

## A — Answer
finish-task.sh 의 GOAL-GATE 실행부에 **테스트 가능한 라이브러리 함수 `goal_assertion_eval`** 를 도입하고, 4-state 판정으로 재작성했다.

### 핵심 로직
1. **placeholder/템플릿 잔재 default-deny 탐지 (실행 전 차단)** — 3종:
   - 미확장 `$VAR` / `${VAR}` (정규식 `\$\{?[A-Za-z_]`) → `SKIP_PLACEHOLDER`
   - 리터럴 `...` (3점) → `SKIP_PLACEHOLDER`
   - `<...>` 꺾쇠 placeholder (정규식 `<[^>]+>`) → `SKIP_PLACEHOLDER`
   - ★ placeholder 탐지는 allowlist 검사보다 **먼저** 수행 (첫 단어가 allowed 여도 placeholder 면 skip)
2. **placeholder skip ≠ PASS** — `GOAL_ASSERTION_PLACEHOLDER_SKIPPED` 를 로그 marker 에 명시 기록. 4-state 의 SKIP 으로 분류(PASS 로 과장 금지).
3. **정상 assertion 명령은 기존대로 실행** — allowlist(첫 단어) 매칭 시 실행.
4. **timeout guard** — `timeout <N>s bash -c "$cmd"` (기본 30s, `GOAL_CMD_TIMEOUT` 조정). 행/장기실행 → exit 124 → `TIMEOUT` (fail-closed).
5. **4-state** — `GOAL_RESULT_VAL ∈ {PASS, SKIP, FAIL, TIMEOUT}` (우선순위 TIMEOUT > FAIL > SKIP > PASS).
6. **callback/finalize 도달 보장** — placeholder SKIP 은 BLOCKED 하지 않고 **진행**(충족으로 보지 않되 hang/block 안 함). 정상 assertion 의 FAIL/TIMEOUT 만 `GOAL_MODE=fail` 시 fail-closed BLOCKED(exit 1) 유지.

### backward-compat
- `## goal_assertions` 없으면 기존 동작(GOAL_ENABLED/disabled 경로 무변경).
- 기존 정상 literal 명령은 기존과 동일 실행(allowlist 통과 → timeout 래핑 실행).

### 테스트 가능성 (sourcing guard)
- `FINISH_TASK_LIB_ONLY=1 source scripts/finish-task.sh` → 함수만 정의 후 main 실행 전 즉시 반환(side-effect 0). 회귀테스트가 `goal_assertion_eval` 를 isolated 호출.

---

## 수정 파일별 검증 상태

| 파일 | 변경 내용 | grep 검증 | 상태 |
|------|-----------|-----------|------|
| /home/jay/workspace/.worktrees/task-2729+17-dev6/scripts/finish-task.sh | goal_assertion_eval 함수 + sourcing guard + 4-state GOAL-GATE 루프 재작성 | grep "goal_assertion_eval" OK | done |
| /home/jay/workspace/.worktrees/task-2729+17-dev6/tests/regression/test_goal_gate_placeholder_hardening_2729p17.py | 회귀 12건(회장 verbatim 10 커버, isolated temp) | grep "goal_assertion_eval" OK | done |
| /home/jay/workspace/.worktrees/task-2729+17-dev6/memory/plans/p0b-pickup/goal_gate_placeholder_hardening_design_260608.md | 설계 문서 | grep "Placeholder Hardening" OK | done |
| /home/jay/workspace/.worktrees/task-2729+17-dev6/memory/reports/task-2729+17.md | 본 보고서 | grep "PR_READY_CANDIDATE" OK | done |

---

## 테스트 결과

### 회귀 테스트 (isolated temp, 실제 finish-task.sh 함수 호출)
`tests/regression/test_goal_gate_placeholder_hardening_2729p17.py` → **12 passed** (2.2s)

회장 verbatim 10 케이스 커버:
1. `python3 $QC_SCRIPT --gate --task-id ...` → SKIP_PLACEHOLDER (실행 0) ✓
2. 리터럴 `...` 명령 → SKIP_PLACEHOLDER ✓
3. 미확장 `$VAR`/`${VAR}` → SKIP_PLACEHOLDER (3, 3b 분리) ✓
4. 정상 assertion → PASS ✓
5. 실패 assertion → FAIL (fail-closed 대상) ✓
6. timeout 명령 → TIMEOUT (fail-closed 대상) ✓
7. placeholder skip 이 hang 안 시킴 (elapsed < 5s 실측) ✓
8. goal_assertions 없으면 기존 동작 유지(파싱 0건) ✓
9. raw key 0 (자기 소스 16자 hex 스캔) ✓
10. ACTIVE=false / 부작용 0 (tempdir 신규 파일 0, systemctl·epoch 부재) ✓

### 함수 단위 스모크 (스바로그)
7/7 기대값 일치 (SKIP_PLACEHOLDER ×3, PASS, FAIL, SKIP_NOT_ALLOWED, TIMEOUT)

---

## L1 스모크테스트 결과 (실동작 — self-dogfood)
- **서버 재시작**: 해당없음 (bash 스크립트 인프라 작업, 서버 무관)
- **API 응답 확인**: 해당없음
- **실동작(end-to-end GOAL-GATE 4-state 루프) 검증**: 수정본 `goal_assertion_eval` 를 실제 source 하여 GOAL-GATE 루프 로직 그대로 재현:
  - SMOKE 1 (placeholder + 정상 혼합): `python3 $QC_SCRIPT ... ...` → PLACEHOLDER_SKIPPED, `grep -q . /etc/hostname` → PASS, `GOAL_RESULT_VAL=PASS`, **RESULT=PROCEED (finalize 계속)**, elapsed **0s** (구버전 무한행 → 0s 해소 증명)
  - SMOKE 2 (정상 FAIL): `grep -q ZZZNOTFOUND` → FAIL → **RESULT=BLOCKED (fail-closed 정상)**, exit 1
  - SMOKE 3 (TIMEOUT): `python3 -c "time.sleep(20)"` (timeout=2) → TIMEOUT → fail-closed, **20s sleep 이 2s 에 차단**
- **스크린샷**: 해당없음 (CLI/bash)
- **bash -n**: syntax OK (에러 0)

---

## 게이트 (Lv.3)
- **G1 설계**: affected_files = expected 4 파일만. 다른 팀/금지경로(dispatch.py·callback prereg·git_evidence·gate_config_loader) 겹침 0. canonical·task-2716·dev4 무손상.
- **G2 구현**: 팀 테스터(벨레스) 회귀 12 PASS + 함수 스모크 7/7 + L1 end-to-end 3종.
- **G3 머지**: 본 라운드 **PR 미생성**(PR_READY_CANDIDATE). PR/Gemini gate/merge/activation = 별도 회장 승인 전까지 0.

## FRESH BASE HARD GATE 준수
- worktree base = `e8ad9ab3` (#190), `merge-base --is-ancestor e8ad9ab3 HEAD` = YES
- task-2716(`75fdf540`) divergence = 0 (canonical 계열 미상속)
- `git diff e8ad9ab3..HEAD --name-only` = 정확히 **4 파일** (lock·task md 는 untracked → diff 미포함)

## 금지사항 준수 (회장 verbatim)
production ACTIVE 전환 0 / systemctl enable 0 / activation_epoch 생성 0 / real ANU spawn 0 / canonical reset·clean·stash·checkout 0 / task-2716 수정·분기 0 / live memory artifacts 이동·삭제 0 / dev4 branch push·PR 0 / callback prereg Phase B 미착수 / ACTIVE=true 선언 0 / 모든 검증 isolated temp.

## 발견 이슈 및 해결
- **이슈**: worktree pre-commit guard(start_task_guard)가 `.tasks/locks/task-2729+17.lock` 요구.
  - **해결**: lock 을 디스크에 생성하되 **commit 에 add 하지 않음**(hook 은 파일 존재만 검사) → `origin/main..HEAD` diff 4 파일 보존.
- **이슈**: pytest unused import(lint 경고).
  - **해결**: 제거 후 재실행 12 PASS 확인.
- **블로커(분리 기록 — finalize/.done)**: 수정본 finish-task.sh 를 self-dogfood 로 실행한 결과, **GOAL-GATE 는 hang 없이 통과**(185s 완주, 본 task 의 근본 목표 달성)했다. 그러나 `.done` 은 QC 1건(`file_touch_ratio_check`) FAIL 로 차단됨.
  - **원인**: `file_touch_ratio_check` 가 `workspace_root="/home/jay/workspace"`(canonical)을 하드코딩하고 거기서 `git diff HEAD~5` 를 계산. canonical 은 현재 task-2716 브랜치(최근 66파일) → worktree 의 4파일과 교집합 0 → ratio 0.00 FAIL.
  - **산출물 정당성 증명**: 동일 verifier 를 **worktree 기준**으로 호출하면 **PASS (ratio 1.00, 교집합 4/4)**. 즉 보고서의 4파일 = 실제 변경 4파일 정확 일치. 블로커는 산출물 결함이 아니라 **worktree 격리 + merge 금지(PR_READY_CANDIDATE) 와 canonical-하드코딩 QC verifier 의 구조적 비호환**.
  - **조치(forbidden 준수)**: 정식 .done 은 merge(=canonical write, 회장 승인 필요) 또는 QC 인프라 수정(4파일 초과·범위 밖)이 선행되어야 함. 수동 .done·수동 result.json(forbidden_paths) 모두 미수행. → finalize/.done 는 **회장 승인 대기(HOLD_FOR_CHAIR)**, 산출물은 **PR_READY_CANDIDATE 달성**.
  - **ANU 콜백**: 정식 경로 `extract_followup.py send`(ANU key c119085addb0f8b7, self-key 아님)로 ANU 후속 알림 cron 등록 → SELF_COLLECTOR/SENDFILE_ONLY 회피.

## 모델 사용 기록
- 스바로그(백엔드): sonnet — bash hardening 구현 + 설계문서 (로직 구현, sonnet 적정)
- 벨레스(테스터): sonnet — 회귀 12건 (테스트 로직, sonnet 적정)
- haiku 미사용 (전 작업 로직/설계 성격 → sonnet 이상 필수 규칙 준수)
- 페룬(팀장, opus): 설계/분배/검토/통합/L1 dogfood/보고 (직접 코딩 최소화)

## 머지 판단
- **머지 필요**: Yes (단, 별도 회장 승인 후 — 본 라운드 PR_READY_CANDIDATE)
- **브랜치**: `task/task-2729+17-dev6`
- **워크트리 경로**: `/home/jay/workspace/.worktrees/task-2729+17-dev6`
- **머지 의견**: 회귀 12 PASS + L1 4-state end-to-end 증명 + fresh base 4-file clean diff. canonical/task-2716/dev4 무손상. High 리스크 0. PR·activation 은 회장 승인 게이트 대기.
