# task-2729+18 — GOAL_GATE_PLACEHOLDER_HARDENING Option A REPLACEMENT (activation 0)

## 레벨
Lv.3 (finish-task.sh GOAL-GATE 실행부 hardening. production activation 아님)

## 발번 주석
PR #191(task-2729+17) HOLD_FOR_CHAIR 후속 Option A replacement(회장 2026-06-08 승인). #191 fresh HIGH(placeholder regex over-match) + MED 2 일괄 수정. **#191 same-branch push/rebase/force/cherry-pick 금지** — fresh origin/main 새 branch. #191(6d9ebbd8)은 **참고만**.

## ★★★ FRESH BASE HARD GATE (오염 재발 방지)
1. worktree 반드시 **origin/main `e8ad9ab3`(또는 이후 최신 main)** 분기. canonical(task-2716 계열) 분기 절대 금지.
2. `git worktree add -b task/task-2729+18-dev5 <path> origin/main`.
3. commit 전: `merge-base --is-ancestor e8ad9ab3 HEAD == YES` AND `task-2716 divergence 0`. 실패 시 즉시 HOLD.
4. diff `origin/main..HEAD` = expected_files 만. 오염 diff → HOLD.
- ★ origin/main(e8ad9ab3)에는 +17 placeholder 코드 없음 → 본 task 가 GOAL-GATE hardening **전체를 새로 구현**(아래 보정 반영). #191 코드는 참고만, 복붙 금지.

## 배경 (단일 소스)
- GOAL-GATE(finish-task.sh): `## goal_assertions` 블록을 추출해 명령 실행. ALLOWED_CMDS(grep curl pytest python3 tsc cat jq npx npm) 매칭 시 실행, FAIL 시 BLOCKED.
- 결함: task md 자동생성 `## goal_assertions` 에 리터럴 `python3 $QC_SCRIPT --gate --task-id ...`(미확장 `$QC_SCRIPT`+리터럴 `...`) → 무한 행 → finalize 정지 + 세션 죽음(+14/+15/+16 callback miss 원인).

## 수정 (finish-task.sh GOAL-GATE 실행부 단독)
### 핵심 로직 (★회장 보정 반영)
1. **placeholder/템플릿 잔재 skip** — 명령 실행 **전** 다음 포함 시 실행 0 + `GOAL_ASSERTION_PLACEHOLDER_SKIPPED`(skip):
   - 미확장 `$VAR` 또는 `${...}` (예 `$QC_SCRIPT`, `${TASK_ID}`)
   - 리터럴 `...` (3점)
   - **템플릿 토큰 형태의 꺾쇠 placeholder만**: `<[A-Za-z0-9_.-]+>` (예 `<url>`, `<foo>`, `<TASK_ID>`)
2. ★★ **정상 shell redirection 은 placeholder 아님**: `command < input.txt > output.txt` 처럼 꺾쇠 뒤 공백/경로가 오는 정상 입출력 리디렉션은 **skip 금지 → 정상 assertion 실행 경로**. (즉 `<[^>]+>` 같은 광역 regex 금지 — 공백 포함 매칭 금지. 토큰 내부는 영숫자/`_`/`-`/`.` 만 허용)
3. **placeholder skip ≠ PASS**: skip 을 PASS 로 과장 금지. marker/report 에 `GOAL_ASSERTION_PLACEHOLDER_SKIPPED` 명시.
4. **정상 assertion(ALLOWED_CMDS 매칭)은 기존대로 실행.**
5. **timeout guard**: 실행 명령에 `timeout <N>s` → hang 시 timeout 후 **fail-closed**(GOAL_TIMEOUT → BLOCKED).
6. **4-state**: GOAL_RESULT = PASS / SKIP(placeholder) / TIMEOUT(fail-closed) / FAIL(fail-closed) 명확 분리.
7. **callback/finalize 도달 보장**: placeholder skip 시 BLOCKED 하지 않고 진행(skip=충족 아님이되 hang/block 안 함). 정상 assertion FAIL/TIMEOUT 은 fail-closed BLOCKED 유지.
### ★ set 옵션 격리 (회장 verbatim — #191 MED)
- GOAL-GATE 함수 내부 `set +e`/`set -e` 가 caller shell 의 errexit 상태를 오염시키지 않도록 격리.
- 방법: subshell `( ... )` 실행 또는 진입 시 `$-` errexit 상태 저장 후 종료 시 복원. source 하는 테스트 러너/다른 스크립트에 errexit 누수 0.
### default-deny
- goal_assertions 내 allowlist 미명시 미확장 `$VAR`/`${VAR}` 는 실행 말고 placeholder skip. literal path 확정은 task md 생성 단계(본 task 범위 밖, dispatch 측 무수정).
### backward-compat
- `## goal_assertions` 없으면 기존 동작 유지. 기존 정상 goal_assertions(literal 명령)도 기존대로 실행.

## EXPECTED FILES (정확히 ≤4 — 초과 시 즉시 HOLD_FOR_CHAIR)
1. `scripts/finish-task.sh` — GOAL-GATE 실행부 placeholder skip(토큰 한정) + redirection 보존 + set 격리 + timeout + 4-state
2. `tests/regression/test_goal_gate_placeholder_hardening_2729p18.py` — 회귀(isolated temp)
3. `memory/reports/task-2729+18.md`
4. `memory/plans/p0b-pickup/goal_gate_placeholder_hardening_replacement_design_260608.md` (필요 시)
- ★ callback prereg(scripts/harness/v36/callback_preregistration.py)/git_evidence(teams/*/qc/verifiers/git_evidence.py)/dispatch.py 무수정. 수정 필요 판명 시 HOLD.

## 필수 regression (isolated temp, 회장 verbatim 16)
1. **정상 redirection assertion 실행됨** (예 `grep -q foo bar.txt > /tmp/o.txt` 류 정상 명령 → 실행, skip 아님). 2. `<...>` 템플릿 placeholder(`grep <foo> x`, `curl <url>`) skip. 3. 미확장 `$VAR`/`${VAR}` skip. 4. 리터럴 `...` 포함 skip. 5. 정상 pass 실행. 6. 실패 assertion fail-closed. 7. timeout 명령 timeout 후 fail-closed. 8. placeholder skip 이 callback/finalize hang 안 시킴(진행). 9. goal_assertions 없으면 기존 동작 유지. 10. set 격리(함수 후 caller errexit 무변경) 검증. 11. bash -n PASS. 12. raw key 0. 13. ACTIVE=false. 14. systemctl enable 0. 15. activation_epoch absent / real spawn 0. 16. canonical task-2716 branch·live memory artifacts 무손상.

## 금지 (회장 verbatim)
1. PR #191 same-branch push  2. rebase/force  3. merge  4. production ACTIVE 전환  5. systemctl enable  6. activation_epoch 생성  7. real ANU spawn  8. canonical reset/clean/stash -u/checkout -f  9. task-2716 branch 수정  10. live memory artifacts 이동·삭제  11. callback prereg Phase B 착수  12. ACTIVE=true 선언
- 모든 검증 isolated temp. canonical 무손상. #191 코드 복붙/cherry-pick 강행 금지(참고만).

## 이번 라운드 범위 (★회장: PR 생성까지 진행)
- 구현 + isolated verify + **PR 생성** + CI/Gemini fresh gate 확인까지.
- ★ 재귀 주의: 본 task finalize 도 (수정 전 GOAL-GATE 면) 동일 hang 가능 → self-dogfood(수정본)로 자기 finalize 통과 확인. 막히면 분리 기록. ANU origin/main..HEAD diff 독립검증 회수.

## 완료 판정
- 회귀 16 PASS + 4-state 명확 + redirection 보존 + set 격리 + callback/finalize 도달 + PR 생성 + CI GREEN + fresh Gemini(head 이후) HIGH/CRITICAL 0 + unresolved 0(또는 non-blocking) → **`MERGE_APPROVAL_CANDIDATE_GOAL_GATE_PLACEHOLDER_HARDENING_ACTIVE_FALSE`**.
- fresh HIGH/CRITICAL 재발 / expected_files 초과 / raw key 노출 / ACTIVE=true 필요 / systemd enable 필요 / real spawn 필요 / canonical mutation 필요 / callback prereg 수정 필요 / task-2716 수정 → **`HOLD_FOR_CHAIR`**.
- ★ merge·production activation = 별도 회장 승인 전까지 금지.

## doctrine
직접 코딩 금지(ANU)/봇 위임 / fresh origin/main e8ad9ab3 base 강제 / canonical·task-2716·dev4·#191 branch 무손상 / isolated temp / callback prereg·git_evidence·dispatch.py 무수정 / backward-compat / activation 0 / raw key 0 / same-PR post-Gemini push 금지·long polling 금지.
```yaml
callback_envelope_byte_limit: 3900
callback_collector_role: ANU
callback_owner_key_source: ".env.keys COKACDIR_KEY_ANU (sealed, literal 출력 0)"
```
