# task-2658 — SPAWN_VISIBILITY_FALSE_NEGATIVE_PREVENTION_GUARD 보고서

## SITUATION
- task-2657 (Axis 3 canary scale-aware guard) 완료 후 ANU가 11:57:24 KST에 task-2657 spawn 4 신호 ABSENT를 측정 → SPAWN_SILENT_DROP_SUSPECTED로 오분류.
- 실제 봇 worktree는 `/home/jay/.cokacdir/workspace/426931FE/wt-2657-dev6`에 정상 spawn되어 있었음. ANU는 `/home/jay/workspace/.worktrees/` 한 곳만 조회 → cokacdir 봇 컨벤션 누락.
- 회장 verbatim 결정: ANU 공통 dispatch/spawn 검증 도구를 `utils/anu_spawn_visibility_guard/*` 위치에 신규 박제하여 재발 방지.

## COMPLICATION
- Axis 3 canary observer chain은 real_t0+24h(2026-05-25T19:51:35+09:00) 도달 전까지 RUNNING 승격 0 유지 필요 → 본 작업은 monitoring/judgment layer 한정.
- PreToolUse runtime / Axis 1·2 runtime / dispatch.py / live settings.json / live cokacdir / hooks/* / axis_3_canary_scale_aware_guard 모듈 일체 변경 0.
- self-attestation 단독 인정 금지(ANCHOR-2). 최소 2 source 교차 강제. TRUE_SILENT_DROP은 30분 hard timeout + 예외 3건 동시 충족.

## QUESTION
- 두 후보 worktree 위치(legacy + cokacdir)를 동시 glob하고, 8 source 교차로 5-enum (SPAWN_VERIFIED / PENDING / VISIBILITY_GAP / CALLBACK_RECOVERED_AFTER_VISIBILITY_GAP / TRUE_SILENT_DROP)을 판정하는 모듈 + 6 regression을 어떻게 박제하여 task-2657 사고를 재현 차단할 것인가?

## ANSWER

### 모듈 설계 (5 파일 · utils/anu_spawn_visibility_guard/)
1. `path_resolver.py` — 두 후보 worktree root 동시 glob. wt-/iso-/iso-wt-/task-/<short>- 5 패턴. schedule_id 미제공 시 cokacdir root 전체 광역 fallback glob (★ ANU가 schedule_id 컨벤션 모를 때 false negative 방지).
2. `source_collector.py` — 8 source 동시 수집 dataclass(SourceSnapshot). 의존성 주입형 `process_lister` (테스트 격리 보장).
3. `status_classifier.py` — 5-enum 판정. Rule 우선순위: TRUE_SILENT_DROP → CALLBACK_RECOVERED → SPAWN_VERIFIED(strict 3신호) → SPAWN_VERIFIED(relaxed 2신호) → SPAWN_PENDING → SPAWN_VISIBILITY_GAP fallback. ANCHOR-2 self-attestation 단독 인정 금지.
4. `timeout_gate.py` — 30분 hard timeout + 예외 3건 (schedule_history pending / result marker / callback envelope) gate.
5. `__init__.py` — 공개 API 통합.

### 6 회장 verbatim 보고

#### 1. changed_files (10)
```
utils/anu_spawn_visibility_guard/__init__.py
utils/anu_spawn_visibility_guard/path_resolver.py
utils/anu_spawn_visibility_guard/source_collector.py
utils/anu_spawn_visibility_guard/status_classifier.py
utils/anu_spawn_visibility_guard/timeout_gate.py
tests/anu_spawn_visibility_guard/__init__.py
tests/anu_spawn_visibility_guard/test_regression_6.py
memory/events/task-2658.spawn-visibility-guard-result-260525.json
memory/reports/task-2658.md
memory/events/task-2658.done
```

#### 2. guard 적용 위치
- `utils/anu_spawn_visibility_guard/*` 5 모듈. ★ Axis 3 전용 위치(`utils/axis_3_canary_scale_aware_guard/*`) **사용 금지** anchor 준수.
- PreToolUse runtime, Axis 1·2 runtime, dispatch.py, live settings.json, live cokacdir, hooks/* 전부 **ZERO 변경**. axis_3 canary 모듈도 미변경 — observer chain 격리 유지.

#### 3. source priority (8 source 우선순위 + 2 source 교차 룰)
| 순위 | source | location |
| --- | --- | --- |
| 1 | legacy worktree | `/home/jay/workspace/.worktrees/wt-<task_short>-<team>/` (외 4 패턴) |
| 2 | cokacdir worktree | `/home/jay/.cokacdir/workspace/<schedule_id>/wt-<task_short>-<team>/` |
| 3 | schedule_id workspace dir | `/home/jay/.cokacdir/workspace/<schedule_id>/` 존재 여부 |
| 4 | executor process | `ps -e -o pid,args` + 8 pattern (wt-/task//--task-id 등) · 별칭 분기 고려 |
| 5 | schedule_history | `/home/jay/.cokacdir/schedule_history/<schedule_id>.log` JSONL parse |
| 6 | result marker | `memory/events/<task_id>.*-result-*.json` glob |
| 7 | report marker | `memory/reports/<task_id>.md` |
| 8 | done marker | `memory/events/<task_id>.done` |
| 8a | callback_inbox.acked | `memory/.callback_inbox/*<task_id>*.acked` + short 패턴 |

★ 교차 규칙: 최소 2 source 양성이어야 callback recovery 판정. self-attestation 단독 source는 **인정 금지**(ANCHOR-2). worktree 두 후보 중 한쪽만 양성이어도 SPAWN_VERIFIED 가능 — silent drop 단정으로 직결 안 됨(ANCHOR-4).

#### 4. TRUE_SILENT_DROP timeout (30분 · 예외 3건)
- `HARD_TIMEOUT_SECONDS = 1800` (30분).
- Blocking exceptions (하나라도 있으면 TRUE_SILENT_DROP 단정 금지):
  1. `schedule_history` last_status ∈ {pending, running, in_progress, started, fired}
  2. `result marker present` (`memory/events/<task_id>.*-result-*.json`)
  3. `callback envelope present` = `done_marker` ∨ `report_marker` ∨ `callback_inbox.acked` (ANCHOR-2: done/report 포함)
- 적격성: `elapsed_seconds >= 1800` AND `blocking_exceptions == ()` AND `positive_sources == ()` AND `callback_evidence_sources == ()`

#### 5. status enum 판정표
| enum | 입력 조건 (요약) | 대응 regression |
| --- | --- | --- |
| `SPAWN_VERIFIED` (strict) | worktree(legacy ∨ cokacdir) ∧ executor_process ∧ first_response_not_refusal=True | R1, R2 |
| `SPAWN_VERIFIED` (relaxed) | worktree ∧ executor_process ∧ first_response_not_refusal=None | R3, R3-bonus |
| `CALLBACK_RECOVERED_AFTER_VISIBILITY_GAP` | callback_evidence ≥ 2 source ∧ (initial_anu_absent OR process_absent) | R4 |
| `SPAWN_PENDING` | NOT silent_drop_eligible ∧ (schedule_history pending OR elapsed < 15분) | R6 (short) |
| `SPAWN_VISIBILITY_GAP` | NOT silent_drop_eligible ∧ positive_sources>0 ∧ NOT pending ∧ elapsed ≥ 15분 — 또는 교차 미달 fallback | (rule 5 fallback) |
| `TRUE_SILENT_DROP` | silent_drop_eligible=True ∧ positive_sources=() ∧ callback_evidence=() | R5 |

#### 6. regression 6 결과 (R1~R6 개별 PASS/FAIL)
모두 PASS · 격리 tmp_path 사용 · 외부 fs/process 미오염.

```
RegressionR1LegacyWorktree::test_r1                                             PASSED [ 14%]
RegressionR2CokacdirWorktree::test_r2                                           PASSED [ 28%]
RegressionR3CokacdirOnlyFalseNegativeBlock::test_r3                             PASSED [ 42%]
RegressionR3CokacdirOnlyFalseNegativeBlock::test_r3_without_schedule_id_fallback_glob PASSED [ 57%]
RegressionR4CallbackRecoveredAfterGap::test_r4                                  PASSED [ 71%]
RegressionR5TrueSilentDrop::test_r5                                             PASSED [ 85%]
RegressionR6SchedulePendingBlocksSilentDrop::test_r6                            PASSED [100%]
============================== 7 passed in 0.11s ===============================
```

| id | 시나리오 | expected | actual | status |
| --- | --- | --- | --- | --- |
| R1 | legacy `.worktrees/` 위치 worktree 존재 | SPAWN_VERIFIED | SPAWN_VERIFIED | ✅ PASS |
| R2 | cokacdir + legacy 양쪽 위치 worktree 존재 | SPAWN_VERIFIED | SPAWN_VERIFIED | ✅ PASS |
| R3 | **legacy 부재 · cokacdir 만 존재 (★ false negative 차단 핵심 · task-2657 사고 직접 재현)** | SPAWN_VERIFIED | SPAWN_VERIFIED | ✅ PASS |
| R3-bonus | R3 + schedule_id 미제공 → 광역 fallback glob | SPAWN_VERIFIED | SPAWN_VERIFIED | ✅ PASS |
| R4 | executor process 부재 + done/result/report 3 source 교차 | CALLBACK_RECOVERED_AFTER_VISIBILITY_GAP | 동일 (crossed=3) | ✅ PASS |
| R5 | 전 source 부재 + elapsed = 30분 + 60초 | TRUE_SILENT_DROP | TRUE_SILENT_DROP (eligible=True, exceptions=[]) | ✅ PASS |
| R6 | schedule_history last_status=pending (short + long elapsed 두 경우) | SPAWN_PENDING + TRUE_SILENT_DROP 단정 금지 | SPAWN_PENDING (short) + NOT TRUE_SILENT_DROP (long, blocking_exceptions=['schedule_history pending']) | ✅ PASS |

#### 7. forbidden_action_count (target 0)
**0**. 11 forbidden 카테고리 모두 0:
- Axis 3 RUNNING 자동 선언: 0
- full rollout: 0
- HARNESS_ENFORCED 전체 선언: 0
- policy 승격: 0
- BLOCK 정책 확대: 0
- PreToolUse runtime 변경: 0
- Axis 1/2 runtime 변경: 0
- live settings.json 변경: 0
- dispatch.py 변경: 0
- live cokacdir 변경: 0
- commit/push/PR/merge: 0

추가 격리 anchor: axis_3_canary_scale_aware_guard 모듈/테스트 변경 0 · observer chain 격리 유지.

## 머지 판단
- **머지 필요**: No (회장 verbatim 금지 #10: commit/push/PR/merge)
- **브랜치**: `task/task-2658-dev6` (origin/main 기준)
- **워크트리 경로**: `/home/jay/workspace/.worktrees/task-2658-dev6`
- **base commit**: `0e172435 (origin/main: Merge PR #144 task-2641)`
- **머지 의견**: 본 task는 ANU 공통 dispatch/spawn 검증 도구 신규 박제. commit/push/PR/merge **전면 금지**(금지 #10) · 회장 직접 결정 시까지 worktree 보존. result marker + report + .done 만 박제하며 코드 자체는 staging 상태로 격리.

## 발견 이슈 및 해결
- **이슈 1**: Pyright LSP가 `utils.anu_spawn_visibility_guard` import를 미해결 처리.
  - **원인**: worktree 디렉터리가 LSP 인덱스에 포함되지 않은 false positive (task-2657 동일 케이스).
  - **해결**: `python3 -m py_compile` 전체 통과 + pytest 7/7 PASS로 실 동작 검증. report에 명시.

- **이슈 2**: pytest collection 초기 ModuleNotFoundError (`utils.anu_spawn_visibility_guard`).
  - **원인**: 루트 `conftest.py`가 `WORKSPACE_ROOT` 환경변수(기본값 `/home/jay/workspace`) 기준으로 sys.path를 설정 → 메인 worktree의 utils 패키지로 해석.
  - **해결**: `WORKSPACE_ROOT=/home/jay/workspace/.worktrees/task-2658-dev6` 환경변수 export 후 pytest 실행. result marker의 `pytest_invocation` 필드에 박제.

## 모델 사용 기록
- 본 task: Opus 4.7 직접 수행 (회장 verbatim executor 지정 + Lv.4 critical + 정밀 단일소스 spec). Sonnet sub-agent 위임 없음 (task-2657 동일 패턴).

## 종결
**`SPAWN_VISIBILITY_FALSE_NEGATIVE_PREVENTION_GUARD_IMPLEMENTED`**
