# task-2352 — dispatch.py 취소 신호 강제 처리 보강

- 작업 ID: task-2352
- 팀: dev7-team (이참나 팀장)
- 작업 레벨: Lv.2 (시스템 신뢰도)
- 작업일: 2026-05-02

## SCQA

### S(상황)
2026-05-02 task-2349(페룬 dispatch) 직후 회장(제이) 방향 변경 결정. 아누(개발실장)가 두 가지 STOP 신호를 발사했다:
1. `task-2349.md` 첫 줄에 STOP 마커 추가
2. dev6 봇(페룬, 키 `1e41a23...`)에게 직접 cron으로 "task-2349 즉시 중단" 메시지 발송 (cron `AB4B6C30`)

→ 페룬은 두 신호를 모두 무시하고 작업 완료, `.done` 생성, 머지까지 진행. 결과물은 brainstorming 결정의 99%를 반영해 손실은 미미했으나, 시스템 신뢰도 측면에서 "취소 신호 무시" 패턴은 회장 결정 변경 시 잘못된 방향 코드 양산 → 머지 후 되돌리기 비용 폭증의 위험을 내포.

### C(복잡 요인)
1. **dispatch.py에 cancellation 인프라 부재**: `--cancel` 플래그, `.cancelled` 마커 폴링, STOP 마커 인식 모두 부재.
2. **finish-task.sh에 취소 가드 없음**: `.cancelled` 가 있어도 그대로 .done 생성.
3. **봇 워크플로우 문서(DIRECT-WORKFLOW.md)에 취소 신호 처리 절차 부재**: 봇이 task 파일 첫 줄을 매번 검사하지 않음.
4. **cron 메시지 도달 타이밍 의존**: 봇이 도구 호출 사이에 cron 메시지를 polling하지 않으면 무시 가능.
5. **사후 검증 부재**: `.cancelled` 가 있는데 `.done` 이 만들어진 사고를 자동 감지할 수단 없음.

### Q(질문)
4계층(task 파일 STOP 마커 / .cancelled 이벤트 / dispatch --cancel 표준 명령 / 봇 워크플로우 가드)을 모두 도입해 봇이 회장 결정 변경 시 즉시 멈추도록 강제할 수 있는가?

### A(답변)
5개 파일에 가드/명령 도입 + 4개 시나리오 시뮬레이션 PASS.
- (A) `dispatch.py --cancel <task_id>`: 7단계 취소 시퀀스(검증 → STOP 마커 → .cancelled → cron 제거 → 봇 중단 메시지 cron → timer end CANCELLED) 표준화.
- (B) `finish-task.sh`: trap 직후 두 가드 — `.cancelled` 마커 발견 시 즉시 `.done` 차단, task 파일 상단 STOP 마커 발견 시 `.cancelled` 자동 생성 + 종료.
- (C) `qc_verify.py _handle_gate`: `.cancelled` 폴링 후 `.qc-result` 생성 스킵, `result["overall"]="CANCELLED"`.
- (D) `DIRECT-WORKFLOW.md`: `## ★ 작업 취소 신호 처리 (필수)` 신규 섹션 — 봇이 매 단계 시작 전 task 파일 head -3 + `.cancelled` 폴링하도록 명시.
- (E) `scripts/cancel-task.sh`: dispatch.py `--cancel` 셸 단축 (chmod +x).

## 진단 결과 (페룬이 어떻게 STOP 신호를 무시했는가)

타임라인 분석:
- 03:08:54 task-2349 dispatch
- 03:09:xx 페룬이 task 파일 처음 읽음 (이때 STOP 마커는 아직 없었을 가능성 높음)
- 03:10:24 아누가 STOP 마커 추가 + cron 중단 메시지 발사 (cron `AB4B6C30`)
- 03:10:24 이후 ~ 06:00~09:00 사이 페룬이 작업 진행 + .done 생성

**원인**: 페룬은 task 파일을 첫 dispatch 시점에만 한 번 읽었을 것으로 추정. 이후 단계에서는 캐시 또는 대화 컨텍스트의 task 내용만 사용. cron 중단 메시지는 페룬 세션 내 도구 사이클에서 polling하는 메커니즘이 없어 도달했어도 무시됨. `.cancelled` 마커 폴링도 dispatch.py / finish-task.sh / qc_verify.py 모두에 없어 사후 차단 게이트도 작동하지 않음.

## 수정 파일 목록

| 파일 | 변경 위치 | commit |
|------|---------|--------|
| `/home/jay/workspace/dispatch.py` | line 3567 (`cancel_task`), 3734 (`--cancel` argparse), 3876 (early dispatch 분기) | `39a830cd` |
| `/home/jay/workspace/scripts/finish-task.sh` | line 26~49 (trap 직후 두 가드 블록) | `941577e2` |
| `/home/jay/workspace/teams/shared/qc_verify.py` | line 598~602 (`_handle_gate` 맨 앞 .cancelled 폴링) | `8fa63047` |
| `/home/jay/workspace/prompts/DIRECT-WORKFLOW.md` | line 59 (`## ★ 작업 취소 신호 처리 (필수)` 신규 섹션) | `940aab1a` |
| `/home/jay/workspace/scripts/cancel-task.sh` | 신규 (chmod +x) | `9d3fa331` |

## 검증 시나리오 결과 (4/4 PASS)

| # | 시나리오 | 결과 | 핵심 출력 |
|---|---------|------|----------|
| 1 | STOP 마커 가드 (finish-task.sh, task-99999) | PASS | `[CANCELLED] task 파일 상단 STOP 마커 감지. .cancelled 생성 후 종료.` exit 0, `.done` 미생성 |
| 2 | `.cancelled` 폴링 가드 (finish-task.sh, task-99998) | PASS | `[CANCELLED] task task-99998는 취소된 작업입니다 (.cancelled 마커 발견). .done 생성 차단.` exit 0 |
| 3 | `dispatch.py --cancel` (task-99997) | PASS | JSON `{"status":"ok","task_id":"task-99997","actions":["stop_marker_added","cancelled_event"]}` |
| 4 | `cancel-task.sh` 셸 단축 (task-99996) | PASS | 인자 없으면 `Usage: ... <task_id>` + exit 1 / 인자 있으면 dispatch.py 동일 동작 |

부가 검증:
- `python3 -c "import dispatch; help(dispatch.cancel_task)"` docstring 정상 출력
- `python3 dispatch.py --help | grep -- '--cancel'` 인자 노출 확인
- `bash -n scripts/finish-task.sh`, `bash -n scripts/cancel-task.sh` 모두 OK

cleanup: 더미 task md 4건, `.cancelled` 4건, task-timers.json 더미 엔트리 3건 모두 삭제. `.done` 생성 없음.

## L1 스모크테스트 결과

- 서버 재시작: 해당없음 (시스템 인프라 작업, 운영 서버 영향 없음)
- API 응답 확인: 해당없음 (CLI 명령 + 게이트 스크립트 변경)
- 스크린샷: 해당없음 (UI 변경 없음)
- **CLI 실행 검증** (L1 대체): `python3 dispatch.py --cancel task-99996` → JSON `{"status":"ok",...,"actions":[...]}` 반환 + exit 0 / `bash finish-task.sh task-99999` → `[CANCELLED]` 출력 + `.cancelled` 생성 + exit 0 (4개 시나리오 모두 검증 완료)

## 모델 사용 기록

| 역할 | 모델 | 사용 사유 |
|------|------|-----------|
| 이참나 (팀장) | opus | 설계/분배/검토/통합 (코딩 직접 안 함) |
| 쿠쿨칸 (백엔드) | sonnet | 5개 파일 수정 — 일반 코딩/로직 구현 |
| 카마소츠 (테스터) | sonnet | 4개 시나리오 시뮬레이션 검증 |

haiku 미사용. 디자인/마케팅 작업 아님 → 로키 소환 불필요.

## 운영 메모 (후속 권고)

1. **사후 검증 자동화 (4순위 미적용)**: task 명세의 D안 — cron으로 5분 뒤 `.cancelled` 있는데 `.done` 만들어지면 알림 — 은 본 task에서 미구현. 후속 task로 분리 권장(영향 범위가 시스템 알림 전체로 커서 별도 검토 필요).
2. **봇 polling 빈도**: 본 보강은 (1) 진입 시 task 파일 head 검사 (2) finish-task.sh 진입 가드만 강제. 진행 중 도구 사이클마다 `.cancelled` 폴링은 봇의 자율적 선택에 의존 (DIRECT-WORKFLOW.md에 명시). 페룬급 무시가 재발하면 dispatch.py에 sidecar polling daemon 검토.
3. **task-2348/2350과 동일 영역**: finish-task.sh 의 trap 영역에 가드 삽입 — 기존 retry/escalate 게이트와 충돌 없음(가드는 line 24 trap 직후, 기존 게이트는 line 50 이후).
4. **8팀 모두 영향**: 모든 팀의 봇이 새 가드를 따르려면 본 보고서 머지 후 다음 dispatch 시점부터 자동 적용. DIRECT-WORKFLOW.md 변경분이 봇 프롬프트에 반영되는지 한 번 dispatch로 확인 필요.

## 셀프 QC 8항목

1. ✅ affected_files 명시 5개 모두 수정 (dispatch.py, finish-task.sh, qc_verify.py, DIRECT-WORKFLOW.md, cancel-task.sh 신규)
2. ✅ 다른 팀 디렉토리 미수정
3. ✅ 보안 키/토큰 하드코딩 없음 (CHAT_ID/ANU_KEY/봇 키는 env 또는 인자로 처리)
4. ✅ Edit 직후 grep 검증 (각 파일 수정 후 commit 단계에서 grep 실행, 모두 1건 이상 매칭)
5. ✅ L1 스모크테스트 (CLI 실행 검증 4시나리오 PASS)
6. ✅ 테스트 회귀 방지 (기존 finish-task.sh 게이트 흐름은 그대로, 가드만 trap 직후 추가)
7. ✅ SCQA 프레임워크 준수
8. ✅ Micro-commit 5건 (commit 메시지 모두 `[task-2352] 쿠쿨칸: ...`)

## 참고
- 사례 task: `task-2349` (STOP 마커 박은 그 파일)
- 사례 cron: `AB4B6C30` (취소 신호 cron, 무시당함)
- 선행 인프라 task: `task-2348` (finish-task.sh 루프 fix), `task-2350` (BG cleanup)

## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회


## 세션 통계
- 총 도구 호출: 0회

