# task-2729+17 — GOAL_GATE_PLACEHOLDER_HARDENING (finish-task GOAL-GATE 실행부, activation 0)

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

## 발번 주석
PR #190(task-2729+16) 머지 후속. callback/finalize 직접 블로커(GOAL-GATE goal_assertions placeholder hard-hang) 해소. **task-2729+17** 발번(회장 확인 요망).

## ★★★ FRESH BASE HARD GATE (지난 +13~+15 오염 재발 방지)
1. worktree 는 **반드시 origin/main `e8ad9ab3`(#190 포함, 또는 이후 최신 main)** 분기. **canonical(task-2716 계열) 분기 절대 금지.**
2. `git worktree add -b task/task-2729+17-dev6 <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 4 만. 오염 diff(다수 파일/task-2716 상속) → HOLD.

## 배경 (단일 소스)
- GOAL-GATE(finish-task.sh): `## goal_assertions` 블록을 추출(re.search)해 명령을 실행. 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` + 리터럴 `...`) → GOAL-GATE 가 `python3 ... --gate --task-id ...` 실행 → **무한 행(invalid task-id)** → GOAL_MODE=fail finalize 정지 + 세션 죽음. task-2729+14/+15/+16 callback miss·세션 죽음의 직접 원인.
- callback prereg 는 무관(DEFER) — 본 task 미터치.

## 수정 (finish-task.sh GOAL-GATE 실행부 단독, B안)
### 핵심 로직
1. **placeholder/템플릿 잔재 skip**: goal_assertion 명령 실행 **전**, 다음 패턴 포함 시 실행 0 + **`GOAL_ASSERTION_PLACEHOLDER_SKIPPED`** 분류(skip):
   - 미확장 `$VAR` 또는 `${...}` (예 `$QC_SCRIPT`, `${TASK_ID}`)
   - 리터럴 `...` (3점)
   - `<...>` 꺾쇠 placeholder
2. **placeholder skip ≠ PASS**: skip 은 PASS 로 과장 금지. marker/report 에 `GOAL_ASSERTION_PLACEHOLDER_SKIPPED` 명시 기록.
3. **정상 assertion 명령은 기존대로 실행**(ALLOWED_CMDS 매칭).
4. **timeout guard**: 실행 명령에 `timeout <N>s` 적용 → 장기실행/hang → timeout 후 **fail-closed**(GOAL_TIMEOUT → BLOCKED). (기존 timeout 패턴 참조)
5. **skip/timeout/fail/pass 명확 구분**: GOAL_RESULT 가 PASS / SKIP(placeholder) / TIMEOUT(fail-closed) / FAIL(fail-closed) 4-state.
6. **callback/finalize 단계 도달 보장**: placeholder skip 시 GOAL-GATE 가 BLOCKED 하지 않고 진행(skip 은 충족으로 보지 않되 hang/block 안 함 — finalize 계속). 단 정상 assertion FAIL/TIMEOUT 은 fail-closed BLOCKED 유지.
### ★ 보강 (회장 verbatim — default-deny 미확장 변수)
- goal_assertions 내부 **미확장 변수 사용은 기본 금지(default-deny)** 로 본다.
- allowlist 가 명시되지 않은 `$VAR`/`${VAR}` 는 실행하지 말고 **placeholder skip**(GOAL_ASSERTION_PLACEHOLDER_SKIPPED).
- 정상 assertion 에 변수가 꼭 필요하면 **task md 생성 단계에서 literal path 로 확정**되어 들어와야 한다(본 task 범위 밖). ★ 본 task 는 **dispatch 생성 측 수정 0 — finish-task GOAL-GATE 방어에 집중**.

### backward-compat
- task md 에 `## goal_assertions` 없으면 **기존 동작 유지**(GOAL_ENABLED/disabled 경로 무변경).
- 기존 정상 goal_assertions(실제 명령·literal) 도 기존과 동일 실행.

## allowed_resources
```yaml
allowed_resources:
  paths:
    - "scripts/finish-task.sh"
    - "tests/regression/test_goal_gate_placeholder_hardening_2729p17.py"
    - "memory/reports/task-2729+17.md"
    - "memory/plans/p0b-pickup/goal_gate_placeholder_hardening_design_260608.md"
    - "memory/events/task-2729+17.*"
    - "memory/tasks/task-2729+17-goal-gate-placeholder-hardening.md"
  read_only_reference:
    - "utils/gate_config_loader.py (GOAL-GATE config — read only)"
    - "memory/events/task-2729+14.followup.txt (블로커 증거 — read only)"
  forbidden_paths:
    - "/home/jay/workspace (canonical working tree — reset/clean/stash/switch/분기 금지)"
    - "scripts/harness/v36/callback_preregistration.py (callback prereg Phase B 미착수)"
    - "dispatch/normal_fallback_callback_helper.py"
    - "teams/*/qc/verifiers/git_evidence.py"
    - "teams/shared/verifiers/git_evidence.py"
    - "dispatch.py (goal_assertions 생성측 — B안은 실행부만)"
    - "memory/state/**"
    - "memory/events/task-*.result.json"
    - "memory/events/task-*.g4-fix-loop-count"
    - "deploy/systemd/**"
    - "task-2716 branch (수정/분기 금지)"
    - "task/task-2729+15-dev4 (evidence-only 보존)"
```

## EXPECTED FILES (정확히 4 — 초과 시 즉시 HOLD_FOR_CHAIR)
1. `scripts/finish-task.sh` — GOAL-GATE 실행부 placeholder skip + timeout guard + 4-state
2. `tests/regression/test_goal_gate_placeholder_hardening_2729p17.py` — 회귀(isolated temp)
3. `memory/reports/task-2729+17.md`
4. `memory/plans/p0b-pickup/goal_gate_placeholder_hardening_design_260608.md`
- ★ callback prereg/git_evidence/dispatch.py 무수정. 수정 필요 판명 시 HOLD.

## 필수 regression (회장 verbatim 10, isolated temp)
1. goal_assertions 에 `python3 $QC_SCRIPT --gate --task-id ...` → 실행 0 + placeholder skip. 2. 리터럴 `...` 포함 명령 skip. 3. 미확장 `$VAR`/`${VAR}` 포함 명령 skip. 4. 정상 assertion 명령 실행. 5. 실패 assertion fail-closed. 6. timeout 명령 timeout 후 fail-closed. 7. placeholder skip 이 callback/finalize 전체 hang 안 시킴(진행). 8. goal_assertions 없으면 기존 동작 유지. 9. raw key 0. 10. ACTIVE=false / systemctl enable 0 / activation_epoch absent / real spawn 0.

## 금지 (회장 verbatim)
1. production ACTIVE 전환  2. systemctl enable  3. activation_epoch 생성  4. real ANU spawn  5. canonical reset/clean/stash -u/checkout -f  6. task-2716 수정/분기  7. live memory artifacts 이동·삭제  8. dev4 contaminated branch push/PR/rebase/force  9. callback prereg Phase B 자동 착수  10. ACTIVE=true 선언
- 모든 검증 isolated temp. canonical 무손상.

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

## 완료 판정
- 회귀 10 PASS + placeholder skip/timeout/fail-closed/normal pass 4-state 명확 + callback/finalize 도달 → **`PR_READY_CANDIDATE_GOAL_GATE_PLACEHOLDER_HARDENING_ACTIVE_FALSE`**(로컬 verify, PR 미생성).
- expected_files 초과 / fresh base 불충족 / callback prereg·git_evidence·dispatch.py 수정 필요 / canonical write 필요 / 수동 .done / real spawn·ACTIVE=true·systemd·epoch 필요 / task-2716 수정 → **`HOLD_FOR_CHAIR`**.
- ★ PR·activation 별도 회장 승인 전까지 금지.

## doctrine
직접 코딩 금지(ANU)/봇 위임 / fresh origin/main e8ad9ab3 base 강제 / canonical·task-2716·dev4 branch 무손상 / isolated temp / callback prereg·git_evidence·dispatch.py 무수정 / backward-compat 필수 / activation 0 / raw key 0 / same-PR 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)"
```