---
qc_verdict: WARN
---

# task-2543 — task-timers.json 5/8 5/9 reconcile + helpers mtime fallback off

## QC Verdict
WARN

### WARN/FAIL 검사 항목 (정직성 박제)
- `scope_check` — WARN: 본 task는 시스템 작업으로 PROJECT_PATH 미지정. dashboard/helpers.py + memory/task-timers.json + scripts/ + tests/ + memory/reports/ 변경은 모두 affected_files에 명시됨 (effective scope 일치). worktree-기반 시스템 작업의 통상 WARN 패턴.
- `claude_md_check` — WARN: 본 task가 CLAUDE.md를 수정하지 않음. CLAUDE.md 갱신 대상 변경(workflow/규칙 변경) 없음으로 의도적 미수정.

- **팀**: dev2-team
- **레벨**: Lv.3 (data reconcile + helpers.py 보강 + 회귀)
- **우선순위**: ★★ blocking
- **일시**: 2026-05-10
- **회장 결정**: 2026-05-10 — 기록탭 5/8/5/9 사라짐 + 신호등 일부 팀 idle_base 부재 동시 회복

## SCQA

### S — Situation
**S**: 회장 대시보드 점검(2026-05-10) 결과
- task-timers.json end_time 분포: 5/2(16) / 5/3(14) / 5/6(3) / **5/8(0) / 5/9(0)** / 5/10(1)
- 신호등 일부 팀 idle_base 부재 → SystemView 라벨 미표시
- 기록탭 5/8, 5/9 작업 0건 표시 (실제 작업은 있었음)

### C — Complication
**C**: 두 증상의 단일 source-of-truth가 task-timers.json + done event JSONL이고, helpers.py mtime fallback이 working-tree mtime 일괄 갱신 사고에 노출되어 있음
- root cause #1: `dashboard/helpers.py:454` 의 mtime fallback이 working-tree 상태에 노출됨
- root cause #2: 5/9 14:32 working-tree mtime 일괄 갱신 사고로 5/8/5/9 entry mtime이 5/9 14:32로 덮임
- root cause #3: 28건의 task가 task-timers.json entry 자체 부재
- task-2527 오전 정정(mtime fallback fix)으로도 데이터 복구 불가

### Q — Question
**Q**: 어떻게 idempotent하게 5/8/5/9 entries를 복구하면서 helpers의 mtime fallback 노출을 차단할 것인가?

### A — Answer
1. **`scripts/reconcile_task_timers_2543.py` (NEW)** — `memory/events/*.done` JSON에서 `completed_at`/`end_time`/`merged_at`을 읽어 누락된 entry만 추가 (idempotent, 기존 entry 덮어쓰기 X, chat_id 격리)
2. **`dashboard/helpers.py` (MODIFY)** — `_resolve_end_time_priority()` 신규 helper 도입. 우선순위: done event JSON → merge-done → done.acked → task-timers → (mtime은 `fallback_to_mtime=True`일 때만, 본 task에서는 강제 False)
3. **회귀 7건** — done event 우선/mtime off/chat 격리/idempotent/5-8 박제/5-9 박제/token raw 0

## 결과

### Reconcile 결과 (1차)
```json
{
  "backfilled": 26,
  "skipped_existing": 0,
  "skipped_other_chat": 0,
  "skipped_no_timestamp": 10,
  "tasks_added": ["task-2485", "task-2485+1", "task-2486", "task-2487+1", "task-2488",
                  "task-2493", "task-2494", "task-2495", "task-2496", "task-2499",
                  "task-2503", "task-2503+1", "task-2507", "task-2509", "task-2509+1",
                  "task-2509+2", "task-2510", "task-2511", "task-2512", "task-2513",
                  "task-2514", "task-2515", "task-2516+1", "task-2517", "task-2521", "task-2523.9"]
}
```

### Idempotent 검증 (2차 실행)
```json
{ "backfilled": 0, "skipped_existing": 26, ... }
```
→ 두 번째 실행에서 `backfilled=0`, `skipped_existing=26`. 완전 idempotent ✓

### task-timers.json 분포 회복
| 날짜 | 이전 | 이후 |
|------|------|------|
| 2026-05-02 | 16 | 16 |
| 2026-05-03 | 14 | 14 |
| 2026-05-06 | 3 | 3 |
| 2026-05-08 | **0** | **17** ✓ |
| 2026-05-09 | **0** | **9** ✓ |
| 2026-05-10 | 1 | 1 |

### 신호등 idle_base 회복 (L1 직접 호출)
```
dev1-team: task-2495 (2026-05-08T08:10:15) — idle 57h
dev2-team: task-2517 (2026-05-09T09:11:17) — idle 32h
dev3-team: task-2511 (2026-05-09T00:57:33) — idle 40h
dev5-team: task-2507 (2026-05-08T13:03:36) — idle 52h
dev6-team: task-2494 (2026-05-08T08:16:03) — idle 57h
dev7-team: task-2499 (2026-05-08T08:10:05) — idle 57h
```
→ 이전: 전 팀 None / 이후: 6개 팀 전부 정상 idle_base 산출 ✓

### 회귀 결과
- `tests/dashboard/test_helpers_done_event_fallback_2543.py`: **7/7 PASS**
  1. `test_done_event_priority_over_timers_end_time` — done event 시각 우선
  2. `test_mtime_fallback_disabled` — fallback_to_mtime=False 시 None
  3. `test_reconcile_skips_other_chat` — 다른 chat silently skip
  4. `test_reconcile_idempotent` — 2회 실행 시 backfilled=0
  5. `test_backfill_2026_05_08` — 5/8 entry 박제
  6. `test_backfill_2026_05_09` — 5/9 entry 박제
  7. `test_reconcile_no_token_leakage` — raw_response/api_key/tokens 미전파
- `tests/dashboard/` 전체 회귀: **35/35 PASS** (기존 28건 + 신규 7건, 회귀 0)

## L1 스모크테스트 결과
- **서버 재시작**: 해당없음 (main dashboard 서버는 머지 후 자동 반영. worktree에서 직접 호출로 검증)
- **API 응답 확인**: localhost:8000/dashboard/index.html → 200 OK + 페이지 타이틀 "조직 대시보드"
- **모듈 직접 호출**:
  - `helpers._resolve_end_time_priority("task-2486", events_dir=/home/jay/workspace/memory/events, ...)` → `2026-05-08T01:57:18+09:00` ✓ (done event 우선순위 동작)
  - `DataLoader._compute_team_idle_base()` × 6개 팀 → 모두 5/8 또는 5/9 task로 정상 산출 ✓
- **스크린샷**: `memory/screenshots/task-2543.png` (Playwright MCP, dashboard/index.html, 머지 전 main 상태 박제)
- **콘솔 에러 0건** 확인 (Playwright `browser_navigate` 결과: `Console: 0 errors, 3 warnings`)
- **Lighthouse 점수**: Chrome DevTools MCP 미동작 → fallback (Playwright만 사용, DIRECT-WORKFLOW Step 4.8 §Fallback 허용 항목)
- **LCP/FCP**: 듀얼 MCP 미가동으로 측정 불가 — 본 task는 백엔드 reconcile 작업이라 UI 성능 영향 없음

## 모델 사용 기록
- 토르(백엔드): sonnet (reconcile 스크립트 + helpers.py 수정)
- 헤임달(테스터): sonnet (회귀 7건 작성)
- 디자인 작업 없음 (해당없음)

## 머지 판단
- **머지 필요**: Yes
- **브랜치**: `task/task-2543-dev2`
- **워크트리 경로**: `/home/jay/workspace/.worktrees/task-2543-dev2`
- **머지 의견**:
  - dashboard 전체 회귀 35/35 PASS, 신규 회귀 7/7 PASS
  - reconcile 스크립트 idempotent 보장 (2차 실행 backfilled=0)
  - helpers.py 변경은 기존 mtime fallback 라인 보존 (다른 호출자 영향 0) + 우선순위 분기 추가만
  - chat_id 격리 + token raw 미노출 박제
  - 충돌 가능성 낮음 (memory/task-timers.json은 직렬 dispatch로 보호됨)

## 발견 이슈 및 해결
1. **worktree events_dir divergence (토르 발견)**:
   - worktree에는 `memory/events/*.done` 파일이 git tracked가 아니라 비어 있음
   - reconcile 스크립트는 메인 워크스페이스의 events_dir(`/home/jay/workspace/memory/events`)를 명시적으로 지정해 실행
   - 해결: 스크립트의 `--events-dir` 인자로 메인 events 디렉토리 지정. 이는 본 task의 일회성 reconcile 한정이며, 추후 운영 환경(main에 머지된 후)에서는 default events_dir(`memory/events`)로 정상 동작
2. **헤임달 회귀에서 `from dashboard import helpers` resolution**:
   - tests/conftest.py가 `WORKSPACE_ROOT` 환경변수로 sys.path를 결정 (default: `/home/jay/workspace`)
   - worktree 내 pytest 실행 시 `WORKSPACE_ROOT=/home/jay/workspace/.worktrees/task-2543-dev2`로 명시 → 7/7 PASS
   - 이는 worktree 검증 한정 절차이며 머지 후에는 default 동작으로 자연 해소
3. **Pyright `reportMissingImports` 진단**:
   - `dashboard`/`scripts.reconcile_task_timers_2543` resolution 경고는 Pyright config 한정 (pytest는 정상 동작)
   - 본 task 범위 외 (기존 `tests/dashboard/test_*.py` 동일 패턴 사용 중)
4. **미사용 import 정리**:
   - 헤임달이 작성한 테스트 파일에서 `datetime`/`pytest` 미사용 → 정리 commit (9746e5bf)

## 회장 §명시 8항목 자체 점검
- [x] 1. helpers.py:454 mtime fallback **삭제 X** — `_resolve_end_time_priority`로 우선순위 분기 추가만
- [x] 2. task-timers.json 기존 entry 덮어쓰기 X — 누락 entry만 추가 (idempotent)
- [x] 3. chat=6937032012 격리 — `skipped_other_chat` 카운트로 박제
- [x] 4. 다른 task 영역 침범 X — task-2533/2534/2535/2536/2527/2542 영역 미수정
- [x] 5. token / response body / API key 노출 X — 회귀 #7로 박제
- [x] 6. owner_pat / 회장 직접 머지 / admin override / force / rebase / manual `.done` / `--session` / task.md commit 미사용
- [x] 7. "wrapper가 처리한다" 표현 미사용
- [x] 8. effective diff = expected_files (4 files + 보고서/done event는 finish-task.sh가 생성)

## 변경 파일 (effective diff)
- `dashboard/helpers.py` (+55, -4): `_resolve_end_time_priority` 추가 + line 454 mtime fallback 회피
- `memory/task-timers.json` (+286): 26건 reconcile entry 추가
- `scripts/reconcile_task_timers_2543.py` (+207, NEW): idempotent reconcile 스크립트
- `tests/dashboard/test_helpers_done_event_fallback_2543.py` (+169, NEW): 회귀 7건
- `memory/reports/task-2543.md` (NEW): 본 보고서
- `memory/events/task-2543.done` (NEW): finish-task.sh가 생성

## 커밋 이력
- `da227175` — 토르: scripts/reconcile_task_timers_2543.py
- `892c2e72` — 토르: dashboard/helpers.py mtime fallback off
- `3cd3c748` — 토르: memory/task-timers.json (backfilled 26)
- `ebaa6df7` — 헤임달: 회귀 7건
- `9746e5bf` — 헤임달: 미사용 import 정리

## 보고 한 줄 요약
**task-2543 TASK_TIMERS_RECONCILE_PASS — 5/8 17건 + 5/9 9건 backfilled (총 26), helpers.py done_event_priority + mtime fallback off, 회귀 7/7 PASS, 전체 dashboard 35/35 PASS, idempotent 박제 (2차 backfilled=0), 신호등 idle_base 6팀 회복, Critical 7종 0건.**
