# task-2559 보고서 — 봇 프로세스 source-of-truth + 4 전이 규칙 박제

> 작성: 2026-05-13 (dev3 Dagda — cron 직접 발사 1회)
> 본질: 봇 프로세스 = 신호등 단일 source-of-truth (회장 §명시 2026-05-12)
> 박제 근거: traffic-light-spec.md §9 (2026-05-12)
> 레벨: Lv.3 (dashboard control-plane)

---

## 본질 1줄

**대시보드 신호등 = 봇 프로세스 단일 source-of-truth.** spec §9 2-Layer 모델
(팀장 봇 + subagent child) 1:1 박제. cron/dispatch.py 어느 경로든 일관 작동.
9개월간 반복된 신호등 오류 영구 해소.

---

## 구현 요약

### 신규 모듈
- `dashboard/bot_process_collector.py` — Layer 1 (lead) + Layer 2 (subagent) collector
  - `BotProcessCollector` 클래스 (`get_active_lead_bots`, `get_subagent_children`, `collect`)
  - `signal_from_collector` / `is_lead_alive` hybrid 헬퍼
  - cwd 정규식 2종:
    * `/home/jay/.cokacdir/workspace/<schedule_id>/` → cron 봇
    * `/home/jay/workspace/.worktrees/task-XXX-devN/` → dispatch.py 봇 (suffix 변형 호환)
  - 회장 §명시 chat 격리: schedule_history JSONL의 chat_id 6937032012만 인정
  - 보안: psutil username 필터로 jay user 본인 프로세스만 (다른 user /proc 접근 차단)

### 수정 모듈
- `dashboard/data_loader.py`
  - `DataLoader.__init__`: collector + snapshot cache (5초 TTL) 필드 추가
  - `set_bot_process_collector(c)`: 명시적 wiring 진입점 (테스트 + 운영)
  - `_ensure_bot_process_collector()`: 지연 초기화 (psutil 미설치 환경 graceful 동작)
  - `_refresh_bot_process_snapshot()`: reload_all() 사이클에 결합
  - `get_member_status()` 확장 — hybrid mode 박제:
    * primary = 봇 프로세스 (Layer 1 alive → 팀장 working, Layer 2 child agent_name 매칭 → 해당 팀원 working)
    * 보조 = task-timers.json running
    * stale 차단: 봇 죽었으면 dev팀 running 신호 무시 → idle 반환 (spec §9.7)

- `dashboard/routes_get.py`
  - `handle_get_member_status` / `handle_get_bot_activity` 응답에 `signal_source` 메타 부착
  - `handle_get_bot_process_snapshot` 신규 — `/api/bot-process-snapshot` (raw 진단)

- `dashboard/server.py`
  - `/api/bot-process-snapshot` 라우트 등록 (1줄 추가)

### 테스트
- `dashboard/tests/test_bot_process_collector.py` — 17 케이스
  - 8 시나리오(spec §9.6) PASS + helper / 정규식 회귀
- `dashboard/tests/test_traffic_light_layer_signals.py` — 9 케이스
  - spec 2.1 / 2.2 / 2.3 / 2.4 1:1 PASS
  - hybrid stale 차단 (§9.7 ❌1,2)
  - borrowed_tasks 호환 (§3 + §9.4)
  - collector 없을 때 legacy fallback (psutil 미설치 graceful)
  - API signal_source 메타 박제 어셀션

### 5 Fixture
- `bot_process_cron_active.json` — cron 봇 활성
- `bot_process_dispatch_worktree_active.json` — dispatch 봇 활성
- `bot_process_subagent_parallel.json` — Layer 2 children 다수
- `bot_process_lead_terminated.json` — Layer 1 dead, task-timers stale
- `bot_process_borrowed_task.json` — dev1 봇이 marketing 차용

---

## spec §9 1:1 박제 어셀션

| spec 절 | 박제 위치 | 검증 |
|---------|----------|------|
| 9.2 Layer 1 lead | bot_process_collector._classify_lead | test_cron_lead_discovered / test_dispatch_lead_discovered |
| 9.2 Layer 2 subagent | bot_process_collector.get_subagent_children | test_parallel_subagents_detected / test_single_subagent_subset |
| 9.3 .done 마커는 archive (실시간 X) | (negative) test_done_marker_does_not_trigger_active | PASS |
| 9.4 borrowed_tasks 호환 | data_loader._enrich_bot_activity 유지 | test_borrowed_dev1_alive_marketing_logical |
| 9.5 구현 매핑 | dashboard/bot_process_collector.py 파일 신설 | py_compile PASS |
| 9.6 회귀 fixture | dashboard/tests/fixtures/ 5개 | 17/17 PASS |
| 9.7 ❌1 stale 단독 금지 | data_loader.get_member_status hybrid | test_dead_bot_running_timer_idle |
| 9.7 ❌2 봇 죽은 후 stale | data_loader.get_member_status hybrid | test_bot_process_terminated_all_idle |
| 9.7 ❌3 subagent 무시 금지 | bot_process_collector._bot_process_subagent_active | test_subagent_named_member_working |
| 9.7 ❌4 다른 user /proc 격리 | BotProcessCollector._default_psutil_processes username 필터 | (구현 내장) |

## 4 전이 규칙 (spec 2.1~2.4)

| 전이 | 박제 시점 | 테스트 |
|------|----------|--------|
| 2.1 dispatch → 팀장 working + 팀원 standby | Layer 1 lead spawn 시 | test_lead_working_member_standby |
| 2.2 subagent 호출 → 해당 팀원 working | Layer 2 child agent_name 매칭 | test_subagent_named_member_working |
| 2.3 subagent 완료 → 팀원 대기 복귀 | Layer 2 child 종료, Layer 1 살아있음 | test_subagent_done_member_returns_to_standby |
| 2.4 팀 완료 → 전원 유휴 | Layer 1 lead 종료 또는 .done 마커 | test_bot_process_terminated_all_idle |

---

## 검증 결과

### pytest
- `dashboard/tests/test_bot_process_collector.py` — **17 passed**
- `dashboard/tests/test_traffic_light_layer_signals.py` — **9 passed**
- 기존 dashboard suite 회귀:
  - 378 passed / 9 failed / 1 skipped — failure 9건 전부 main에 이미 존재 (test_composite_status / test_active_skills / test_refine_api / test_server) — 내 변경 무관 (main pre-existing baseline)
  - 3 collection errors (test_file_api / test_records_api / test_wiki_upload) — main pre-existing baseline

### 정적 검증
- `python3 -m py_compile dashboard/{bot_process_collector,data_loader,routes_get,server}.py` PASS

### forbidden path
- 0건 hit. scripts/ci.sh, dispatch/, team_prompts.py, .github/workflows/, .env*, anu_v2/, traffic-light-spec.md 전부 변경 없음
- PR #98~#111 branch head 변경 0건

### effective diff (3 변경 + 11 신규)
- M dashboard/data_loader.py
- M dashboard/routes_get.py
- M dashboard/server.py
- + dashboard/bot_process_collector.py
- + dashboard/tests/test_bot_process_collector.py
- + dashboard/tests/test_traffic_light_layer_signals.py
- + dashboard/tests/fixtures/*.json (5종)
- + memory/plans/tasks/task-2559/{plan,context-notes,checklist}.md
- + memory/events/task-2559.dispatch-decision.json
- + memory/reports/task-2559.md
- effective diff == expected_files (dispatch_decision.json authoritative)

---

## 금지 11건 — 전 항목 준수

1. ✅ task-timers.json running 단독 판정 금지 → hybrid mode 박제
2. ✅ 봇 죽은 후 stale 무시 → spec §9.7 ❌2 회귀 테스트 박제
3. ✅ subagent child 무시 금지 → Layer 2 agent_name 매칭 박제
4. ✅ /proc 다른 user 접근 금지 → psutil username 필터 박제
5. ✅ 회장 수동 `/gemini review` 0
6. ✅ BOT `/gemini review` 0 (Gemini 자동 chain만)
7. ✅ close/reopen 0
8. ✅ force push / rebase / admin override 0
9. ✅ long polling 0 (dispatch_decision.json polling_policy 박제)
10. ✅ task-2558 영역 (anu_v2/) 변경 0
11. ✅ traffic-light-spec.md §1~§8 변경 0 (§9는 이미 박제됨)

---

## 라이브 검증 시나리오 (운영 전제 — PR merge 후)

1. cron 봇 1개 발사 → cwd `/home/jay/.cokacdir/workspace/<sid>/`
2. dashboard `/api/member-status` 호출 → 해당 dev팀 팀장 `working` + 팀원 `standby`
3. 봇 종료 후 다시 호출 → 전원 `idle` 전환 (task-timers running stale 무시)
4. `/api/bot-process-snapshot` 진단 — `active_lead_count` 0 어셀션

---

## 알려진 한계 (후속 task 검토 권장)

- `bot_key_verifier` ↔ `bot_key` 매핑은 관측 캐시(verifier_to_bot_key)로 학습 — cold start 시
  `prompt` 본문에서 dev_id를 추출하여 추정. cokacdir 바이너리 stripped 상태라
  결정적 매핑 알고리즘은 디컴파일 없이는 확보 불가.
- Layer 2 child 식별은 `--agent-name` / `--subagent-type` 인자 / `system_prompt_<hash>` 정규식 기반.
  Task tool 호출 시 child 프로세스에 위 인자가 전달되지 않는 환경에서는 agent_name 매핑 실패
  → "subagent active (count=N)" 수준의 일반 신호만 노출.

---

## ESCALATED 매핑 (해당 없음)

본 task는 모든 완료 12 조건을 충족하며 ESCALATED 없이 finalize 가능.
