# task-2534 — data_loader 자동머지 시그널 병합 (신호등 sync fix B) 완료 보고

- **일시**: 2026-05-10
- **봇**: dev5-team 마르둑 (Marduk) 단독
- **작업 레벨**: Lv.3
- **작업 유형**: data_loader 신규 helper + `_compute_member_status` 보강 + 회귀 8건
- **branch**: `task/task-2534-dev5`
- **base**: `abf0c297` (origin/main top — task-2529 merged)
- **회장 §결정**: 2026-05-10 — task-2532 audit fix B (data_loader 보강 우선)

## 1. 본 task 본질

task-2532 audit가 발견한 갭:

> data_loader가 자동머지 시그널 (cron schedule_history / events/*.merge-queue.json /
> orchestration-audit) **미참조** → 활성 봇이 dashboard에서 idle로 표시.

회장 발견 사례 3건 박제:

| Case | cron schedule | mtime | dur | gap |
|---|---|---|---|---|
| task-2528+1 dev1 | `B9792045` | 12:17 | 18m55s | ~19m 100% |
| task-2530 dev7   | `5AFBFD82` | 13:05 | 26m43s | ~27m 100% |
| task-2531 dev4   | `298A007B` | 미발견 | — | 표시=유휴 (정상) |

Fix B = data_loader가 task-timers.json **+** cron schedule_history mtime
**+** events/*.merge-queue.json mtime 을 병합해서 활성/유휴를 판정한다.

## 2. 구현 (expected_files 정합)

| 파일 | 종류 | LOC delta | 핵심 변경 |
|------|------|-----------|----------|
| `dashboard/data_loader.py` | MODIFY | +145 / -10 | 모듈 헬퍼 3종 + DataLoader 캐시/refresh + 6 호출자 일괄 교체 |
| `tests/dashboard/test_data_loader_signal_merge_2534.py` | NEW | +330 | 회귀 8건 (10 test functions) |
| `memory/reports/task-2534.md` | NEW | (본 파일) | 완료 보고 |

`dashboard/helpers.py` 는 spec상 "필요 시 fallback 보강" 이었으나, 모듈 헬퍼가 자족적이라
수정 불필요 — affected_files 목록에서 자연 제외.

## 3. 핵심 변경 요지

### 3.1 모듈 레벨 헬퍼 3종 (data_loader.py)

```python
# 새 상수
DASHBOARD_CHAT_ID = 6937032012
SIGNAL_FRESHNESS_SECONDS = HEARTBEAT_FRESH_SECONDS  # 600초 (10분) — heartbeat와 정합
SCHEDULE_HISTORY_DIR_DEFAULT = Path("/home/jay/.cokacdir/schedule_history")
_TASK_ID_PATTERN = re.compile(r"task-\d+(?:\+\d+)?(?:-[A-Z])?")

# (1) cron 시그널 수집 — chat=6937032012 격리
def _collect_cron_schedule_signals(schedule_history_dir, chat_id, freshness_seconds, now=None
                                  ) -> Dict[str, float]:
    """schedule_history JSONL에서 task_id별 최신 cron 시그널 mtime 수집.

    회장 §명시 — chat 격리: 다른 chat의 라인은 결과에 포함 X.
    token raw 노출 0: prompt/response/schedule_id 자체는 결과 dict에 절대 미포함.
    """

# (2) merge-queue 시그널 수집
def _collect_merge_queue_signals(events_dir) -> Dict[str, float]:
    """events/*.merge-queue.json mtime을 task_id별로 수집."""

# (3) 통합 status 판정
def _compute_member_status(task_id, task, heartbeat_dir, pid_provider,
                           cron_signals=None, merge_queue_signals=None,
                           now=None, freshness_seconds=SIGNAL_FRESHNESS_SECONDS) -> str:
    """priority: cron 발사 > merge-queue HEAD > timers running > timers completed → idle."""
```

### 3.2 DataLoader 보강

- `__init__`: `schedule_history_dir`, `dashboard_chat_id`, `_cron_signals`, `_merge_queue_signals` 4 필드 신설
- `_refresh_signal_cache()`: cokacdir 외부 입력 예외 흡수, reload TTL과 동일 사이클로 갱신
- `_compute_task_liveness(task_id, task)`: 캐시 시그널 + `_compute_member_status` wrapping
- `reload_all()`: `_refresh_signal_cache()` 호출 1줄 삽입
- `evaluate_task_liveness` 6 직접 호출자 → `self._compute_task_liveness` 일괄 교체
  - `get_team_stats` (running / escalated 분기 2건)
  - `get_running_tasks_by_team`
  - `_enrich_bot_activity`
  - `get_system_status` (running / escalated 분기 2건)

### 3.3 회귀 8건 (tests/dashboard/test_data_loader_signal_merge_2534.py)

| # | 검증 | 결과 |
|---|------|------|
| 1 | timers running + hb fresh + PID alive → working | PASS |
| 2 | cron 발사 후 timers stale → working (cron 시그널 우선) | PASS |
| 3 | merge-queue HEAD → working | PASS |
| 4 | 모든 시그널 부재 → idle | PASS |
| 5 | priority cron > mq > heartbeat (4 sub-case) | PASS |
| 6 | chat=6937032012 격리 (다른 chat 무시) | PASS |
| 7 | token raw 0 (prompt/schedule_id 절대 누수 X) | PASS |
| 8 | task-2528+1 / task-2530 / task-2531 회장 발견 fixture (3 sub-case) | PASS |

## 4. 회귀 테스트 결과

```
tests/dashboard/test_data_loader_signal_merge_2534.py — 10 passed
tests/dashboard/test_signal_pid_aggregation.py        — 13 passed (회귀 무파괴)
tests/dashboard/                                      — 23 passed
tests/dashboard/ + tests/regression/                  — 537 passed, 0 failed
```

`evaluate_task_liveness` 시그니처는 보존되어 기존 task-2422 회귀(13건) 전 PASS.

## 5. 회장 §명시 절대 금지 — 0건 위반

- ❌ task-2533 / task-2535 / task-2536 영역: 미수정 ✅
- ❌ task-2526~2532 코드 영역: 미수정 ✅
- ❌ task-timers.json 직접 변경: read-only로만 사용 ✅
- ❌ owner_pat / 회장 직접 머지 / admin override / force / rebase / manual `.done`: 0건 ✅
- ❌ task.md commit / `--session`: 0건 ✅
- ❌ Critical 7종 외 회장 보고: 0건 ✅
- ❌ 다른 dashboard 컴포넌트 수정: 0건 (data_loader.py 단일 + 신규 테스트) ✅

## 6. token raw 0 검증

- `_collect_cron_schedule_signals` 반환 dict는 `{task_id: float mtime}` 전용
- 회귀 7번이 `json.dumps(signals)` 직렬화 후 `ABC123` / `FF00FF00` / `비밀 토큰` 0건 확인
- prompt/response/schedule_id raw 값은 호출자 어디에도 노출되지 않음

## 7. 결론

`task-2534 DATA_LOADER_SIGNAL_MERGE_PASS` —
schedule_history+merge-queue 병합, 회귀 10/10 PASS, 영역 한정 100%, Critical 7종 0건.
