# D-2 fire wiring 설계문서 (task-2774+8, guard-native)

> ★ 구현 PR 설계 기록. **이 PR 은 real fire 0(결선만)**. 실 발사는 PR merge 후 별도 D-2 승인.
> base = e4904e2f10c6f1e38610f85a8b3595b252362eec. 정본 sha256(앞16) = c6ba97c9373a7b35.
> ★ correction(task-2774+9): `_parse_cokacdir_cron_stdout` 의 cron 등록 판정을 fail-open→fail-closed 로 교체 — id 실존+비어있지 않을 때만 `cron_registered=True`(미등록 시 `FIRE_BLOCKED_NO_CRON_ID`/`FIRE_REGISTRATION_FAILED`). 테스트 21→31.

## 1. 닫은 결선 공백 (D-2-A audit 확정)
- `attempt_owner_fire` approved one-shot 경로는 골격만 — production 실발사 `fire_fn` default 결선 0
  (fire_fn 미주입 → FIRE_ERROR). 코드 변경 없이 real fire 불가.
- `ONESHOT_TARGET` 고정 하드코딩 — default 자동발사 벡터 잠재.

## 2. 변경 범위 (expected_files — 정확히 이 범위)
- `dispatch/anu_result_pickup_runner.py` — **순수 추가(+256/-0)**. 기존 함수 무수정.
- `tests/regression/test_d2_fire_wiring_2774p8.py` — 신규 fail-closed 테스트 12 클래스(21 케이스).
- `memory/plans/spawn_safety_governor/d2_fire_wiring_design_2774p8.md` — 본 문서.

★ owner_proof_pickup_gate / attempt_owner_fire / governor / G2 / G3 / D-1 PASS 경로 /
  build_callback_launch_fn(anu_callback_launch_audit.py) **무수정**(diff 0 deletions 으로 입증).

## 3. 결선 구조 (신규 — 순수 추가)
### 3.1 `run_approved_oneshot_fire(...)` — 승인된 1-shot 의 **유일한** 진입점
일반 runner/pickup/driver 경로는 본 함수를 자동으로 타지 않는다(production call site 0).
순서 fail-closed:
1. **ACTIVE gate** = 함수인자 `flag_reader` 로만 판정(전역 flag 파일 생성/읽기 0·
   p0b_driver_enabled activation 과 별개). 부재/False/예외 → `NOOP_DISABLED`(fire_fn 미호출).
2. 실발사 `fire_fn` 명시 주입 부재 → `FIRE_BLOCKED_NO_FIRE_FN`(신규 fail-closed enum).
   ★ default 실발사 fire_fn 0 — real fire_fn 은 호출자가 명시 생성·주입해야 함.
3. 승인 `target` 명시 주입 부재(None) → `FIRE_BLOCKED_NO_APPROVAL`(하드코딩 자동발사 차단).
4. 위 통과 시에만 `attempt_owner_fire(approved_one_shot=True, fire_fn=<주입>, target=<주입>)`
   에 위임. target 3조건 정확 일치·gate READY 재확인·dedupe 1회 소진은 attempt_owner_fire 가
   **무수정 재사용**.

### 3.2 `build_oneshot_cokacdir_fire_fn(...)` — production real-fire fire_fn factory
- 반환 fire_fn 은 attempt_owner_fire 가 구성한 real_argv(실 ANU key 포함·--cron --once)를
  받아 정확히 1회 cokacdir subprocess 실행 → outcome dict 반환.
- ★ **recurring 금지**: argv 에 `--cron` + `--once` 동시 존재만 허용(없으면 실행 0·fail-closed).
- ★ **raw key hygiene**: argv/stdout/예외를 log/persist/return 하지 않음 — 고정 라벨만.
- ★ `runner` 인자: 테스트가 실 subprocess 대신 mock 주입(실 cokacdir 호출 0 보장).
- 본 PR 은 본 factory 의 반환 fire_fn 을 production 에서 호출하지 않음(결선만).

## 4. 안전 불변식 (쐐기 1–9 매핑)
1. default 실발사 fire_fn 0 (attempt_owner_fire·wrapper 양쪽) → 미주입 시 FIRE_BLOCKED_NO_FIRE_FN.
2. 실발사 fire_fn 은 run_approved_oneshot_fire 에서만 명시 주입(일반 경로 자동발사 0).
3. target 3조건(task_id + abs path + head_sha==canonical HEAD 40자 정확) — attempt_owner_fire
   재사용. 미일치 → fail-closed(FIRE_BLOCKED_NO_APPROVAL / head 는 RESULT_NOT_PICKUP_ELIGIBLE).
   prefix/short SHA 완화 0.
4. approved_one_shot 1회 소진 — (task_id, head_sha) dedupe marker. 2회차 DEDUPED·fire 0.
5. target 은 호출 인자(파일 manifest 아님) — audit/log raw key/민감정보 0. dedupe marker 로 재사용 차단.
6. --once 외 recurring 금지 — factory 가 --cron+--once 강제.
7. ACTIVE gate = 함수인자 flag_reader·전역 파일 0·D-2 호출 범위 한정. p0b_driver_enabled 별개.
8. raw key argv/log/audit 노출 0 — sealed loader 런타임 로드(attempt_owner_fire 소유), 예외 고정 라벨,
   redacted_argv 별도. (changed 파일에 key 평문 부재 프로그램 검증 PASS)
9. canonical p0b_inbox backlog 13건 처리 대상 0 — target 외 전부 FIRE_BLOCKED_NO_APPROVAL.

## 5. 신규 enum (attempt_owner_fire 9 enum 계약 불변)
- `GATE_NOOP_DISABLED = "NOOP_DISABLED"`
- `GATE_FIRE_BLOCKED_NO_FIRE_FN = "FIRE_BLOCKED_NO_FIRE_FN"`
- `GATE_ONESHOT_WRAPPER_STATUSES` (별도 tuple — GATE_ALL_TERMINAL_STATUSES 확장 0).

## 6. 테스트 (mock fire_fn / mock flag_reader / mock runner만 · 실 cokacdir 호출 0)
21 케이스(회장 11종 전수 + factory 입증 3 + mismatch 세분). subprocess.run/Popen
assert_not_called 로 real fire 0 입증. 신규 21 PASS · 회귀 무손상.

## 7. D-2 실행 절차 (PR merge 후·별도 승인 — 본 PR 범위 밖)
`fire_fn = build_oneshot_cokacdir_fire_fn()` →
`run_approved_oneshot_fire(gate_result, flag_reader=<ACTIVE gate ON>, fire_fn=fire_fn, target=<승인 1건>)`
→ owner-proof PASS → approved_one_shot → fire_fn 1회 → callback 1회 관측 → teardown + ACTIVE=false 복귀.
