# task-2528 — worktree 완료 작업 task-timers.json reconcile lifecycle bug fix (완료 보고서)

- 2026-05-10 / dev1-team 브라흐마 단독 / Lv.3 / lifecycle_reconciliation
- task-2527 audit 권고 #1 (root cause 직접 fix) 한정 — #2(fallback chain) / #3(UI hint) 무수정

## Root cause (task-2527 audit 재인용)

worktree에서 완료된 task가 `memory/task-timers.json` (active + archived 양쪽)에 entry가
누락되면 dashboard `helpers.py:450-454` mtime fallback이 발동한다. 5/9 14:32에 PR #70
머지점검 + task-2520 후속 working-tree 재구성으로 reports/tasks/workspace 300+ 파일이
sub-second 클러스터로 mtime 균등화 → 8 task(2514/2515/2516/2516+1/2517/2518-self-host/
2519/2519-self) 모두 동일 timestamp 표시 회귀.

## Fix 사양

### 1. `utils/lifecycle_reconciliation_manager.py` (MODIFY)

`_reconcile_worktree_completion_to_timers()` 신규 helper:

- Trigger: `has_done` OR `has_merge_done` OR `pr_state==MERGED` 중 하나라도 충족
- Skip: `task-timers.json` (active) OR `task-timers-archived.json` 어느 쪽이든 entry 존재 시
- Reconstruct 우선순위:
  1. `memory/events/<task>.done` JSON (`task_id`, `team_id`, `end_time`, `duration_seconds`, `qc_result`)
  2. `memory/events/<task>.merge-done` JSON
  3. `.done` 파일 mtime
  4. `.merge-done` 파일 mtime
- Insert into active `task-timers.json` (idempotent — 이미 있으면 no-op)
- 보조 필드: `ended_by`, `reconcile_run_id`, `reconciled_from`, `reconciled_at`, `note`,
  `merge_commit`, `pr_number`

Wired into `reconcile()` flow: state와 무관하게 항상 마지막에 호출 (FINALIZED-but-no-timer
케이스도 잡기 위함). 결과는 `LifecycleReport.backfill_metadata.worktree_timer_reconcile`
필드에 audit-friendly metadata로 기록.

추가 상수: `_TIMERS_ARCHIVE_FILE_NAME = "memory/task-timers-archived.json"`.

### 2. `tests/regression/test_worktree_timer_reconcile_2528.py` (NEW, 회귀 7+4)

| # | 회귀 명 | 클래스 | 결과 |
|---|---|---|---|
| 1 | happy path | TestHappyPath | PASS |
| 2 | 8 task 부재 fixture (5/9 14:32 사고 박제) | TestEightStuckTaskFixture | PASS |
| 3 | idempotent (2회 실행 → 1 entry) | TestIdempotent | PASS |
| 4 | archived 충돌 차단 | TestArchiveCollision | PASS |
| 5 | mtime fallback 미발동 | TestMtimeFallbackRegression | PASS |
| 6 | chat=6937032012 격리 (다른 chat 미혼입) | TestChatIsolation | PASS |
| 7 | token raw prefix 0 (`ghs_`/`ghp_`/`github_pat_`) | TestTokenRawZero | PASS |
| + | dry-run no-insert | TestHappyPath | PASS |
| + | no completion evidence skip | TestMtimeFallbackRegression | PASS |
| + | reconcile() integrated FINALIZED-no-timer | TestReconcileIntegration | PASS |
| + | _build_reconciled_timer_entry no-token deep walk | TestTokenRawZero | PASS |

총 11/11 PASS, 0.15s. 기존 `test_lifecycle_reconciliation_manager_2518.py` 22/22 유지.

## 회장 §명시 절대 금지 준수

- ❌ `dashboard/**` 수정 0건 (helpers.py:450-454 / ArchiveView.js:435 무수정 — 권고 #2/#3 별도 task)
- ❌ `scripts/safe_cron_dispatch.py` / `utils/cron_targeting_audit.py` 수정 0건 (task-2526 영역)
- ❌ admin override / owner_pat fallback / force push / rebase / manual `.done` 0건
- ❌ task.md commit 포함 0건
- ❌ Critical 7종 외 회장 보고 0건
- ❌ token raw value 노출 0건 (회귀 #7로 박제)

## 정합성

- Merge Topology Gate metadata (expected_files 4개) 자기참조 PASS:
  - `utils/lifecycle_reconciliation_manager.py` (MODIFY)
  - `tests/regression/test_worktree_timer_reconcile_2528.py` (NEW)
  - `memory/reports/task-2528.md` (NEW)
  - `memory/events/task-2528.done` (NEW)
- dependency: task-2527.acked ✅ / task-2518.merged (#70) ✅ / task-2526.merged (#75) ✅
- parallel_policy=serial_only / cherry_pick_allowed=false 준수
- chat=6937032012 격리: timer entry 직렬화에 chat_id 흔적 0 (회귀 #6 박제)

## 한 줄 보고

```
task-2528 LIFECYCLE_TIMER_RECONCILE_FIX_PASS — 8 task fixture reconcile, idempotent, 회귀 7/7 PASS, Critical 7종 0건.
```
