# task-2774+10 — PR #253 same-PR micro-fix: subprocess text=True 인코딩 fail-closed

상태명: `TASK2774P10_PR253_SUBPROCESS_TEXT_ENCODING_MICROFIX_APPROVED_ACTIVE_FALSE`
담당: dev2 (PR #253 / branch `task/task-2774+8-dev2` 소유 봇)
회장 승인: A안 same-PR micro-fix 1회 (2026-06-25, ChatGPT 합의 + 회장 verbatim)

## 배경
PR #253 (D-2 fire wiring) fresh head `6dba16a6656b0983eb24e1c892e05212652cf949f` 에 Gemini
재리뷰 결과 신규 MEDIUM 1건 발생. `build_oneshot_cokacdir_fire_fn` 의
`subprocess.run(..., text=True)` 가 시스템 기본 인코딩으로 stdout 디코딩 → UTF-8 아닌
로케일/디코딩 불가 바이트에서 `UnicodeDecodeError` 로 fire_fn 실행 자체가 깨질 수 있음.
`_parse_cokacdir_cron_stdout` 는 이미 bytes + `errors="replace"` fail-closed 디코딩을
갖췄으므로(1847-1848 라인 근처), 디코딩을 parser 에 위임하는 것이 플랫폼 독립적.

## 승인 범위 (엄수)
- 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`** 만.
- 필요 시 `memory/plans/spawn_safety_governor/d2_fire_wiring_design_2774p8.md` correction note **1줄** 허용.
- expected_files 밖 수정 필요해지면 **STOP_REPORT** (임의 확장 금지).

## 구현 요구
1. `build_oneshot_cokacdir_fire_fn` 의 `subprocess.run` 에서 **`text=True` 제거**.
2. stdout/stderr 를 **raw bytes 로 캡처** (`capture_output=True`).
3. stdout 파싱은 `_parse_cokacdir_cron_stdout`(bytes + replace fail-closed parser)에 위임.
4. `UnicodeDecodeError` 가 fire_fn 실행 전체를 깨뜨리지 않게 할 것.
5. 디코딩 불가 바이트는 `errors="replace"` 또는 동등 안전 처리.
6. parsing 실패 → `FIRE_REGISTRATION_FAILED` (fail-closed).
7. id 없음/빈값/공백/None → `FIRE_BLOCKED_NO_CRON_ID` 유지.
8. 정상 id 가 있을 때만 `cron_registered=True` 유지.
9. `--cron --once` 강제 유지. recurring cron 금지 유지.
10. raw key argv/log/audit 노출 0 유지. 실 cokacdir 호출 0 유지.

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

## 테스트 보강 (test_d2_fire_wiring_2774p8.py)
- subprocess stdout 이 bytes 일 때 정상 id 파싱 성공.
- stdout bytes 에 UTF-8 디코딩 불가 바이트가 있어도 `UnicodeDecodeError` 없이 fail-closed.
- 비JSON bytes 응답 → `FIRE_REGISTRATION_FAILED`.
- id 없음/빈값/공백/None → 계속 `registered=False`.
- 정상 id 만 `registered=True`.
- stderr bytes 가 있어도 raw key 노출 없이 처리.
- `subprocess.run`/`Popen` **mock only**, 실 cokacdir 호출 0.
- 기존 D-2 wiring 31 tests 유지. 핵심 회귀 유지. `py_compile` PASS.

## 검증 (봇 보고에 포함)
- PR head 새 sha 보고.
- diff 가 expected_files 안인지.
- raw key scan 0 / real fire 0 / callback 0 / ACTIVE=false 유지.
- (가능 범위) 신규 + 기존 D-2 테스트 PASS 수.

## 절대 금지
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 summary). 상세는 report.md 위임.
- **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+10.md"
    - "memory/events/task-2774+10.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
```