# task-2774+11 — PR #253 same-PR micro-fix: flag_reader non-callable fail-closed (★마지막 MED 라운드)

상태명: `TASK2774P11_PR253_FLAG_READER_CALLABLE_GUARD_MICROFIX_APPROVED_ACTIVE_FALSE`
담당: dev2 (PR #253 / branch `task/task-2774+8-dev2` 소유 봇)
회장 승인: same-PR micro-fix 1회 (2026-06-25, ChatGPT 합의 + 회장 verbatim).
★ **이번을 PR #253 의 마지막 Gemini MED 대응 micro-fix 라운드로 기록한다.**

## 배경 / 재분류
fresh head `615ed83936e1503c0cb6efcd23af6b736938d8cf` 에 Gemini 신규 MEDIUM 1건.
`run_approved_oneshot_fire`(line 1972~, 검사부 line 2010-2020) 에서:
```python
    _active = False
    if callable(flag_reader):        # ← flag_reader 가 None 아니고 non-callable(예: True)이면 조용히 skip
        try: _active = bool(flag_reader())
        except Exception: _active = False
    if not _active:
        ... status=GATE_NOOP_DISABLED ...
```
→ `flag_reader=True` 같은 **non-callable 객체**가 들어오면 경고/예외 없이 `_active=False` →
`GATE_NOOP_DISABLED` 로 **침묵 비활성화**. 실발사 결선에서 silent misconfiguration → 원인 추적 곤란.

★ 재분류(회장): 이건 위험한 fire 가 나가는 **fail-open 아님**. **silent misconfiguration / fail-fast
diagnostic robustness** 문제. 그래도 D-2 fire wiring PR 목적에 부합 + 수정 비용 작아 same-PR 에서 닫음.

## 승인 범위 (엄수)
- PR #253 **same-PR** micro-fix 1회 (새 PR 금지). branch `task/task-2774+8-dev2` 위 커밋.
- 수정 허용: **`dispatch/anu_result_pickup_runner.py`** + **`tests/regression/test_d2_fire_wiring_2774p8.py`** 만.
- 필요 시 design md correction note **1줄** 허용. expected_files 밖 필요 시 **STOP_REPORT**.

## 구현 요구
1. `run_approved_oneshot_fire`(또는 해당 wrapper 진입점)에서 flag_reader 를 검사한다.
2. `flag_reader is None` → 기존 기본 OFF/`NOOP_DISABLED` 동작 **유지**.
3. `flag_reader` 가 None 이 아니고 **callable 이 아니면** → **조용히 NOOP_DISABLED 로 삼키지 말 것.**
   - 회장 선호: `TypeError` 를 그대로 던지기보다 **가능하면 명시적 fail-closed status 반환**.
   - 새 status 명은 원인을 드러내는 형태 선호: **`GATE_INVALID_FLAG_READER`** 또는
     **`FIRE_BLOCKED_INVALID_FLAG_READER`**. (현재 enum 에 INVALID_FLAG_READER 없음 — 충돌 0 확인됨.)
   - **단 기존 terminal status enum/set 과 충돌 금지** (GATE_TERMINAL_STATUSES / GATE_ONESHOT_*
     / GATE_ALL_TERMINAL_STATUSES 등에 추가가 필요하면 정합성 유지하며 추가).
   - TypeError 든 명시 status 든 **silent NOOP 만 아니면 됨** (회장이 요구한 핵심).
4. 기존 정상 callable flag_reader 경로 **유지**:
   - callable 이 `False` 반환 → 기존처럼 `NOOP_DISABLED`.
   - callable 이 `True` 반환 → 기존 approved/success 경로.
5. raw key 노출 0 / real fire 0 / ACTIVE=false 유지.

## 수정 금지 (회귀 무손상)
- owner-proof / governor / G2 / G3 / D-1 path / `build_callback_launch_fn` 수정 금지.
- `attempt_owner_fire` 기존 동작 수정 금지. target 조건 완화 금지.
- registered fail-closed 로직 완화 금지. **subprocess bytes 처리 로직(+10) 완화 금지.**

## 테스트 보강 (test_d2_fire_wiring_2774p8.py, 순수 추가)
- `flag_reader=True` (non-callable) → 명시 fail-closed status(예 `GATE_INVALID_FLAG_READER`) 또는 TypeError.
- `flag_reader=object()` (non-callable) → 동일.
- `flag_reader=None` → 기존 OFF/`NOOP_DISABLED` 유지.
- `flag_reader=lambda: False` → `NOOP_DISABLED` 유지.
- `flag_reader=lambda: True` → 기존 approved path 유지.
- non-callable flag_reader 에서 **fire_fn 미호출**.
- non-callable flag_reader 에서 **subprocess 미호출**(mock `assert_not_called`).
- raw key 노출 0.
- 기존 D-2 wiring **38 tests 유지**. 핵심 회귀 유지. `py_compile` PASS.

## 검증 (봇 보고에 포함)
- PR head 새 sha 보고. diff 가 expected_files 안인지. raw key scan 0 / real fire 0 / callback 0 /
  ACTIVE=false 유지. 신규 status enum 이 기존 set 과 충돌 0 인지. D-2 테스트 PASS 수(≥45 기대).

## ★ 반복 제한 (LOOP BOUNDARY)
- **이번이 PR #253 의 마지막 Gemini MED micro-fix 라운드.**
- 이후 fresh **HIGH/Critical** → 당연히 STOP.
- 이후 fresh **MED 가 또 나오면 자동으로 같은 방식 micro-fix 반복 금지.** 그 경우 상태명
  `TASK2774P11_PR253_GEMINI_MED_LOOP_BOUNDARY_CHAIR_DECISION_REQUIRED_ACTIVE_FALSE` 로 **회장 판단 요청.**

## 절대 금지
merge / auto-merge / real fire / ACTIVE gate ON / callback 자동발사(아래 ANU callback 예외) /
systemd start/enable / thread 수동 resolve / Gemini comment dismiss / evidence-defer 임의종결 /
production activation 표현 / ENFORCED=true 단정.

## 완료 처리 + ANU callback (필수)
- 완료 시 `memory/events/<task_id>.done` + `memory/reports/<task_id>.md`.
- 봇 idle 전 **ANU callback 필수**(성공/실패/blocked 무관). collector=ANU,
  owner_key=`c119085addb0f8b7`, self-key 금지. callback prompt UTF-8 **≤3900 bytes**
  (`printf '%s' "$P" | wc -c`), envelope 만(task_id·PR#253·new head sha·result_path·report_path·one-line).
- **merge 절대 금지.** PASS 나와도 ANU 가 독립검증 → fresh-head Gemini 재리뷰 → CI 확인 →
  MERGE_CANDIDATE 로만 보고. 회장 명시 승인 전까지 PR #253 open 유지.

## 진행
dev2 는 micro-fix 구현·테스트·로컬검증 후 branch push 로 PR #253 갱신, 보고서 + .done +
ANU callback 으로 마감. (ANU 가 이후 독립검증/Gemini 재리뷰/CI 확인 담당)

## allowed_resources (본 task의 capability)

```yaml
allowed_resources:
  paths:
    - "dispatch/anu_result_pickup_runner.py"
    - "tests/regression/test_d2_fire_wiring_2774p8.py"
    - "memory/plans/spawn_safety_governor/d2_fire_wiring_design_2774p8.md"
    - "memory/reports/task-2774+11.md"
    - "memory/events/task-2774+11.done"
  forbidden_paths:
    - ".github/**"
    - "memory/events/*.cron-*"
    - "scripts/finish-task.sh"
  commands:
    - "pytest"
    - "python3 -m py_compile"
    - "git"
    - "gh"
  merge_policy: "manual"
  ttl_hours: 24
```