# task-2778 — Terminal Artifact Visibility Enforcement (설계 문서)

상태명: `TASK2778_TERMINAL_ARTIFACT_VISIBILITY_ENFORCEMENT_DESIGN_APPROVED_ACTIVE_FALSE`
작성: ANU 직접 (dev팀 위임 아님 — callback-miss 사이클 재발 회피)
작성일: 2026-06-28 KST
범위: **설계 only**. 코드 0 · hook 설치/수정 0 · pre-push 설치 0 · merge 0 · real fire 0 · activation 0.
승인 경계: 본 문서는 회장 단계승인(ChatGPT 단계판정 4번 결정)에 따른 설계 산출물. 구현은 별도 승인.

---

## 0. 한 줄 요약

이번 사건의 근본 원인은 "dev2가 callback/.done을 안 남긴 것"이 **아니다**.
dev2는 worktree-local에 terminal artifact(report·.done)를 **남겼으나**, 그것이
canonical `/home/jay/workspace`의 ANU authoritative polling 시야로 **surface되지 않아**
terminal callback miss처럼 보였다. 따라서 강제해야 할 것은 단순한 "남기기"가 아니라
**"ANU가 authoritative하게 볼 수 있는 위치로 surface하기"**다.

최종 원인 한 줄: `canonical ↔ worktree terminal artifact visibility gap`

---

## 1. 사건 재현 (실측 evidence, 2026-06-28)

PR #257 / task-2777+2 / head `9ce3d561e928de5d5ae9e7539e3de0944300c715` / branch `task/task-2777-dev2`

관측된 디스크 사실:

- canonical `/home/jay/workspace/memory/events/anu_callback/task-2777+2-normal-completion.json`
  envelope는 **존재**. `envelope_only:true`, `executor_self_key_used:false` (self-key 미사용 = 정상).
- 그 envelope가 가리키는:
  - `result_path: memory/events/task-2777+2.done` → canonical에 **부재**
  - `report_path: memory/reports/task-2777-r2.md` → canonical에 **부재**
- 그러나 worktree `/home/jay/workspace/.worktrees/task-2777-dev2/` 안에는:
  - `memory/reports/task-2777-r2.md` → **존재** (committed, head 9ce3d561, git clean)
  - `memory/events/task-2777+2.done` → **존재** (worktree-local)
- gitignore 판정:
  - `memory/reports/task-2777-r2.md` → **tracked** (PR branch에 commit, PR diff에 포함)
  - `memory/events/task-2777+2.done` → **gitignored** (PR diff에 안 오름, worktree-local only)
- +1 envelope(task-2777+1)도 **동일 패턴**(report/.done canonical 부재, worktree 존재) → 1회성 아님, 체인 전반 반복.

해석 정정:

- 이전 해석: `dev2가 callback/.done을 안 남겼다`
- 정정 해석: `dev2가 worktree-local terminal artifact는 남겼으나, canonical ANU polling 시야로 surface되지 않아 terminal callback miss처럼 보였다`

이로 인한 2차 오판 위험: ANU의 canonical-only 폴링은 envelope가 "dangling pointer"인 것처럼
보이게 만들고, "terminal callback missing"으로 오판하게 한다. 실제로는 artifact가 존재하므로
재생성 요구는 불필요한 사이클 + 추가 callback-miss 위험을 만든다.

---

## 2. 누락 고리 진단 (구조)

terminal signal 강제 고리가 push / finish-task / Stop hook / owner-pickup 사이에 비어 있다.

1. **pre-push hook 부재** — push 시점에 `.done/report/envelope`의 canonical 가시성을 강제하지 못함.
2. **Stop hook이 enforcer가 아니라 verifier** — 없을 때 만들어 surface시키는 게 아니라 확인만 함.
3. **finish-task가 SUCCESS/QC 통과 중심** — start-guard-fail, CI fail, dirty, 중간 종료에서는
   terminal artifact가 canonical로 안 넘어올 수 있음.
4. **ANU collector가 canonical-only 스캔** — active task의 worktree path를 authoritative하게
   조회하지 않아 worktree-local terminal artifact를 놓침.

→ 단일 hook 하나로는 부족. **(가시성 surfacing) + (생성 강제)** 2겹이 필요.

---

## 3. 설계

### A. canonical ↔ worktree visibility gap (근본 축)

반드시 명문화:

- worktree 안에는 report/.done 존재할 수 있음
- canonical root에는 동일 artifact 부재일 수 있음
- `.done`은 gitignore/local marker라 PR diff에 안 오름 (정상)
- report는 PR branch에는 있으나 main/canonical에는 merge 전까지 안 보임
- canonical-only polling은 worktree terminal artifact를 놓칠 수 있음
- 그 결과 envelope가 dangling처럼 보이거나 terminal callback miss로 오판될 수 있음

핵심 불변식(설계 목표): **terminal artifact가 어디서 생성되든(worktree-local·gitignored 포함),
ANU가 canonical authoritative 위치에서 그 존재를 알 수 있어야 한다.**

### B. terminal artifact surfacing 구조 — 후보 비교

| 후보 | 메커니즘 | 장점 | 단점/위험 |
|---|---|---|---|
| B1 | ANU collector가 active task worktree path를 authoritative 조회 | 코드 변경 최소, 읽기 전용 | task→worktree mapping registry 필요, collector 복잡도↑ |
| B2 | worktree-local `.done/report/envelope`를 canonical `memory/events/terminal_artifact_index/`에 manifest로 surface | canonical 단일 진실원, 폴링 단순 | manifest 생성 주체/시점 정의 필요 |
| B3 | pre-push hook이 terminal artifact index를 canonical에 생성 | push 경로에서 강제 | push 안 하는 종료 경로(중단/실패)는 못 잡음 |
| B4 | Stop hook enforcer가 terminal artifact index를 canonical에 생성 | 모든 종료 경로 커버(근본) | Stop hook을 enforcer로 승격 필요 |
| B5 | ANU-owned pickup runner가 worktree+canonical 양쪽 스캔 | 활동-독립 수거 | runner 상시성 필요(P0-B 결합 주의) |

> 표 표기는 설계 비교용. 실제 구현 시 GFM 표 대신 본문 서술로 옮긴다(보고 채널 규칙).

권장 조합(설계안, 구현은 별도 승인):
- **공통 기반**: task_id → branch → worktree path **mapping registry** 유지 (B1/B5 공통 전제).
- **canonical 단일 진실원**: `memory/events/terminal_artifact_index/<task_id>.json` manifest (B2).
- **생성 강제 2겹**: pre-push(B3, warn 우선) + Stop hook enforcer(B4, 근본).
- **수거**: ANU collector/pickup이 manifest + worktree 양쪽을 읽음 (B1+B5).

### C. pre-push terminal guard

- task branch에서만 작동 (main/canonical push 미간섭)
- push 직전 task_id, branch, head, **worktree path** 확인
- report/.done/envelope 존재 여부 확인
- 없으면 `PUSH_ATTEMPT_WITHOUT_TERMINAL_MARKER` manifest/envelope를 **canonical에** 생성
- 있으면 worktree-local artifact를 canonical manifest로 surface
- 초기 모드 = **strict block 아님, warn/envelope mode** (작업물 유실 방지 — push 자체는 허용)
- strict mode(없으면 push 차단)는 **별도 승인 후** 승격
- dev self-key callback fire 금지 · raw key 사용 금지
- ANU-owned pickup 대상 artifact만 생성

### D. Stop hook terminal enforcer (근본)

- 기존 Stop hook **verifier → enforcer 승격** 설계
- callback/.done/result가 있으면 PASS (+ canonical manifest로 surface 보장)
- 없으면 `TERMINAL_CALLBACK_MISSING` failure artifact를 **canonical에** 생성
- artifact는 **canonical에서 ANU가 볼 수 있어야 함** (이번 사건의 핵심 교정점)
- 포함 필드: task_id, branch, head, dirty, PR number, last known status
- 성공/실패/차단/문제 **모두** terminal status로 남김
- merge와 callback **분리** (callback ≠ merge ≠ finish-task)
- dev self-key callback fire 금지 · raw key 출력/기록 금지
- enforcer 자체가 crash하지 않도록 **fail-open + explicit-marker** 원칙

### E. 상태 enum (필수 10종)

- `CALLBACK_DELIVERED` — 실제 cokacdir schedule status ok delivery
- `CALLBACK_ENVELOPE_ONLY` — envelope만, delivery 미확인
- `CALLBACK_ENVELOPE_DANGLING_TARGETS` — envelope가 가리키는 result/report가 (canonical에서) 안 보임
- `WORKTREE_LOCAL_TERMINAL_ARTIFACT_PRESENT` — worktree에는 artifact 존재
- `CANONICAL_TERMINAL_ARTIFACT_MISSING` — canonical에는 artifact 부재
- `TERMINAL_ARTIFACT_SURFACED_TO_CANONICAL` — surfacing 성공(목표 상태)
- `PUSH_ATTEMPT_WITHOUT_TERMINAL_MARKER` — push 시점 terminal marker 부재
- `STOP_HOOK_ENFORCED_TERMINAL_RESULT` — Stop hook이 강제로 terminal 기록
- `FINISH_TASK_BLOCKED_BUT_TERMINAL_RECORDED` — finish-task 차단됐으나 terminal은 남김
- `ANU_PICKUP_FROM_WORKTREE_REQUIRED` — ANU가 worktree에서 직접 수거 필요

### F. finish-task와의 역할 분리

- finish-task = SUCCESS/QC/merge-readiness 경로 (기존 유지)
- terminal enforcer = **모든 종료 상태**의 신호 보존 (성공 아닌 경로 포함)
- 둘은 직교. enforcer는 finish-task 성공 여부와 무관하게 terminal signal을 canonical에 보장.

### G. ANU-owned pickup 연계

- generated artifact/manifest를 ANU-owned pickup이 수거할 수 있게 설계
- executor(dev bot)는 **직접 callback fire 안 함** — envelope/manifest만
- callback delivery와 terminal artifact 생성을 **구분**
- 향후 P0-B always-on pickup과 연결 가능하되, 본 설계는 ACTIVE=false에서도 쓸 수 있는 최소 강제부터.

---

## 4. rollout 순서 (각 단계 별도 승인)

1. design only (본 문서)
2. isolated parser/index tests (격리)
3. ANU collector worktree-read support (읽기 전용 surfacing)
4. pre-push warn/envelope mode
5. Stop hook enforcer dry-run mode
6. strict mode
7. OS pickup integration

> 3까지는 읽기/manifest 생성(비파괴), 4~6은 hook 결선, 7은 상시성. 각 경계에서 회장 승인.

---

## 5. 구현 후보 (참고 — 구현 승인 시 확정)

expected_files 후보:
- `dispatch/terminal_artifact_index.py` (manifest 생성/조회, 읽기+append)
- `scripts/pre_push_terminal_guard.sh` (또는 .py) — task branch push 가드
- Stop hook enforcer 모듈 (기존 Stop hook 승격분)
- ANU collector worktree-read 확장
- `tests/regression/test_terminal_artifact_visibility_2778.py`

테스트 후보:
- worktree-local artifact 존재 + canonical 부재 → surface manifest 생성 검증
- gitignored .done이 manifest로 표면화되는지
- pre-push warn mode: marker 부재 시 envelope 생성 + push 허용
- Stop hook enforcer: 종료 전 terminal 부재 → TERMINAL_CALLBACK_MISSING 생성
- enforcer fail-open: 내부 오류 시 crash 없이 explicit marker
- self-key 미사용 / raw key 0 정적 검증

---

## 6. 위험 및 rollback

- **위험 1**: pre-push strict mode 조기 도입 → 작업물 push 차단 → 산출물 유실. **완화**: warn 우선.
- **위험 2**: Stop hook enforcer crash → 종료 자체 방해. **완화**: fail-open + explicit-marker.
- **위험 3**: surfacing이 dev self-key callback fire로 변질. **완화**: executor는 manifest only, fire 금지.
- **위험 4**: manifest 폭주/중복. **완화**: task_id 키 dedupe, append-only ledger.
- **rollback**: 각 단계는 flag/mode 분리. warn→strict, dry-run→enforce 모두 off로 즉시 복귀 가능.

---

## 7. 금지 (본 설계 단계)

지금 hook 코드 수정 / pre-push 설치 / Stop hook 수정 / PR #257에 enforcement 섞기 /
callback real fire / dev self-key callback / raw key 추가 / systemd·always-on activation /
production activation / merge — **전부 금지**.

---

## 8. 완료 상태명

`TASK2778_TERMINAL_ARTIFACT_VISIBILITY_ENFORCEMENT_DESIGN_READY_ACTIVE_FALSE`

이후 구현은 단계별 별도 승인으로 진행.
