# task-2775+1 보고서 — PR #255 same-branch 보정: N 누계를 epoch-scope window 누계로 보정

상태: `TASK2775_R1_EPOCH_SCOPE_MICROFIX_DONE_ACTIVE_FALSE`
담당: dev2-team (오딘/토르/헤임달) · PR #255 / branch `task/task-2775-dev2`
작성: 2026-06-26

---

## S (Situation)
ANU 독립검증(`task2775_anu_independent_verification_n_window_scope_260626.md`)이
`TASK2775_N_WINDOW_SCOPE_GAP_ACTIVE_FALSE` 결함을 보고했다.
`_count_active_window_pickups()`가 이름과 달리 `p0b_activation_epoch` 이후 필터 없이
ledger의 `outcome==LIVE_PROCESSED` **전체**를 카운트 → 첫 limited 창은 우연히 안전하나
**재가동(두 번째 창부터)은 이전 창 entry 때문에 즉시 false-OFF** 되는 구조.

## C (Complication)
PR #255 방향·범위는 맞으나 N window scope가 ledger 전체 누계라 재가동 안전성이 깨진다.
- expected_files **2개만** 허용(`dispatch/anu_pickup_driver.py`, `tests/regression/test_limited_activation_bounds_2775.py`).
- PR #255 **같은 브랜치 위 commit 추가**(새 PR 금지). merge/activation/real fire/systemd/flag 생성 금지.
- ACTIVE=false · systemd disabled · flags OFF 유지.

## Q (Question)
N=3 자동 OFF 기준을 **ledger 전체 누계 → 현재 limited activation window(epoch 이후) 누계**로
보정하되, 기존 default-OFF/NOOP_DISABLED/kill·N·T 단락 경로를 무손상으로 보존할 수 있는가?

## A (Answer)
epoch-scope 보정 완료. **MERGE_CANDIDATE 가능**(단 머지는 회장 승인 전까지 보류, PR open 유지).

---

## 구현 (expected_files 2개 내, diff 증거)

### 1) `dispatch/anu_pickup_driver.py` (+126 / 일부 -)
- **`_parse_processed_at_to_unix(processed_at)` 신규**: ledger `processed_at`(ISO8601)을
  timezone-aware datetime으로 파싱 → `dt.timestamp()`로 unix epoch(float) 반환.
  - KST `+09:00` 등 tz offset 보존(timestamp()가 offset 반영). `Z`(UTC)→`+00:00` 보정.
  - **naive(tz 미보존)/부재/타입불일치/파싱실패 → None**(활성창 entry 오인 금지, 보수적 제외).
- **`_count_active_window_pickups(root, *, activation_epoch=None, ledger_count_fn=None)` 보정**:
  - `activation_epoch` 주어지면 `outcome==LIVE_PROCESSED` 이면서
    `processed_at_unix >= activation_epoch` 인 entry**만** 카운트.
  - `processed_at` 부재/파싱실패/naive → 제외.
  - **`activation_epoch is None` → 0 반환**(전체 ledger 누계 금지 — 재가동 즉시 false-OFF 차단).
  - 파일 부재/읽기실패 → 0, 개별 라인 파싱실패 → 그 라인만 skip(기존 robustness 유지).
- **`limited_activation_bound_gate` 재구조화** (확인 순서 kill → [epoch 부재 정책] → N → T):
  - epoch을 kill 이후 1회 읽어 N/T 공통 사용.
  - N 체크가 `_count_active_window_pickups(root, activation_epoch=epoch, ...)`로 epoch-scope화.
  - **epoch 부재 정책(req #5)**: `is_activated_fn`이 **명시 주입되어 active=True**일 때만
    fail-closed(자동 disabled write + `NOOP_ACTIVE_EPOCH_MISSING`). 미주입(=production main 기본
    호출) → active=False → epoch 부재면 None 반환(기존 default-OFF/legacy scan_once 라우팅 무손상).
- 신규 상수 `VERDICT_NOOP_ACTIVE_EPOCH_MISSING = "NOOP_ACTIVE_EPOCH_MISSING_AUTO_DISABLED"`,
  `__all__`에 `_parse_processed_at_to_unix`/신규 verdict 추가.

### 2) `tests/regression/test_limited_activation_bounds_2775.py` (+412)
기존 12케이스 중 3개(N 관련)를 epoch-scope로 갱신 + 신규 9케이스 추가 → **총 21 PASS**.

---

## 설계 결정 및 트레이드오프 (req #5 ↔ out-of-scope test-2760)

req #5의 "active인데 epoch 부재 → fail-closed"를 **production main()의 `is_activated`(p0b_driver_enabled)
에 자동 결선하면** out-of-scope 테스트 `test_p0b_event_strategy_wiring_2760.py::MainDoubleGateBranch`
3건(`is_activated=True`를 patch한 hermetic 라우팅 테스트, epoch 미설정)이 깨진다. 해당 파일은
expected_files 밖이라 수정 불가(stop 조건).

**해결**: fail-closed를 `is_activated_fn` **명시 주입 시에만** 발동하도록 정의하고, 정책 자체는
2775 회귀 테스트(주입)로 고정. 근거 — (1) over-count 위험은 epoch-scope(`activation_epoch=None→0`)로
이미 제거됨, (2) ACTIVE=false이므로 production 영향 0, (3) 기존 legacy 라우팅/테스트 무손상.
- **잔여 갭(ANU 판단 요청)**: 향후 **진짜 limited activation 가동 시** main()이 `is_activated` ON +
  epoch 마커 분실 상태를 자동 fail-closed 하려면 test-2760에 epoch 마커 setup 추가(별도 승인 작업)가
  필요하다. 본 +1 범위(2 files)에서는 정책 정의+테스트 고정까지만 수행.

---

## 테스트 결과

### 인-스코프 (21 PASS)
`pytest tests/regression/test_limited_activation_bounds_2775.py` → **21 passed**.
- epoch **이전** LIVE_PROCESSED 3건 → N 카운트 **0** (`test_epoch_scope_before_epoch_not_counted`).
- epoch **이후** LIVE_PROCESSED 3건 → 자동 disabled + `NOOP_N_EXCEEDED` + `result_path==""`(fire 0).
- **재가동 안전**: 이전 창 3건 + 새 epoch 이후 0건 → 즉시 false-OFF 없음(None) (`test_reactivation_safe_old_window_ignored`).
- `processed_at` **부재** 제외 / **파싱실패**(`"not-a-date"`,`""`,int) 제외.
- **timezone-aware** KST `+09:00` ↔ UTC `+00:00` 동일 시각 unix 일치 검증.
- **epoch 부재 active**(주입) → `NOOP_ACTIVE_EPOCH_MISSING` 자동 disabled / epoch 부재 inactive → None.
- **T=120 만료 main() L1 스모크**(`test_main_t_expired_smoke`, CANONICAL_ROOT monkeypatch).

### 회귀 (driver 스위프, 통제된 A/B 비교)
- `test_p0b_event_strategy_wiring_2760.py` → **20 passed**(보정 전 3 fail → 보정 후 0 fail).
- 통제된 back-to-back A/B(mine vs parent) 3회 반복 → **양쪽 동일 6 failed/9 passed**.
  - 잔여 6건(`test_anu_pickup_p0b_audit_decision_check.py` 5 + `test_sealed_key_and_launcher_wiring_2729p13.py::test_07` 1)은
    **real `/home/jay/workspace` 라이브 상태(CLOSEOUT_DONE/PICKUP_SKIP 등)를 읽는 비-hermetic 테스트**로,
    동시 가동 봇이 상태를 변경해 flaky. **parent(de96e52e)에서도 동일 실패 = 본 +1 변경과 무관**(net new 0).
- `py_compile` 양 파일 **PASS**.

## L1 스모크테스트 결과 (필수 기록)
- **서버 재시작**: 해당없음(driver 모듈 — 서버/HTTP 아님).
- **실동작 확인(실제 main() 경로)**: 격리 tmp root에 epoch=now-121분 설정 후 `drv.main([])` 실행:
  - return code **0**
  - stderr: `limited-activation bound gate NOOP (fire 0, governor 미호출): NOOP_T_EXPIRED_AUTO_DISABLED`
  - `p0b_driver_enabled` 첫줄 == **`disabled`**(T 만료 자동 disabled write 검증)
  - real workspace **무접촉**(tmp 격리), fire 0(launcher/callback 생성 이전 단락)
- **스크린샷**: 해당없음(백엔드 모듈).

---

## ACTIVE=false / 안전 증거
- merge **0**(PR #255 open 유지), activation **0**, real fire **0**, canary **0**, recurring **0**.
- `systemctl enable·start` **0**, activation flag 생성 **0**, real workspace 상태 파일 변경 **0**.
- expected_files 밖 수정 **0**(diff = 2 files only). governor/runner/launcher/owner-proof/systemd/`.github`/finish-task 무접촉.
- `git diff --name-only de96e52e..HEAD` = `dispatch/anu_pickup_driver.py`, `tests/regression/test_limited_activation_bounds_2775.py`.

## PR / SHA
- PR **#255 유지**(새 PR 생성 안 함), base = `main`, head branch = `task/task-2775-dev2`.
- 보정 전 head(=+1 base): `de96e52e34c5b99c307c1c585be52ba29b339fe6`
- 보정 후 head(new): `29ec5819db256e76b874917829b98d80364808ca`
- +1 commits: `2786ae72`(epoch-scope 보정), `29ec5819`(epoch 부재 fail-closed 결선 보존).

## 모델 사용 기록
- 토르(백엔드, driver 구현): **sonnet**. 헤임달(테스터, 회귀 테스트): **sonnet**.
- 팀장(오딘, Opus): 설계/회귀 진단/통제 A·B 비교/epoch 부재 결선 1줄 보정/통합검증. (haiku 미사용.)

## MERGE_CANDIDATE 판정
**가능**. 단 회장 명시 승인 전까지 PR #255 open 유지. PASS 나와도 머지 금지 →
ANU 독립검증 → fresh-head Gemini → CI 확인 후 회장 머지 판단.

## 발견 이슈 및 해결
1. **req #5 fail-closed가 out-of-scope test-2760 3건을 깨뜨림** → fail-closed를 `is_activated_fn`
   명시 주입 시에만 발동하도록 보정(위 "설계 결정" 참조). test-2760 20 PASS 복구. 잔여 갭은 ANU에 명시.
2. **driver 스위프 6 flaky fail** → 통제된 A/B로 parent와 동일 = 본 변경 무관(비-hermetic 라이브 상태 의존)임을 확인, 보고에 기록.
