# task-2782 — 4종 종단 terminal callback 결선 (구현 후보+테스트까지만, ACTIVE=false)

## 레벨
Lv3 (finalize/callback 경로 수정 + 회귀 테스트). 코드 작업.

## allowed_resources (본 task의 capability)

```yaml
allowed_resources:
  paths:
    - "scripts/finish-task.sh"
    - "scripts/harness/v36/terminal_state_callback.py"
    - "tests/regression/test_finish_task_terminal_termination_2782.py"
    - "memory/reports/task-2782.md"
  forbidden_paths:
    - "utils/replacement_pr_runner.py"
    - "utils/merge_queue_executor.py"
    - "utils/real_merge_hooks.py"
    - "utils/repository_policy_adapter.py"
    - "utils/automation_contracts.py"
    - "scripts/gemini_evidence_verify.py"
    - "dispatch.py"
    - ".git/hooks/**"
    - ".github/**"
    - ".claude/**"
    - ".env*"
    - "memory/state/**"
  commands:
    - "git"
    - "pytest"
    - "python3"
    - "gh"
  merge_policy: "none"
  ttl_hours: 8
```

## 목표 (한 줄)
finish-task 4종 종단(SUCCESS/FAILED/BLOCKED/ESCALATED) **모두** ANU terminal status(terminal marker + 최소 result.json + terminal callback 등록 또는 fail-closed marker)가 남도록 결선한다. `.done.escalated`를 정상완료가 아니라 **terminal callback 대상**으로 편입. dispatched marker 부재 시 **silent exit 0 금지**. **코드+테스트까지만.**

## 단일 소스 (반드시 먼저 읽을 것)
초안: `memory/reports/bside_bounded_task_draft_260702.md` (§enum·envelope·테스트 그대로 구현)
근거: `memory/reports/infra_defect_two_axis_260702.md` (B축 root-cause)

## expected_files (이 3개만)
- `scripts/finish-task.sh` — state_file_missing/ESCALATED/BLOCKED/FAILED 분기(≈878~929)에 (a)최소 result.json 원자적 write (b)terminal_state_callback emit 호출 추가.
- `scripts/harness/v36/terminal_state_callback.py` — dispatched marker 부재 시 **fallback context(dispatch schedule_id) 또는 fail-closed marker**. **silent exit 0 제거**.
- `tests/regression/test_finish_task_terminal_termination_2782.py` (신규).

## 종단 상태 enum (고정)
terminal_state: **SUCCESS / FAILED / BLOCKED / ESCALATED** — 각각 필수 3산출: ①terminal marker ②최소 result.json ③terminal callback 등록 **또는** fail-closed marker.
context fallback: `DISPATCHED_MARKER_MISSING_FALLBACK_SCHEDULE_ID` / `CALLBACK_CONTEXT_UNRESOLVED_FAIL_CLOSED`.

## result.json 최소 envelope (고정 스키마)
`{task_id, terminal_state, result_path(self-ref 허용), report_path, callback_schedule_created:bool, callback_context, pickup_required:bool, reason, ts(UTC ISO8601)}`. 모든 종단 분기가 terminal marker 발행 **직후 원자적 write**. write 실패 시 finalize를 fail-closed(침묵 통과 금지). ★ `pickup_required`(owner-pickup 필요 여부) 명시.

## ★★★ 회장 dispatch 경계 (2026-07-03, 최상위 준수) — `TASK2782_TERMINAL_CALLBACK_ARTIFACT_FIX_DISPATCH_APPROVED_WITH_TRUNCATE_WATCH_ACTIVE_FALSE`
**핵심 = terminal artifact 생성 보강. callback delivery 활성화 아님.**
- artifact/marker/result.json 생성은 **허용**.
- **executor self-key callback 발사 금지.** **ANU key literal 금지**(코드/테스트 어디에도 ANU owner key 문자열 하드코딩 0).
- **owner-pickup runner 활성화 금지.** real fire / systemd / cron / always-on runner **금지**.
- **`CALLBACK_DELIVERED` 승격 금지.** owner pickup이 실제 확인되지 않으면 **최대 상태 = `CALLBACK_REGISTERED_BUT_NOT_PICKED_UP` 또는 `OWNER_PICKUP_REQUIRED`**. 분류 enum: `CALLBACK_DELIVERED / CALLBACK_NOT_DELIVERED / CALLBACK_REGISTERED_BUT_NOT_PICKED_UP / CALLBACK_FALSE_CLAIM`.
- **ACTIVE=false 유지.**
- **PR #258 artifact surfacing 재구현 금지.** 2782=**WRITE/emit 층**(종단마다 status를 남게), 2778A=**READ/surface 층**(이미 남은 걸 드러냄) — 분리 유지.
- 종단마다 **종단 상태·reason·task id·artifact path·pickup_required 여부** 명확히 남길 것.
- **truncate watcher 전제**: ANU가 dispatch 전 bounded stdlib polling watcher(90분·canonical 2파일·복원0)를 **arm 완료**. 봇은 watcher 신경 쓸 필요 없음. 단 canonical 2파일(utils/replacement_pr_runner.py·test) **절대 무접촉**.
- **PR/merge/activation 미승인** — worktree 커밋 + ANU 보고까지만.

## terminal callback 보장 방식
- escalation/BLOCKED/FAILED 분기에서도 `terminal_state_callback emit` 호출(현재 SUCCESS .done 경로만 커버).
- terminal_state_callback: dispatched marker 있으면 그 schedule_id; 없으면 dispatch 시점 산출물(capabilities/task-<id>.json 또는 cron_response schedule_id) fallback; 그래도 불가면 **fail-closed marker(.terminal-callback-failed.json)** + 명시 신호(비차단이되 침묵 금지).
- ★ executor self-key 발사 0 유지(OS-level pickup contract). 자가 콜백 신규 생성 금지 — "종단마다 terminal status가 남는다"만 보장.

## 테스트 (신규 회귀, mock/tmp 격리 — canonical 무접촉, 10케이스)
1 state_file_missing escalation→result.json+callback marker/registration / 2 dispatched marker 부재→fallback 또는 fail-closed marker(silent exit0 금지 assert) / 3 BLOCKED→result.json+callback / 4 FAILED(QC FAIL)→result.json+callback / 5 SUCCESS(.done) 회귀 0 / 6 4종 종단 terminal marker 전수 assert / 7 callback 등록 실패 주입→fail-closed marker / 8 escalation result.json에 callback_schedule_created·callback_context·pickup_required 필드 / **9 self-callback(executor self-key) 발사 0 확인·ANU key literal 미등장 assert / 10 owner_pickup_required 상태가 `CALLBACK_DELIVERED`로 승격되지 않음 확인(최대 `CALLBACK_REGISTERED_BUT_NOT_PICKED_UP`/`OWNER_PICKUP_REQUIRED`)**.

## STOP_REPORT 조건 (하나라도 해당 시 즉시 중단·보고)
- expected_files 밖 수정 필요 / self-key callback 발사 필요 / owner-pickup 활성화 필요 / real fire·cron·systemd·always-on 요구 / merge-executor 체인(replacement_pr_runner 등) 수정 필요 / **canonical 파일 터치 발생** / 테스트가 live finish-task 실행을 proof로 주장 / task-2782를 callback delivery 완료로 표현하려는 경우 / 종단 게이트 구조가 예상(878~929)과 상이.

## ★★ 종료 검증 함정 (GPT verbatim — 반드시 준수)
- **봇 본인의 최종 finish-task는 canonical(수정 전) finish-task.sh로 실행될 수 있다.** 따라서 **봇 자신의 finish-task 종단 결과를 수정본의 end-to-end proof로 주장 금지.**
- 수정본 동작 검증은 **pytest / mock / tmp harness 또는 worktree 경로를 명시한 isolated 실행으로만** 보고.
- finish-task가 escalate로 끝나도(2781처럼) 그것을 "결선 고쳐짐"으로 해석 금지.
- **본 task 완료 후에도 상태는 여전히 구현 후보(ACTIVE=false).** 실제 활성화는 PR→리뷰→merge 후 별도 검증.

## ACTIVE=false 불변식 / 금지
- **PR 생성 금지 · merge 금지 · ci.yml/ruleset 변경 0 · Merge Queue 0 · real fire 0 · executor self-key 콜백 0.**
- merge-executor 체인(replacement_pr_runner 등, A축 조사 대상) **무접촉**.
- 산출물 = 수정 2파일 + 신규 테스트 + 로컬 pytest 통과 + worktree 커밋 + ANU 보고.

## 산출물 & 보고 (PR 생성 없음 — ANU 보고까지만)
1. worktree 브랜치에 커밋. **PR 생성하지 말 것**(GPT 금지).
2. ANU 보고: (a)수정/생성 파일 범위 (b)로컬 pytest 결과 (c)forbidden 무접촉 증거(.github/·merge-executor 체인 diff 0) (d)수정본 검증은 isolated 실행 근거만.
3. 봇 종료 전 ANU callback(envelope만·UTF-8 ≤3900B). finish-task 종단이 escalate여도 result.json/보고서 경로를 envelope에 담아 ANU에 남길 것.

## 검증 루프
자체 리뷰 1회 → pytest 통과 → forbidden 무접촉 diff 확인 → 함정(종료검증) 준수 확인.