# task-2535 — schedule_id freshness 검증 (신호등 sync fix C)

- 작업 유형: schedule_id 신선도 검증 로직 신설 (Lv.3)
- Track: traffic_light_fix_c / schedule_id_freshness_validation
- 회장 §결정: 2026-05-10 — task-2532 audit fix C 옵션
- dependency: task-2526.merged ✅ + task-2528.merged ✅
- parallel_policy: parallel_safe (A/B/D 영역 무겹침)
- 일시: 2026-05-10 (KST)

## 본질 fix 결과

### 1. `utils/schedule_id_freshness.py` (NEW)
- `SCHEDULE_FRESHNESS_THRESHOLD_MIN = 60` (회장 §명시 60분)
- `CHAIRMAN_CHAT_ID = 6937032012` (회장 chat 격리 상수)
- `classify_freshness(schedule_id) -> Literal["FRESH", "STALE", "MISSING"]`
- `is_schedule_id_fresh(schedule_id, now) -> tuple[bool, str]`
- `schedule_id_age_seconds(...)` (lifecycle evidence detail용)
- `_read_last_chairman_record()` — schedule_history/{ID}.log JSONL 파싱 시
  chat_id != CHAIRMAN_CHAT_ID 인 record 는 silently skip (cokacdir docs § isolation 정합)
- 절대 노출 차단: token / response 본문 / raw key — ts / chat_id 만 dataclass 로 반환

### 2. `utils/lifecycle_reconciliation_manager.py` (MODIFY)
- `StuckReason.STALE_SCHEDULE_ID` enum 추가 (기존 16종 → 17종)
- `LifecycleEvidence`에 3 필드 추가 (모두 `Optional` 기본값):
  - `schedule_id: Optional[str]`
  - `schedule_id_freshness: Optional[str]`  (FRESH/STALE/MISSING/None)
  - `schedule_id_age_seconds: Optional[float]`
- `detect_stuck_cases()` 에 STALE_SCHEDULE_ID 검출 블록 추가:
  - 조건: `schedule_id_freshness=="STALE"` AND `timer_status=="running"` AND `pr_state!="MERGED"`
  - MISSING/FRESH/None 은 박제 보류 (in-progress 오탐 방지 / 매핑 부재만으로 stuck X)
  - PR 이미 MERGED → 별도 stuck reason (TIMER_RUNNING_BUT_PR_MERGED 등) 가 처리

### 3. `tests/regression/test_schedule_id_freshness_2535.py` (NEW — 회귀 6건 + 보강 = 21 PASS)
1. **fresh** (5분 전 응답)        → FRESH ✅
2. **stale** (90분 전 응답)       → STALE ✅
3. **missing** (history 부재)     → MISSING ✅
4. **lifecycle stuck STALE_SCHEDULE_ID 분류** ✅
5. **chat=6937032012 격리** (다른 chat record 는 freshness 판정 미참여) ✅
6. **token raw 0** (validator 출력 / stuck detail / 모듈 소스에 ghs_/ghp_/github_pat_ 0건) ✅

## 회귀 결과 (worktree task/task-2535-dev3)

```
tests/regression/test_schedule_id_freshness_2535.py — 21 PASS
tests/regression/ 전체                                  — 535 PASS
의존성 직접 회귀:
  test_cron_session_safety_guard_2526.py    — 26 PASS
  test_worktree_timer_reconcile_2528.py     — 11 PASS
  test_auto_finalize_chain_default_2529.py  — 15 PASS
영향 받는 영역(lifecycle/reconcile/stuck) 회귀                — 514 → 535 (신규 21만 증가, 기존 0건 영향)
```

## 회장 §명시 절대 금지 — 모두 0건 위반

- ❌ task-2533 / task-2534 / task-2536 영역 직접 수정 → **0건** (utils/schedule_id_freshness.py 와 utils/lifecycle_reconciliation_manager.py 두 파일 + 신규 회귀만)
- ❌ task-2526~2532 코드 영역 수정 → **0건** (lifecycle_reconciliation_manager 는 task-2528 영역이지만 enum / dataclass / detect_stuck_cases 신규 분기만 추가, 기존 8 + 4 + 4 stuck 로직 변경 0)
- ❌ owner_pat / 회장 직접 머지 / admin override / force / rebase / manual `.done` → **0건**
- ❌ task.md commit / `--session` → **0건**
- ❌ Critical 7종 외 회장 보고 → **0건**

## affected_files (실제 변경)

- `utils/schedule_id_freshness.py` — NEW (228 lines)
- `utils/lifecycle_reconciliation_manager.py` — MODIFY
  (StuckReason enum +1 / LifecycleEvidence +3 fields / detect_stuck_cases +1 block / import +1)
- `tests/regression/test_schedule_id_freshness_2535.py` — NEW (21 tests)
- `memory/reports/task-2535.md` — NEW (본 보고서)
- `memory/events/task-2535.done` — NEW (자동 finalize chain 박제용)

## 보고

```
task-2535 SCHEDULE_ID_FRESHNESS_PASS — freshness validator + STALE_SCHEDULE_ID stuck 분류, 회귀 21/21 PASS (전체 535/535), Critical 7종 0건.
```
