# task-2779_b+1 — Axis B HIGH microfix: `_is_production_canonical` 하위경로 차단 (same-PR #260)

상태명(착수): `TASK2779B_AXIS_B_STOP_HOOK_DRYRUN_HIGH_MICROFIX_APPROVED_ACTIVE_FALSE`
담당: dev2-team (오딘)
승인: 회장 2026-06-29 (GPT 단계판정: fresh Gemini active HIGH 2건 = 핵심 safety invariant 갭 → **same-PR bounded microfix +1**. backlog 아님. merge/strict/hook 미승인)
근거: PR #260 fresh Gemini(head c0f92f1b) active HIGH ×2(동일 이슈) / 독립검증 `task2779b_axis_b_independent_verification_260629.md` / 실행계약 `terminal_callback_enforcement_axis_b_stop_hook_dryrun_contract_260629.md`
대상 PR: **#260 (기존 브랜치 `task/task-2779_b-dev2`, head fast-forward 갱신)** — 새 PR·force push·merge 금지.

## 레벨
보안/safety microfix (1파일 중심 + 테스트). same-PR head 갱신까지.

## merge_policy
none — head 갱신까지만. merge는 ANU 독립검증→fresh Gemini active HIGH 0→CI 후 회장 승인 별도. **봇 merge 금지.**

## 문제 (fresh Gemini active HIGH ×2 — head c0f92f1b, line 61, outdated=false)
`scripts/stop_hook_terminal_enforcer.py`의 `_is_production_canonical()`이 `given == prod`(정확 일치)만 차단 → canonical_root를 운영 root의 **하위 디렉토리**(예: `/home/jay/workspace/sub_project`)로 주면 검사 우회 → **운영 환경 하위에 marker/manifest 생성 위험**. 축 B 핵심 safety invariant("실제 canonical marker 0")의 구멍.

## 필수 수정 목표 (1줄 수준)
`_is_production_canonical`이 운영 root 일치뿐 아니라 **하위경로도 차단**:
```python
        given = Path(canonical_root).resolve(strict=False)
        prod = Path(_PRODUCTION_CANONICAL_ROOT).resolve(strict=False)
        return given == prod or prod in given.parents
```

## allowed_resources

```yaml
allowed_resources:
  paths:
    - "scripts/stop_hook_terminal_enforcer.py"
    - "tests/regression/test_stop_hook_terminal_enforcer_2779.py"
    - "memory/reports/task-2779b-r2.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/**"
    - ".github/**"
    - ".claude/**"
    - ".env*"
    - "memory/state/**"
    - "memory/events/terminal_artifact_index/**"
    - "**/inbox/**"
    - "**/archive/**"
    - "**/quarantine/**"
  commands:
    - "git"
    - "pytest"
    - "python3"
    - "gh"
  merge_policy: "manual"
  ttl_hours: 8
```

## 필수 요구 (12)
1. `_is_production_canonical`: `given == prod or prod in given.parents`로 하위경로 차단
2. 운영 root 하위경로(`/home/jay/workspace/sub_project`)도 marker 생성 거부
3. 정확 일치 root도 기존대로 거부(무회귀)
4. 격리 tmp root는 정상 허용(거짓양성 0)
5. broad refactor 금지 — 이 함수 + 관련 테스트만
6. dry-run 비차단/fail-open 등 기존 동작 무변경
7. 실제 Stop hook 설치 0 · 실제 종료 차단 0 · strict 0
8. callback/real fire/systemd/cron 0 · raw key/ANU key 0
9. finish-task.sh 본체 변경 0
10. P0c 증거 + 축 A 산출물 무변경
11. expected_files 밖 수정 0 · forbidden paths 변경 0
12. ACTIVE=false 유지

## 테스트 필수 (5, 격리)
1. 운영 root 정확 일치 → marker 거부(무회귀)
2. **운영 root 하위경로(`/home/jay/workspace/sub_project`) → marker 거부(신규, 이번 HIGH)**
3. 격리 tmp root → 정상 marker 생성(거짓양성 0)
4. sibling-prefix(`/home/jay/workspace_evil`)는 under-prod 아님 → 허용(오판 금지)
5. 기존 stop-hook 10 + 축 A 8 + 2778 31 무회귀

## 검증 요구 (10)
1. `python3 -m py_compile scripts/stop_hook_terminal_enforcer.py`
2. `pytest tests/regression/test_stop_hook_terminal_enforcer_2779.py` PASS
3. 축 A `test_pre_push_terminal_guard_2779.py` + 2778 `test_terminal_artifact_visibility_2778.py` PASS(무회귀)
4. L1 smoke: 실제 `/home/jay/workspace` **및 하위경로** 모두 marker 생성 0 확인
5. raw key/ANU key/self-key 0
6. expected_files 밖 수정 0 · forbidden paths 변경 0
7. callback/fire/systemd/cron/activation 0
8. PR #260 head fast-forward only · force push 0 · 새 PR 0 · merge 0
9. ACTIVE=false · P0c+축A 증거 무변경 · G4 marker 무변경
10. strict/blocking·실제 hook 미포함

## STOP 조건 (→ `TERMINAL_CALLBACK_ENFORCEMENT_CONTRACT_STOP_REPORT`)
expected_files 밖 수정 필요 / 실제 hook 설치·strict 필요 / production canonical에 marker 남겨야만 테스트 가능 / HIGH가 다른 축으로 번짐 / fresh Gemini 후 HIGH·CRITICAL 유지.

## 금지 (12)
merge / auto-merge / broad refactor / 실제 Stop hook 설치 / strict·blocking 승격 / finish-task.sh 본체 변경 / 실제 pre-push hook 설치 / callback delivery·real fire / owner key·raw key·credential touch / systemd·cron·daemon / G4 marker 삭제 / hygiene backlog 동시 처리 — **전부 금지.**

## 완료 후보 상태명
- 구현 완료: `TASK2779B_AXIS_B_STOP_HOOK_DRYRUN_HIGH_MICROFIX_DONE_ACTIVE_FALSE`
- 이후: ANU 독립검증 → fresh Gemini active HIGH/Critical 0 → CI → MERGE_CANDIDATE_READY (회장 승인 별도)
- HIGH 유지/다른 축: `TASK2779B_AXIS_B_LOOP_BOUNDARY_REVIEW_REQUIRED_ACTIVE_FALSE`

## callback / 종료 규칙
- 결과(성공/실패/blocked 무관) **봇 idle 전 ANU callback 필수**. callback ≠ merge ≠ finish-task.
- ANU-owned callback envelope만. **dev self-key callback fire 금지. raw key 출력 금지.**
- **기존 worktree `.worktrees/task-2779_b-dev2` / 브랜치 `task/task-2779_b-dev2`에서 작업, PR #260 head ff 갱신**.
- ★ branch underscore→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`