# task-2712 v3 — FAILURE_CALLBACK_BEFORE_EXIT_GUARD (READ_ONLY_SPEC_REVIEW_PHASE · v3)

★ **READ_ONLY_SPEC_REVIEW_PHASE v3** — Codex round 2 OVERALL PASS_WITH_RECOMMENDATIONS + ELIGIBLE_CONTINUE + ANU 자동 루프 자격 6/6 PASS · 회장 verbatim "v2 이후 Codex 지적이 spec/doc/schema/test 보강에 한정되고, 실제 파일 수정 인가가 추가로 필요하지 않으면 ANU가 자동 revision loop를 진행한다" 명시 인가 정합 자동 진행.

- v1: sha `18e400ee...` (★ 보존)
- v2: sha `0f6850e6...` (★ 보존)
- v3 origin: Codex round 2 remaining 4 recommendations 1:1 surgical fix

---

## 0. Header / Authorization Anchor (★ v2 유지)

- **task_id**: `task-2712`
- **chair_authorization_id**: `CHAIR-AUTH-TASK-2712-FAILURE-CALLBACK-BEFORE-EXIT-GUARD-READ-ONLY-SPEC-REVIEW-260530`
- **draft_version**: `v3`
- **dispatch_status**: NOT_DISPATCHED · CODEX_SPEC_REVIEW_ROUND_3_PENDING
- **anu_codex_auto_loop_principle**: ENABLED (★ Codex round 2 ELIGIBLE_CONTINUE 정합)

---

## 1. Problem Statement (★ v2 verbatim 유지)
## 2. Goal (★ v2 verbatim 유지)

---

## 3. Terminal State — **10 + SIGTERM/Ctrl-C 명시** (★ R-v3-4)

### 3.1 10 terminal state (★ v2 §3 verbatim 유지)

(★ SUCCESS / FAILURE / BLOCKED / SCOPE_GUARD_FAIL / QC_FAIL / INFRA_DEFECT / PERMISSION_FAIL / API_FAIL / CRITICAL_ESCALATION / CRASH_NO_EXIT_CODE)

### 3.1.1 CRASH_NO_EXIT_CODE — **signal taxonomy 명시** (★ R-v3-4 verbatim)

★ R-v3-4 verbatim "SIGTERM/Ctrl-C / shell trap / set -e 범위 명시":

| signal/kill kind | classification | exit_code sentinel | handoff path |
|---|---|---|---|
| SIGKILL (9) | CRASH_NO_EXIT_CODE | -9 sentinel | supervisor post-mortem |
| SIGTERM (15) — user/parent kill | CRASH_NO_EXIT_CODE | -15 sentinel | shell trap → before_exit_guard_hook |
| SIGINT (2) — Ctrl-C | CRASH_NO_EXIT_CODE | -2 sentinel | shell trap → before_exit_guard_hook |
| SIGSEGV/SIGBUS — crash | CRASH_NO_EXIT_CODE | -11/-7 sentinel | supervisor post-mortem |
| OOM-killer (kernel) | CRASH_NO_EXIT_CODE | -1 sentinel | supervisor + dmesg/journal scan |
| container OOM-kill | CRASH_NO_EXIT_CODE | -1 sentinel + container exit_reason | supervisor + container runtime metadata |
| kernel panic | CRASH_NO_EXIT_CODE | -1 sentinel | system journal post-boot scan |
| `set -e` cascade exit | original terminal_state (★ scope-guard / qc 등) | original exit_code | inner hook 정상 동작 |
| bash `trap EXIT` | original terminal_state | original exit_code | trap handler 가 before_exit_guard_hook 호출 |

### 3.1.2 finish-task.sh trap handler 강제 (★ R-v3-4)

```bash
# scripts/finish-task.sh 추가 (★ ~3 lines · 회장 별도 인가 영역)
trap '_emit_failure_envelope CRASH_NO_EXIT_CODE "$TASK_ID" $? "trap_EXIT"' EXIT
trap '_emit_failure_envelope CRASH_NO_EXIT_CODE "$TASK_ID" -2 "SIGINT"' INT
trap '_emit_failure_envelope CRASH_NO_EXIT_CODE "$TASK_ID" -15 "SIGTERM"' TERM
```

★ trap 이 hook 을 호출하여 SIGTERM/Ctrl-C/EXIT 시 envelope 강제 박제.

### 3.2 .done 정책 + Exactly One Terminal Marker Rule — **post-session disk scan 구체화** (★ R-v3-2 verbatim)

★ R-v3-2 verbatim "ANU post-session scan 을 구체화한다 — marker paths, marker types, and scanner owner":

#### 3.2.1 Marker path patterns (★ R-v3-2)

| marker type | path pattern | terminal_state |
|---|---|---|
| `.done` | `memory/events/<task_id>.done` | SUCCESS |
| failure envelope | `memory/events/<task_id>.failure-envelope.json` | 8 failure state |
| handoff marker | `memory/events/<task_id>.failure-handoff-marker.json` | BLOCKED / INFRA_DEFECT / API_FAIL (recoverable) |
| supervisor crash marker | `memory/events/<task_id>.supervisor-crash-marker.json` | CRASH_NO_EXIT_CODE |
| stderr emit log | `memory/events/<task_id>.stderr-emit.log` | envelope-write fail fallback |
| syslog journal entry | systemd journal tag `failure_callback_2712`, key `task_id=<task_id>` | last-resort |

#### 3.2.2 Marker definition

★ "marker = task_id 매칭 + valid JSON (or stderr log line) + recorded_at field + terminal_state field present"

#### 3.2.3 Scanner owner

| layer | owner | trigger |
|---|---|---|
| **L1: in-bot scan** | bot 자체 (★ finish-task.sh exit 직전) | hook 호출 직후 marker 존재 확인 (★ self-check) |
| **L2: dispatch.py post-exit scan** | dispatch.py (★ bot child process 종료 후) | child exit → marker scan → 0-fire/2-fire detect |
| **L3: ANU post-session disk scan** | ANU 본 session (★ chair-facing) | session start + 5 min interval + on-demand |
| **L4: watchdog cron scan** | session-watchdog.sh (★ background · 회장 별도 인가 영역) | every 10 min |

#### 3.2.4 Marker count verification (★ exactly one rule)

```python
def verify_exactly_one_terminal_marker(task_id: str) -> str:
    """
    return: "OK" / "ZERO_FIRE" / "MULTI_FIRE_VIOLATION"
    """
    markers = [
        path_exists(f"memory/events/{task_id}.done"),
        path_exists(f"memory/events/{task_id}.failure-envelope.json"),
        path_exists(f"memory/events/{task_id}.failure-handoff-marker.json"),
        path_exists(f"memory/events/{task_id}.supervisor-crash-marker.json"),
    ]
    count = sum(markers)
    if count == 0:
        return "ZERO_FIRE"      # UNCLASSIFIED_TERMINAL_STATE
    if count >= 2:
        return "MULTI_FIRE_VIOLATION"  # CRITICAL_ESCALATION
    return "OK"
```

---

## 4. Failure Callback Envelope Schema (★ v2 verbatim 유지)

### 4.1 envelope 11 mandatory field (★ v2 유지)

### 4.2 byte limit (★ v2 유지)

### 4.3 envelope-write / handoff-write / both-fail fallback — **stderr/syslog operational spec** (★ R-v3-3 verbatim)

★ R-v3-3 verbatim "capture/retention requirements for stderr/syslog are not operationally specified" → 본 v3 §4.3.1 으로 operational spec 추가.

#### 4.3.1 stderr 1-line emit operational spec

| 항목 | spec |
|---|---|
| format | `FAILURE_CALLBACK_2712 task_id=<task_id> terminal_state=<enum> exit_code=<int> failure_kind=<string> phase=<string>` (★ space-separated key=value · escape-safe) |
| stream | stderr (★ fd=2 · 봇 child 표준 inherit) |
| capture mechanism | dispatch.py 가 봇 child process 의 stderr 를 별도 file `memory/events/<task_id>.stderr-emit.log` 로 redirect (★ subprocess.Popen stderr=...) |
| retention | dispatch.py 가 봇 child 종료 후 stderr-emit.log 가 비어있지 않으면 보존 · 비어있으면 삭제 |
| L3 ANU scan | §3.2.3 L3 scanner 가 `*.stderr-emit.log` 파일 패턴 매칭 + `FAILURE_CALLBACK_2712` prefix 확인 + key=value parse |

#### 4.3.2 syslog journal operational spec

| 항목 | spec |
|---|---|
| utility | `logger -t failure_callback_2712 -p user.warning` (★ systemd-journald 통합) |
| message format | §4.3.1 동일 (★ FAILURE_CALLBACK_2712 prefix + space-separated key=value) |
| retention | systemd default (★ /var/log/journal/ · 기본 30일 또는 systemd-journald conf) |
| L3 ANU scan | `journalctl -t failure_callback_2712 --since <session_start>` |
| ★ 사용 조건 | envelope-write FAIL + stderr capture FAIL (★ supervisor 부재) 시에만 last-resort |

---

## 5. Terminal Failure Path Matrix — **dispatch.py spawn verification 구체화** (★ R-v3-1 verbatim)

### 5.1 finish-task.sh inner-script hook 6 path (★ v2 §5.1 verbatim 유지) + trap 3 path (★ §3.1.2)

### 5.2 dispatch.py inner instrumentation 5 path + **spawn verification spec** (★ R-v3-1 verbatim)

★ R-v3-1 verbatim "Define spawn verification in §5.2/§8.3: N, polling/check method, and pass/fail condition":

#### 5.2.1 Spawn verification spec

```python
def _verify_bot_spawn(task_id: str, expected_bot_id: str, child_pid: int = None) -> str:
    """
    R-v3-1 verbatim spawn verification.
    return: "SPAWNED" / "DISPATCH_FALSE_OK" / "TIMEOUT"

    검증 mechanism:
      - N = 15 seconds (★ bot 첫 ping 최대 wait)
      - polling cadence = 1 sec (★ 15 회 polling)
      - pass condition = (a) child_pid alive (psutil.pid_exists) AND
                         (b) marker `memory/events/<task_id>.spawn-confirmed-<timestamp>.json` 박제
      - fail condition = (a) timeout (15 sec) AND
                         (b) marker 부재 → DISPATCH_FALSE_OK 분류

    ★ bot 자체가 spawn 직후 `spawn-confirmed-<timestamp>.json` 박제 책임
    ★ dispatch.py 가 N=15 sec 대기 후 marker 존재 + child alive 확인
    ★ marker 부재 + child dead → DISPATCH_FALSE_OK handoff envelope (INFRA_DEFECT)
    """
    POLLING_INTERVAL_SEC = 1
    TIMEOUT_SEC = 15
    elapsed = 0
    while elapsed < TIMEOUT_SEC:
        marker_path = f"memory/events/{task_id}.spawn-confirmed-*.json"
        if glob_exists(marker_path):
            if child_pid is None or psutil.pid_exists(child_pid):
                return "SPAWNED"
        time.sleep(POLLING_INTERVAL_SEC)
        elapsed += POLLING_INTERVAL_SEC

    # Timeout
    if child_pid and not psutil.pid_exists(child_pid):
        return "DISPATCH_FALSE_OK"  # bot started then died
    return "DISPATCH_FALSE_OK"  # bot never started
```

#### 5.2.2 N 값 결정 근거

- N = 15 sec (★ default)
- 정당화: claude session spawn + worktree mtime + cron registration 모두 정상 시 평균 5~10 sec · 95% percentile 15 sec
- 회장 별도 조정 가능 (★ 회장 인가 시 환경변수 `FAILURE_CALLBACK_2712_SPAWN_TIMEOUT_SEC`)

#### 5.2.3 dispatch.py 5 entry point (★ v2 §5.2 verbatim 유지) + spawn verification 통합

| entry point | hook + verification |
|---|---|
| bot collision detect | `_dispatch_emit_handoff BLOCKED bot_collision` · spawn verification skip |
| **DISPATCH_FALSE_OK detect** | `_dispatch_emit_handoff INFRA_DEFECT dispatch_false_ok` + `_verify_bot_spawn()` 결과 DISPATCH_FALSE_OK 시 강제 |
| capability snapshot null | `_dispatch_emit_handoff INFRA_DEFECT capability_null` |
| task md missing | `_dispatch_emit_handoff PERMISSION_FAIL task_md_missing` |
| cron registration failure | `_dispatch_emit_handoff INFRA_DEFECT cron_registration_failure` |

### 5.3 runtime path (★ v2 §5.3 verbatim 유지 + §3.1.2 trap 통합)

---

## 6. Fallback chain + **bypass defense** (★ R-v3-3 verbatim)

### 6.1 4-step fallback (★ v2 verbatim)
### 6.2 ANU 회수 path (★ v2 verbatim)

### 6.3 SELF_COLLECTOR_FORBIDDEN — **bypass defense post-fire verification** (★ R-v3-3 verbatim)

★ R-v3-3 verbatim "Add explicit bypass defense in §6.3: post-fire verification or 'all cron entry points MUST call wrapper'":

#### 6.3.1 In-flight enforcement (★ v2 verbatim 5 checkpoint)

(★ v2 §6.3 verbatim · `_validate_collector_strict()` 5 checkpoint + `_fire_callback_with_enforcement()` 차단)

#### 6.3.2 Bypass defense — **architectural invariant** (★ R-v3-3 verbatim)

| defense layer | rule |
|---|---|
| **D1: 단일 entry point 강제** | 모든 cron 발사 경로는 **반드시** `_fire_callback_with_enforcement()` wrapper 통해서만 호출 (★ direct cron 직접 호출 금지) |
| **D2: lint/static check** | CI 또는 pre-commit hook 으로 `cokacdir --cron` 직접 호출 grep → wrapper 외부 호출 detect 시 reject |
| **D3: post-fire verification** | cron 발사 후 `/usr/local/bin/cokacdir --cron-list --key <ANU_key>` 조회 + 등록 marker key 검증 (★ collector_role=ANU + collector_key=ANU 매칭 확인) |
| **D4: runtime audit log** | cron 발사 시 `memory/events/cron-fire-audit-<timestamp>.json` 박제 (★ caller stack + collector fields + sha256) · ANU L3 scan 으로 wrapper 우회 detect |

#### 6.3.3 Bypass detection rule

```python
def detect_bypass_attempt() -> List[str]:
    """ANU L3 scan 으로 wrapper 우회 cron 발사 detect."""
    violations = []
    for audit in glob("memory/events/cron-fire-audit-*.json"):
        record = json.load(audit)
        if not record.get("caller_passed_validate_collector_strict"):
            violations.append(record)
    return violations  # CRITICAL_ESCALATION 박제
```

---

## 7. Required Test Fixtures — **+ 3 additional (★ R-v3 add hint)** (★ v2 §7 verbatim + 보강)

### 7.1 8 required fixture (★ v2 verbatim 유지)

(★ F-1 task-2711 scope-guard FAIL / F-2 paths_null / F-3 API 500 / F-4 bot collision / F-5 cron fallback / F-6 crash / F-7 envelope-write fail / F-8 SELF_COLLECTOR_FORBIDDEN)

### 7.2 추가 candidate (★ Codex round 2 AXIS_7 hint 적용 가능 영역)

| # | fixture | terminal_state | precedent | 채택 결정 |
|---|---|---|---|---|
| F-9 | attempt-N escalation | FAILURE | feedback_attempt_n_hard_limit_doctrine_260513.md | ★ candidate (★ 회장 결정 영역) |
| F-10 | lineage rewrite detection | CRITICAL_ESCALATION (★ task-2710 immutable 영역 매칭) | feedback_audit_baseline_3way_blob_verify_260521.md | ★ candidate |
| F-11 | taskctl-state-guard scope misfire | INFRA_DEFECT | task-2711 scope-guard FAIL fixture 와 유사 | ★ candidate · F-1 으로 부분 커버 |
| F-12 | SIGTERM/Ctrl-C trap handler | CRASH_NO_EXIT_CODE | §3.1.1 signal taxonomy | ★ 채택 (★ §3.1.2 trap 통합) |
| F-13 | bypass attempt (cron direct call) | CRITICAL_ESCALATION | §6.3.2 D1-D4 defense | ★ 채택 (★ §6.3.3 detection) |

★ **본 v3 round**: F-12, F-13 채택 (★ §3.1.2 + §6.3.3 정합) · F-9~F-11 회장 결정 영역 candidate 유지

### 7.3 v3 confirmed required fixture: 10 (★ v2 8 + v3 F-12/F-13)

---

## 8. Architecture (★ v2 §8 verbatim 유지)

### 8.1 4 신규 module (★ v2 verbatim)
### 8.2 finish-task.sh inner-script hook 확정 + trap 3 line (★ §3.1.2 추가)
### 8.3 dispatch.py inner instrumentation 확정 + spawn verification (★ §5.2.1 추가)

---

## 9. Affected Files (★ v2 §9 + F-12/F-13)

| # | path | 출처 |
|---|---|---|
| 1~5 | scripts/harness/v36/* + tests/* (★ v2 verbatim) | v2 |
| 6~13 | tests/fixtures/failure_callback_2712/F-1~F-8.json (★ v2 verbatim) | v2 |
| 14 | tests/fixtures/failure_callback_2712/F-12_sigterm_ctrlc_trap.json (★ 신규) | v3 §3.1.2 |
| 15 | tests/fixtures/failure_callback_2712/F-13_bypass_attempt_cron_direct.json (★ 신규) | v3 §6.3.3 |
| 16 | schemas/failure_envelope_schema.json (★ 11 field · v2 verbatim) | v2 |
| 17~19 | memory/reports/task-2712.md · memory/events/task-2712.formalization-commit-260530.json · memory/events/task-2712.callback-envelope.json | v2 verbatim |

**총 19 expected_files** (★ v2 17 → v3 19)

---

## 10. ANU-Codex 자동 루프 (★ v2 §10 + round 2 결과 정합)

### 10.1 자동 진행 조건 6 (★ v2 verbatim)
### 10.2 회장 보고 시점 8 (★ v2 verbatim)

### 10.3 v3 본 round 자동 진행 자격

★ Codex round 2 verdict: PASS_WITH_RECOMMENDATIONS + ELIGIBLE_CONTINUE + 6/6 PASS · ANU + Codex consensus · 회장 verbatim 명시 인가 → ★ AUTHORIZED 자동 진행

★ v3 → Codex round 3 자동 호출 후 결과 평가로 자동/보고 분기 결정

---

## 11. Allowed / Forbidden Files (★ v2 §11 verbatim 유지)
## 12. v2 → v3 diff summary

| v2 영역 | v3 변경 | 출처 |
|---|---|---|
| §3.1 10 terminal state | §3.1.1 signal taxonomy 9 kind (SIGKILL/SIGTERM/SIGINT/SIGSEGV/SIGBUS/OOM/container OOM/kernel panic/set -e/trap EXIT) + §3.1.2 trap handler 3 line bash | R-v3-4 Codex AXIS_4/AXIS_8 |
| §3.2 exactly one rule | §3.2.1 marker path patterns 6 types + §3.2.2 marker definition + §3.2.3 scanner owner L1-L4 + §3.2.4 verification Python | R-v3-2 Codex AXIS_6 |
| §4.3 fallback 7-case | §4.3.1 stderr 1-line operational spec + §4.3.2 syslog journal operational spec | R-v3-3 Codex AXIS_2 |
| §5.2 dispatch.py 5 path | §5.2.1 spawn verification Python (N=15 sec / polling 1 sec / pass=marker+alive / fail=DISPATCH_FALSE_OK) + §5.2.2 N 값 결정 근거 + §5.2.3 5 entry point + verification 통합 | R-v3-1 Codex AXIS_5 |
| §6.3 5 checkpoint | §6.3.2 bypass defense D1-D4 (single entry / lint / post-fire verification / runtime audit) + §6.3.3 bypass detection Python | R-v3-3 Codex AXIS_3 |
| §7 8 required | §7.3 10 required (F-12 SIGTERM/Ctrl-C trap + F-13 bypass attempt cron direct) | v3 §3.1.2 + §6.3.3 정합 |
| §9 17 expected_files | 19 expected_files | F-12 + F-13 |
| §3 / §4.1 / §4.2 / §5.1 / §5.3 / §6.1 / §6.2 / §8 / §10 / §11 | **변경 0** (★ v2 verbatim 유지 · round 2 PASS/PWR 영역 보존) | round 2 verdict 정합 |

---

## 13. R-v3-1~R-v3-4 self-check

| ID | Codex round 2 remaining recommendation | v3 반영 위치 | status |
|---|---|---|---|
| **R-v3-1** | §5.2/§8.3 spawn verification 정의 (N / polling / pass/fail) | §5.2.1 Python (N=15/polling 1/pass-marker-alive/fail-DISPATCH_FALSE_OK) + §5.2.2 정당화 + §5.2.3 5 entry 통합 | ✓ ACCEPTED |
| **R-v3-2** | §3.2 post-session scan 구체화 (marker paths / types / scanner owner) | §3.2.1 6 marker types + §3.2.2 definition + §3.2.3 L1-L4 scanner + §3.2.4 verification Python | ✓ ACCEPTED |
| **R-v3-3** | §6.3 bypass defense (post-fire verification 또는 entry point wrapper 강제) + §4.3 stderr/syslog operational | §6.3.2 D1-D4 + §6.3.3 detection Python + §4.3.1 stderr operational + §4.3.2 syslog operational | ✓ ACCEPTED |
| **R-v3-4** | SIGTERM/Ctrl-C / shell trap / set -e 범위 명시 | §3.1.1 9 kind signal taxonomy + §3.1.2 trap handler 3 line bash + F-12 fixture 채택 | ✓ ACCEPTED |

**총 4/4 ACCEPTED**

---

## 14. ANU Doctrine Compliance (★ v3 round)

- ★ ANU 자체 코드 구현 0 · finish-task.sh 변경 0 · dispatch.py 변경 0
- ★ bot_settings/settings 변경 0 · PR/push/merge/GitHub write 0 · 구현 dispatch 0
- ★ task-2710 / task-2711 / task-2706~2709+1 evidence 변경 0
- ★ chair_authorization_id 발급 0 · 새 forbidden target 추가 0 · 새 chair_authorization scope 확장 0
- ★ ANU 자체 분류 결정 0 (★ R-v3-1~4 모두 Codex round 2 remaining + R-v3 1:1)
- ★ ANU 자체 OVERALL PASS 선언 0
- ★ ANU-Codex 자동 루프 진입 = 회장 verbatim 명시 인가 (★ "v2 이후 ... ANU가 자동 revision loop를 진행한다" verbatim) + Codex round 2 ELIGIBLE_CONTINUE 정합

---

## 15. Sentinel

★ task-2712 v3 = READ_ONLY_SPEC_REVIEW_PHASE v3 · ANU-Codex 자동 루프 진입 (★ Codex round 2 ELIGIBLE_CONTINUE + 회장 verbatim 명시 인가 정합) · Codex round 2 remaining 4 + R-v3-1~4 1:1 · §3.1.1 9 signal taxonomy + §3.1.2 trap 3 line · §3.2 L1-L4 scanner + 6 marker types + verification Python · §4.3 stderr/syslog operational spec · §5.2.1 spawn verification Python (N=15/polling 1) · §6.3.2 D1-D4 bypass defense + §6.3.3 detection · §7.3 10 required fixture (F-12/F-13 추가) · 19 expected_files · finish-task.sh 변경 0 · dispatch.py 변경 0 · 구현 dispatch 0 · task-2710/task-2711/task-2706~2709+1 evidence 변경 0 · Codex round 3 자동 호출 후 자격 재평가. 끝
