---
task_id: task-2471+1
type: hardening-fix
scope: silent_corruption_guard self-application + .done.escalated reason payload
status: completed
---

# task-2471+1 — 결정 근거 + 3 Step Why

## 1. 핵심 진단 결과 (코드 경로 박제)

### 1.1 빈 `.done.escalated` 발행자 — 정확한 코드 경로
**파일**: `scripts/done-watcher.sh`
**라인**: 96-122
**호출 stack**:
```
cron / systemd timer
  → /home/jay/workspace/scripts/done-watcher.sh (script entry)
    → for done_file in $(find $EVENTS_DIR -name "*.done" ...)  # line 96
      → file_age >= 1800 (30분 stale 체크)  # line 100
        → escalated_file=$EVENTS_DIR/${task_id}.done.escalated  # line 101
        → python3 -c "os.open(path, O_CREAT|O_EXCL|O_WRONLY); os.close(fd)"  # line 105-115
```
**결함 정확 위치**: line 110-114 — `os.open(path, os.O_CREAT|os.O_EXCL|os.O_WRONLY)` 후 즉시 `os.close(fd)`. **fd에 어떤 데이터도 write하지 않음**.
**시간 일치 증거**: `.done` mtime 03:55, `.done.escalated` mtime 04:25 = 정확히 1800s (30분) 후. cron 주기 매칭.

### 1.2 `.done` 발행자 — 두 단계 합성
**1단계**: `scripts/finish-task.sh:1003-1024` — atomic O_EXCL 발행 (status, gate_results, completed_at)
**2단계**: `memory/task-timer.py:_write_event_file` (line 518-581) — `task-timer.py end` 호출 시 기존 `.done` 읽어서 추가 필드 (team_id, end_time, duration_seconds) 병합 → atomic rename으로 덮어쓰기

### 1.3 silent_corruption_guard 자기 적용 실패
**hardening 위치**: `scripts/taskctl.py:cmd_done` (1622+)에만 통합
**production 경로**: `finish-task.sh:1003-1024`이 `.done`을 직접 발행, `taskctl done` 호출 안 함
**결과**: hardening은 호출되지 않는 dead path에 박제. 본 task가 hardening의 "false security" 정확히 노출.

### 1.4 state COMMITTED 영구 정지 원인
**finish-task.sh의 taskctl 호출**:
- line 439: `taskctl status` (read-only)
- 그 외: pr-open, merge, done, recover **호출 0건**

따라서 task-2471은 마지막 transition (RUNNING → COMMITTED at 2026-05-06T18:00:29Z) 이후 어떤 state 변화도 없음. PR 머지 (18:31:18Z) 이후에도 state는 COMMITTED.

### 1.5 silent_corruption_guard 자체 결함
**기존 검사 항목** (verify_done_preconditions):
- check_pr_merged_at — mergedAt not null
- check_pr_merge_commit_oid — mergeCommit.oid not null
- check_origin_main_ancestry — origin/main에 ancestry

**누락 항목**: `.done` + `.done.escalated` 동시 존재 패턴 → 본 task 결함이 정확히 이 사각지대.

### 1.6 origin branch auto-delete 미작동
`task/task-2471-dev2` 브랜치가 origin에 잔존 (43ce82ba). 가능 원인:
- PR #36 머지가 `--delete-branch` 옵션 없이 수행됨
- worktree_manager.finish (`scripts/finish-task.sh:466`)이 머지 후 후속 단계에서 호출되지 않음 (실제 .merge-done 미생성)

### 1.7 chairman manual recovery audit 채널 결론
**결론**: **불필요**. 본 사고는 manual recovery로 덮을 사고가 아닌 hardening의 자기검증 실패. 회장이 직접 명시: "manual recovery로 덮지 말고 drink-your-own-champagne 검증으로 처리한다."

## 2. 3 Step Why (회장 명시 자문)

### 1st Why — 왜 이 설계가 필요한가?
**A**: task-2471 hardening이 추가한 `silent_corruption_guard` (taskctl.py cmd_done에만 통합)이 production 경로(finish-task.sh)에서 호출되지 않아 자기 자신에게 적용 안 됨. drink-your-own-champagne 실패. 따라서 hardening을 production 경로(finish-task.sh)에 적용 + done-watcher.sh의 빈 marker 결함 동시 수정 필요.

### 2nd Why — 왜 A가 최선의 접근인가?
**B**: 대안 1 — finish-task.sh를 taskctl 명령으로 전환 (전면 리팩토링). 대안 2 — silent_corruption_guard 호출만 finish-task.sh에 추가 (최소 침습). 회장이 "최소 코드 수정"을 명시했고 finish-task.sh는 워크플로우 핵심 진입점이라 전면 리팩토링은 회귀 위험이 큼. 따라서 대안 2가 최선. 

**Codex 사전 리뷰에 포함할 질문**: "이 설계의 대안은 무엇이며, 왜 이 접근이 최선인가?" — 대안 1/2를 명시 비교.

### 3rd Why — 왜 B가 다른 대안보다 나은가?
**C**: 
- 대안 1 (전면 리팩토링)은 finish-task.sh ~1052 LOC 중 머지/QC/.done/timer/notify 5단계 모두 영향 → 2 ~ 3일 작업 + 회귀 위험 다수.
- 대안 2 (최소 침습)는 finish-task.sh 단일 위치 (line 1003 직전)에 import + 호출 추가 → 10~20 LOC + 즉시 검증 가능 + 회귀 위험 최소.
- 본 task ttl_hours=4 제한 + 회장 "진단 우선" 지침에 부합.
- 향후 전면 리팩토링은 별도 task로 진행 가능 (본 task 범위 외).

A→B→C 일관성: PASS. 진단 우선 + 최소 침습 + 회귀 최소화로 일관됨.

## 3. 코드 수정 결정 박제

### F1 — done-watcher.sh 빈 marker 결함
- **결정**: line 105-115의 python3 inline을 수정하여 atomic O_EXCL + JSON payload (`{"trigger":"stale_done","ts":"...","source":"done-watcher.sh:96-122","done_path":"...","age_seconds":N}`) 박제.
- **사유**: 빈 marker는 archeology 불가능 + 향후 동일 사고 재발 시 추적 불가.

### F2 — silent_corruption_guard `.done` + `.done.escalated` 동시 존재 탐지
- **결정**: 신규 함수 `check_done_escalated_conflict(task_id, *, events_dir=None) -> dict`. `verify_done_preconditions`의 4번째 check로 추가.
- **사유**: 회장 §7-9 "동시 존재 시 reject 테스트" 명시.

### F3 — finish-task.sh `.done` 발행 직전 silent_corruption_guard 호출
- **결정**: `finish-task.sh:1003` 직전 (P0-1 .g3-fail 검사 다음)에 `verify_done_preconditions` + `check_done_escalated_conflict` 호출. fail 시 `.done` 차단.
- **사유**: hardening의 자기 적용 보장. drink-your-own-champagne layer 2.

### F5 — task-2471 state COMMITTED → DONE 정상 전이
- **결정**: 본 task 워크트리에서 `taskctl pr-open task-2471 --pr 36` → `taskctl merge task-2471` → `taskctl done task-2471` 정상 transition 박제.
- **사유**: state machine 정합성 검증. admin override 미사용. 정상 transition 경로 사용.

### F6 — `.done.escalated` 사유 박제 후 archive
- **결정**: 0 byte 파일을 사유 JSON으로 덮어쓴 후 `memory/events/archive/`로 이동.
- **사유**: 회장 §6-4 "사유 박제 후 명시 해소" 명시. silent corruption guard fix(F2/F3)가 이 충돌을 차단할 것.

### F7 — branch cleanup
- **결정**: 분석 결과 머지 commit이 `1f96ddcd` (origin/main HEAD)이고 task-2471-dev2의 모든 commit이 main에 포함됨. 따라서 안전 삭제 가능. 단, `gh api ... -X DELETE` 또는 `git push origin --delete` 호출은 보고서에 결과를 박제한 후 수행 (회장 §8 forbidden 검토 필요 — git push --force 금지지만 단순 delete는 forbidden 목록에 없음).

## 4. 위임 분배 (sanitize 게이트 통과 후)
- 토르(sonnet): F1, F2, F3 (코드 수정 3건)
- 헤임달(sonnet): F4 (regression test 2건)
- 오딘(opus): F5/F6/F7 (state 전이/marker 박제/branch cleanup) + 통합 + 보고서

## 5. Sanitize 게이트
- 대상: 본 task의 코드 + 설계 문서
- 마스킹 대상 항목 검사: 주민등록번호/연락처/API 키/계좌번호/보험 증권번호
- 결과: PII 미발견 (시스템 작업, 사용자 데이터 무관). PASS.

## 6. drink-your-own-champagne 메타 검증
- task-timer.py 등록 ID: `task-2471+1` (확인됨, dispatch ID 정확 보존)
- task_id에 `+1` suffix가 task_id_parser.parse_task_id_v2에 의해 정상 인식되는지 검증: `parse_task_id_v2("task-2471+1") → {base: "task-2471", retry: "1"}`
