# task-2779_b — Axis B: Stop hook terminal enforcer (dry-run) 구현 (PR 후보까지)

상태명(착수): `TERMINAL_CALLBACK_ENFORCEMENT_AXIS_B_STOP_HOOK_DRYRUN_IMPLEMENTATION_APPROVED_ACTIVE_FALSE`
담당: dev2-team (오딘) — terminal_artifact_index.py 연속성
승인: 회장 2026-06-29 (GPT 단계판정: 축 B 실행계약 수용 → dry-run **코드+격리테스트+PR 후보까지만**. 실제 Stop hook 설치/종료 차단/strict 미승인)
근거: 실행계약 `memory/reports/terminal_callback_enforcement_axis_b_stop_hook_dryrun_contract_260629.md` / 설계 `terminal_callback_enforcement_design_260628.md` §D·§E / 축 A merge `task2779a_axis_a_merged_closeout_260629.md` / P0c 증거 `p0c_phase1_observed_260629.md`

## 레벨
기능 구현 (1 스크립트 + 격리 테스트 + 보고서). PR 후보 생성까지.

## merge_policy
none — PR 후보 생성까지만. merge는 ANU 독립검증→fresh Gemini→CI 후 회장 승인 별도. **봇 merge 금지.**

## ★★★ 승인 범위 (절대 경계)
- **dry-run enforcer 코드 + 격리 fixture 테스트 + PR 후보 작성만.**
- **실제 Stop hook 설치 0 / 실제 종료 차단 0 / strict·blocking 0 / 실제 pre-push hook 설치 0.**
- ★ **dry-run marker/manifest 생성은 테스트용 tmp canonical root(또는 명시적 격리 root)에서만.** 실제 `/home/jay/workspace` canonical에 dry-run marker 생성 **절대 금지**(관측/운영 경로 미접촉).
- 스크립트는 작성되지만 실제 종료 경로(Stop hook)에 결선하지 않는다(결선은 다음 승인).

## allowed_resources (본 task의 capability)

```yaml
allowed_resources:
  paths:
    - "scripts/stop_hook_terminal_enforcer.py"
    - "dispatch/terminal_artifact_index.py"
    - "tests/regression/test_stop_hook_terminal_enforcer_2779.py"
    - "memory/reports/task-2779b-r1.md"
  forbidden_paths:
    - "scripts/finish-task.sh"
    - "scripts/pre_push_terminal_guard.py"
    - "scripts/anu_control_status.py"
    - "scripts/anu_terminal_artifact_surface.py"
    - "dispatch/anu_pickup_driver.py"
    - "dispatch.py"
    - ".git/hooks/**"
    - ".claude/**"
    - ".github/**"
    - ".env*"
    - "memory/state/**"
    - "memory/events/terminal_artifact_index/**"
    - "**/inbox/**"
    - "**/archive/**"
    - "**/quarantine/**"
  commands:
    - "git"
    - "pytest"
    - "python3"
    - "gh"
  merge_policy: "manual"
  ttl_hours: 8
```

## 목표
봇 **종료 시점**에 terminal callback envelope/.done/report 존재 여부를 **dry-run으로 감지**하는 enforcer 스크립트를 작성한다. 없으면 **어떤 artifact를 남겨야 하는지** `TERMINAL_CALLBACK_MISSING`/`STOP_HOOK_ENFORCED_TERMINAL_RESULT` 성격의 marker/manifest를 **격리 root에** 생성(보고만). **실제 종료/exit 차단 0(dry-run). 실제 Stop hook 결선 0. 실제 canonical marker 0.**

## 필수 요구 (16)
1. 새 스크립트 `scripts/stop_hook_terminal_enforcer.py` (dry-run enforcer)
2. 입력: task_id, branch, head, worktree path, **canonical root(격리 가능하게 인자/주입)**. 기존 surface/guard 인자 패턴 참고
3. 기존 `dispatch/terminal_artifact_index.py` build_manifest/surface_manifest **재사용**(신규 로직 최소)
4. 종료 시 terminal artifact(envelope/.done/report) 존재 → "존재" 판정 + surface 또는 PASS marker
5. 부재 → `TERMINAL_CALLBACK_MISSING` 또는 `STOP_HOOK_ENFORCED_TERMINAL_RESULT` marker/manifest 생성
6. ★ marker/manifest 생성은 **주입된 격리 canonical root에서만**. 실제 `/home/jay/workspace` 기본값으로 marker 생성 금지(테스트는 tmp root 강제)
7. **dry-run = 어떤 경우도 종료/exit 차단 0**(감지·기록만, exit 0)
8. 내부 오류 시 **fail-open + explicit marker**(crash로 종료 방해 금지)
9. 외부 path input은 기존 containment helper 통과(path traversal 무회귀)
10. dev self-key callback fire 0 · raw key/secret/ANU key literal 0
11. callback delivery 0 · real fire 0 · driver 실행 0 · systemd/cron 0
12. **실제 Stop hook 설치 0 · 실제 종료 차단 0 · strict 0 · 실제 pre-push hook 설치 0**
13. finish-task.sh 본체 변경 0
14. 기존 P0c 증거 + 축 A 산출물 덮어쓰기/삭제 0 — 테스트는 별도 fixture/tmp root
15. expected_files(allowed paths) 밖 수정 0 · forbidden paths 변경 0
16. ACTIVE=false 유지

## 테스트 필수 (9, 격리 fixture/tmp root)
1. 정상 종료 fixture(artifact 존재) → "존재" 판정 + surface/PASS marker
2. 실패 종료 fixture(artifact 부재) → `TERMINAL_CALLBACK_MISSING`/`STOP_HOOK_ENFORCED_TERMINAL_RESULT` marker 생성
3. 중단 종료 fixture(중간 kill 상태) → 부재 감지 + marker
4. dry-run 비차단: 위 모든 경우 exit 0(종료 차단 0)
5. 내부 오류 주입 → fail-open(crash 없이 explicit marker)
6. malicious rel path → containment helper 거부(traversal 무회귀)
7. ★ marker 생성이 **tmp root에만** 발생, 실제 canonical 미접촉(정적/구조 검증)
8. self-key 미사용 / raw key 0 정적 검증
9. 기존 2778/2779(축 A) regression 무회귀

## 검증 요구 (14)
1. `python3 -m py_compile scripts/stop_hook_terminal_enforcer.py`
2. `pytest tests/regression/test_stop_hook_terminal_enforcer_2779.py` PASS
3. 기존 `test_terminal_artifact_visibility_2778.py` PASS(무회귀)
4. 기존 `test_pre_push_terminal_guard_2779.py` PASS(무회귀)
5. raw key/secret scan 0 · ANU key literal 0
6. expected_files 밖 수정 0 · forbidden paths 변경 0(.git/hooks 포함)
7. callback/fire/systemd/cron/activation 0
8. 실제 Stop hook 설치 0 · 실제 종료 차단 0
9. ★ 실제 canonical(/home/jay/workspace)에 dry-run marker 0
10. strict/blocking 미포함
11. ACTIVE=false
12. P0c 증거 + 축 A 산출물 무변경
13. 새 PR 후보 생성(머지 0)
14. finish-task.sh 본체 무변경

## STOP 조건 (하나라도 필요해지면 즉시 중단 → `TERMINAL_CALLBACK_ENFORCEMENT_CONTRACT_STOP_REPORT`)
실제 hook 설치 필요 / 실제 종료 차단 필요 / strict 승격 필요 / finish-task.sh 본체 변경 필요 / callback delivery·real fire·owner key·systemd·cron·production write 필요 / expected_files 밖 수정 필요 / **실제 canonical에 dry-run marker를 남겨야만 검증 가능** / 축 A actual hook install 또는 hygiene backlog로 번짐.

## 금지 (13)
실제 Stop hook 설치 / 실제 종료 차단 / strict·blocking mode / finish-task.sh 본체 변경 / 실제 pre-push hook 설치 / callback delivery / real fire / owner key·raw key·credential touch / systemd·cron·daemon / production activation / auto-merge / P0c·축A 증거 삭제·덮어쓰기 / hygiene backlog 동시 처리 · broad Phase 2 확장 — **전부 금지.**

## 완료 후보 상태명
- 구현 완료: `TERMINAL_CALLBACK_ENFORCEMENT_AXIS_B_STOP_HOOK_DRYRUN_IMPLEMENTATION_DONE_ACTIVE_FALSE`
- 이후: ANU 독립검증 → fresh Gemini → active HIGH 0/Critical 0 → CI → MERGE_CANDIDATE_READY (회장 승인 별도)
- STOP 조건 발생: `TERMINAL_CALLBACK_ENFORCEMENT_CONTRACT_STOP_REPORT`

## callback / 종료 규칙
- 결과(성공/실패/blocked 무관) **봇 idle 전 ANU callback 필수**. callback ≠ merge ≠ finish-task.
- ANU-owned callback envelope만. **dev self-key callback fire 금지. raw key 출력 금지.**
- callback/result/report/.done은 **canonical에서 ANU가 볼 수 있게** 남길 것.
- 새 worktree/브랜치/PR로 작업(새 task id task-2779_b). PR 후보 생성까지(merge 0).
- ★ branch underscore(task-2779_b)→pre-push regex 차단은 알려진 deviation B. PR push 필요 시 공식 TASKCTL_BYPASS+evidence(branch-pattern 1건만).
- callback prompt UTF-8 ≤3900 bytes, envelope만.

## goal_assertions (auto-generated)
- `python3 -m py_compile scripts/stop_hook_terminal_enforcer.py`
- `pytest tests/regression/test_stop_hook_terminal_enforcer_2779.py`