# task-2702 — DAEMON_SOURCE_FILE_FLAPPING 진단 보고 (read-only) · 결론 OWNER_DECISION_REQUIRED

- 담당: dev2-team Odin(오딘) · chair_authorization_id: `CHAIR-AUTH-TASK-2702-DAEMON-SOURCE-FILE-FLAPPING-ROOT-CAUSE-FIX-20260528-JJONGS-IMPLEMENT-001`
- 작업 시각: 2026-05-28 09:49~10:20 KST
- 모드: **read-only 진단** (mutating 0 · source/test 손상 0 · 금지행위 0)
- 결론: **WRITER_MECHANISM_IDENTIFIED + NO_CURRENTLY_ACTIVE_FLAPPING_WRITER_REPRODUCES → 규정 준수 최소 패치 부재 → `OWNER_DECISION_REQUIRED`**

> ⚠ task 파일 sha256 불일치 기록: 지시문 기대 `5f536df8…` vs 실제 `f54e0ad8…` (next_task_proposed 마커의 `5ef1726d…`와도 불일치). 내용은 dispatch verbatim 목표와 정확히 일치하여 동일 task로 판단하고 진행. read-only라 위험 없음.

---

## 요약 (SCQA)

- **S**: utils/replacement_pr_runner.py(정본 718L) + tests/regression/test_replacement_pr_runner_2510.py(정본 493L)가 0바이트 truncate↔복원 flapping 한다고 보고됨(메타 1차 종료선 유일 blocker).
- **C**: 회장 단서 = 두 파일 mtime 나노초 완전 동일(`08:58:35.472434414`) → "단일 writer 일괄 재기록(git checkout/reset/stash 계열)".
- **Q**: truncate writer(daemon/hook/watcher)는 무엇이며 write-path를 어떻게 분리하나?
- **A**: **truncate writer 메커니즘은 특정**(`transplant_expected_files`의 기본-WORKSPACE write). 그러나 **현재 활성 writer는 다중 방법으로 재현되지 않음**(과거 벡터는 이미 제거/격리). 나노초 동일 mtime은 truncate가 아니라 **복원(git checkout) 측 시그니처**. 규정 준수 가능한 최소 차단 패치가 존재하지 않아(근원 차단은 보호 대상 718줄 source 편집 필요 = 금지) **OWNER_DECISION_REQUIRED**.

---

## 보고 필드 1 — flapping writer 정체 (증거 기반)

**(A) truncate(0바이트) writer 메커니즘 = `utils/replacement_pr_runner.py::transplant_expected_files`**
- L185 `cwd = repo_dir or str(WORKSPACE)` — `repo_dir` 미지정 시 **실제 WORKSPACE**(`WORKSPACE = os.environ.get("WORKSPACE_ROOT","/home/jay/workspace")`, L20, import 시점 평가).
- L193-195 `target = Path(cwd) / filepath; target.parent.mkdir(...); target.write_text(sr.stdout, encoding="utf-8")` — `git show <source_head>:<filepath>` stdout을 **파일에 직접 기록**.
- 함수 docstring 자체 경고(L182): *"파일 시스템에 실제로 write하므로 호출자는 임시 작업 dir(tmp_path 등)을 repo_dir로 줘야 source 손상 방지."*
- expected_files 가 `["utils/replacement_pr_runner.py","tests/regression/test_replacement_pr_runner_2510.py"]`(task-2510 시나리오 정본)일 때, repo_dir 없이 호출 + `git show` stdout이 빈 문자열(returncode 0)이면 → `Path("/home/jay/workspace")/<두 파일>.write_text("")` = **0바이트**. for-loop로 두 파일 연속 기록 → 거의 동시 mtime.
- 이 두 경로를 0바이트로 만들 수 있는 코드 경로는 코드베이스 전체에서 `transplant_expected_files`가 **유일**(grep 전수: 현재 .py/.sh에 `truncate -s 0 …replacement_pr_runner`/`open('utils/replacement_pr_runner…','w')` 0건).

**(B) production 잠재 호출부 = `utils/merge_queue_executor.py` L1745** `ReplacementPRRunner(runner=runner, dry_run=args.dry_run)` — **repo_dir 미지정**(기본 WORKSPACE). W2 `ctx.replacement_runner.execute()`(L1255). 정본 production wiring(baseline reaudit 2026-05-21: v1 utils/ 718L = sole production source). → 잠재 위험 경로(향후 어떤 PR의 expected_files가 이 두 파일이고 transplant 도달 시 실 source write).

**(C) 과거(현재는 제거됨) 0바이트 벡터** — task-2586+1 forensic(05-15):
- `tests/regression/test_audit_trail_followup_2579.py:214` `open('utils/replacement_pr_runner.py','w').close()`
- `tests/regression/test_audit_trail_bash_capture_v3.py:69+188` `truncate -s 0 utils/replacement_pr_runner.py`
- → **현재 두 .py 모두 삭제됨**(`__pycache__`에 orphan `.pyc`만 잔존 · pytest 9는 .py 없는 orphan .pyc 수집/실행 불가 = inert).

**비-writer 확정(false attribution 배제)**: 실행 데몬 루프 0(ps), inotify/watcher 0, user crontab의 시간별 `cleanup-stale-tasks.sh`도 **10:00 정각 경계가 모니터링 중 통과했으나 flap 0** → cleanup-stale 배제. finish-task.sh는 truncate가 아니라 이미 truncated된 상태를 stash로 격리(GIT-GATE)할 뿐(stash@{3} = `[task-2569] … test_replacement_pr_runner_2510.py + utils/replacement_pr_runner.py` 명시).

## 보고 필드 2 — truncate 메커니즘 (왜 0바이트인가)

- **직접 write(write_text("")) — git checkout/reset/stash 아님.** `transplant_expected_files`가 빈 `git show` stdout을 `Path.write_text`로 기록 → 0바이트.
- **나노초 동일 mtime(`08:58:35.472434414`)의 정체 = truncate가 아니라 복원**: audit-trail.jsonl `2026-05-28T08:58:43+09:00` (session `cce85d66`, task-2701, dev2 오딘, ws BB572E3F) `git checkout origin/main -- utils/replacement_pr_runner.py tests/regression/test_replacement_pr_runner_2510.py` (dpat=`git-checkout-file`, 명령 주석 `=== truncate 2파일 재복원 ===`). 즉 회장 단서의 "단일 writer 일괄 재기록"은 **수동 복원(git checkout) 측 시그니처**이며 active truncate writer가 아님.
- **측정 혼동 요인**: baseline reaudit(05-21)는 과거 "0 lines stub" 결론이 실은 파일이 `stash@{0}/{3}`로 빠져있던 **stash-out 중간상태 측정 오류**였다고 정정. finish-task GIT-GATE stash + 수동 git checkout 복원이 결합해 "flapping"으로 관측될 수 있었음.

## 보고 필드 3 — write-path 분리 패치 내역

**패치 없음(N/A) — 이유(규정 준수 최소 패치 부재):**
1. 근원 writer(`transplant_expected_files`)는 **보호 대상 `utils/replacement_pr_runner.py`(718줄 유지 의무) 내부**. fail-closed 빈-stdout 가드 추가 = source 편집 → 718줄 위반 + source 편집 금지.
2. production 호출부 `merge_queue_executor.py L1745`의 repo_dir를 workspace 외부로 돌리면 **replacement-PR production 기능 자체가 파손**(replacement 흐름은 실 repo에 branch/commit/push해야 함) = "주변 리팩터/기능 파손" 금지.
3. **현재 비격리 테스트 호출부가 존재하지 않음** — 전수 확인 결과 현재 모든 호출은 `repo_dir=str(tmp_path)`(t04/t05/t07/t13/t14/t15) 또는 `dry_run=True`(orchestration_2514, e2e_2515) 또는 transplant 미도달(t01 no-op/t03 forbidden-abort/T16 import-only). 패치할 비격리 대상 부재.
4. owner_trigger/divergence_guard/finish-task/dispatch.py/settings/hooks 변경 = 명시 금지.
→ `추정 패치 금지` 원칙(task ②)에 따라 **패치 보류 + OWNER_DECISION_REQUIRED**.

## 보고 필드 4 — 안정성 측정 (linecount/sha256 시계열 · 패치 없이도 현 상태 안정)

| 시각(KST) | rpr L/sha12 | test L/sha12 | mtime | git |
|---|---|---|---|---|
| 09:24 (ANU 사전) | 718/95809c89 | 493/57ebdc51 | 08:58:35.472434414 | clean |
| 09:49 (착수) | 718/95809c89b2f8 | 493/57ebdc51bde5 | 08:58:35.472434414 | clean |
| 10:19 (최종) | 718/95809c89b2f8 | 493/57ebdc51bde5 | 08:58:35.472434414 | clean |

- **flap_monitor.sh** 09:52:59~10:19(26분+) 고속 폴링(0.3s) 모니터링: **CHANGE 이벤트 0건**. 10:00 정각 hourly cron 경계 포함.
- 실제 파일 mtime은 08:58:35에서 **1시간 20분+ 무변경** = 복원 이후 재truncate 0.

**재현 시도(모두 NEGATIVE · 실제 파일 무손상):**
1. `WORKSPACE_ROOT=/tmp/rpr_sandbox` redirect + 러너 4개 테스트(70 passed) → 샌드박스 카나리 truncate 0.
2. chmod 0444 가드 + 전체 `tests/`+`anu_v2/`(5911 passed) → 두 파일 PermissionError 0 · 무손상.
3. chmod 0444 가드 + 의심 e2e/auto_merge/integration → truncate 0.
4. **chmod 0444 가드 + `bash scripts/ci_preflight.sh /home/jay/workspace --affected-only 1`(finish-task QC가 실제 호출하는 명령, HEAD~1 affected=divergence test)** → exit=0 PASS · truncate 0.

→ **현재 코드베이스는 flapping을 재현하지 않음**(다중 독립 방법 일치).

## 보고 필드 5 — source/test skip-worktree 미적용 확인

`git ls-files -v` 결과 두 파일 모두 **`H`**(정상 추적 · skip-worktree/assume-unchanged 미적용). 진단 전 과정에서 skip-worktree 추가 0.

## 보고 필드 6 — 현재 skip-worktree 목록 (16개 · source/test 미포함 재확인)

```
S dashboard/data/naver-sa-stats.json
S memory/bot_settings_sync.json
S memory/canary-status.json
S memory/cross-functional-status.json
S memory/events/bot-activity.json
S memory/events/member-status.json
S memory/logs/audit-trail.jsonl
S memory/logs/qc-skip-log.jsonl
S memory/logs/retry-counters/task-test-003.fail_history.jsonl
S memory/memory-check-log.json
S memory/pipeline-status.json
S memory/specs/.spec-state-cache.json
S memory/task-timers.json
S memory/token-ledger.json
S memory/whisper/session-guidance.json
S memory/whisper/status.json
```
- **16개 전부 state JSON/JSONL** (task 기재 15개 대비 +1, 모두 임시 noise 억제 state). **.py source/test 0건** = source/test 미적용 확정.

## 보고 필드 7 — forbidden_action_count

**0**. (mutating git 0 · reset/clean/stash/checkout/push 0 · skip-worktree(source/test) 0 · dispatch.py/settings/hooks 변경 0 · PR#158/159/160·task-2700·2700+1 branch 변경 0 · manual .done 0 · G4 marker 삭제 0 · 추가 메타 개선 0. chmod 0444↔0644 가드는 내용 불변·git mode 중립(100644 유지)·즉시 원복.)

---

## ★ OWNER_DECISION_REQUIRED — 회장 결정 필요

근원 writer 메커니즘은 특정되었으나(`transplant_expected_files` 기본-WORKSPACE write, 보호 대상 718줄 source 내부) **현재 활성 flapping은 다중 방법으로 재현 불가**하며, 규정 준수 가능한 최소 차단 패치가 존재하지 않습니다. 다음 중 택일을 요청합니다:

- **옵션 1 (ALREADY_REMEDIATED_ACCEPT)**: 과거 truncate 벡터(하드코딩 truncate 테스트 .py 삭제 + 모든 러너 테스트 호출부 tmp_path 격리)가 이미 제거/격리되어 현 상태가 안정(26분+ 무flap, 1h20m+ mtime 무변경, CI-PREFLIGHT 무재현)임을 인정 → ANU가 10분+ 독립 재확인 후 META 1차 종료선 close.
- **옵션 2 (AUTHORIZE_SOURCE_GUARD)**: 718줄 보존/ source 편집 금지에 **명시적 예외**를 부여하여 `transplant_expected_files`에 fail-closed 가드(repo_dir 미지정 시 또는 cwd==실 WORKSPACE에서 tracked production source 경로 write 거부) 추가. = 근원 영구 차단이나 보호 대상 source 라인수 변경 동반 → 회장 결재 필수.
- **옵션 3 (PROVIDE_REPRO)**: 08:48 flapping을 관측한 정확한 재현 조건(예: 특정 merge-queue 호출/ task-2701+1 finish-task 당시 affected 파일 집합) 제공 → 보호 대상 외 파일에 한정한 표적 패치 범위 산정.

본 task는 task ② 지침(`원인 미특정/추정 패치 금지 → OWNER_DECISION_REQUIRED 보고 후 정지`)에 따라 **패치 미적용 · finish-task 미실행 · .done 미생성** 상태로 정지합니다.

## 모델 사용 기록
- 본 진단은 read-only forensic 성격(다수 mutating-위험 명령의 안전성 판단·증거 해석 필요)으로 팀장(오딘, Opus) 직접 수행. 팀원 위임 없음(코드 작성 0 · 위험 통제 우선).

## 산출물
- `memory/reports/task-2702.md` (본 보고서)
- `memory/events/task-2702.owner-decision-required.json` (escalation 마커)
- 진단 보조: `/home/jay/.cokacdir/workspace/DBB288E5/flap_monitor.{sh,log}` (워크스페이스 외부, 비추적)
