# task-2554+2 보고서 — OWNER_TRIGGER_ONLY_CAPABILITY 통합 capability 완성

**status**: IN_PROGRESS (PR open / Gemini fresh review 대기 단계)
**level**: Lv.4 (critical)
**team**: dev5 (마르둑)
**replacement_of**: task-2554+1 / PR #105 (IMPLEMENTATION_INCOMPLETE_PER_OWNER_SPEC)
**owner_directive**: 회장 §명시 옵션 E 해석 2 (2026-05-12 KST) — 5 layer 통합 완성

---

## SCQA

### Situation
- task-2554 (PR #104, head 4e8f89795ab1) → ESCALATED: HIGH race condition + 6 Gemini medium unresolved.
- task-2554+1 (PR #105, head 08f2d29ccb14) → Replacement attempt. HIGH race condition fix 반영했으나
  Gemini fresh medium 3건 (회장 §1~§2 직접 미충족) + §3~§5 미구현 → IMPLEMENTATION_INCOMPLETE_PER_OWNER_SPEC.
- OWNER_TRIGGER_ONLY_CAPABILITY 가 main 미반영 → task-2554+1 자체 PR 의 Gemini follow-up trigger
  합법 경로 부재 → chain 정책 진입 (자동 task-2554+3 X, OWNER_DECISION_REQUIRED).

### Complication
- PR #105 추가 push 금지 (회장 §명시 forbidden 18 #2/#7/#10).
- 2nd OWNER bootstrap trigger 요청 금지 (THIS_PR_AND_THIS_HEAD_ONLY 1회성 박제 보존).
- 회장 §1~§5 가 5 layer 통합 완성을 단일 PR 안에서 요구.

### Question
PR #105 close 보존 + 신규 PR 1건으로 5 layer (runner / audit / executor integration / file evidence /
regression fixtures) 통합 완성을 어떻게 박제하고 chain 정책 위반 0 으로 머지할 것인가?

### Answer
회장 §명시 옵션 E 해석 2 1:1 — origin/main 기준 새 PR (task/task-2554+2-dev5) 으로 capability 완성:

- §1 runner (`anu_v2/owner_trigger_only.py`):
  PR #105 baseline + RESULT_PENDING import + http_post 직전 ``txn.record(PENDING)`` (crash-safety).
- §2 audit (`anu_v2/owner_trigger_audit.py`):
  PR #105 baseline + bounded reverse scan (``_iter_rows_reverse`` + ``DEDUPE_SCAN_MAX_ROWS=512``).
  ``_has_posted`` / ``_has_active_trigger`` 가 reverse scan 사용 (O(N) full scan 제거).
- §3 executor integration (`anu_v2/merge_queue_executor.py`):
  ``GEMINI_STALE_ON_HEAD`` + ``OWNER_TRIGGER_REQUIRED`` decision codes,
  ``detect_gemini_stale_on_head`` / ``emit_owner_trigger_decision`` /
  ``record_owner_trigger_outcome`` / ``detect_fresh_gemini_review`` /
  ``mark_gemini_fresh_detected`` / ``record_no_fresh_evidence_after_post`` /
  ``orchestrate_owner_trigger_for_stale_pr`` 7 method 추가 (minimal patch — 기존 evaluate flow 보존).
- §4 file evidence: decision JSON v1 + 5종 marker (requested / posted / failed / gemini-fresh-detected /
  posted-but-no-fresh-evidence) — owner_trigger_audit.jsonl 영구 append-only.
- §5 regression fixtures: 6 신규 test file (35 tests) — bootstrap_gap_pr105 / fresh_medium_3 /
  dedupe_same_head / head_changed_fail_closed / token_unavailable / executor_posted_but_no_fresh_evidence.

---

## 회장 §1~§5 1:1 어셀션

### §1 runner (anu_v2/owner_trigger_only.py)
- `RESULT_PENDING` import: anu_v2/owner_trigger_audit 에서 import (line 47).
- http_post 직전 `txn.record(PENDING)`: trigger_gemini_review 5c 단계 (line ~250).
- body exact match (`/gemini review`): `COMMENT_BODY` 상수 + `assert_endpoint_allowed`.
- head fail-closed: `validate_decision(current_head_actual=...)` E_HEAD_MISMATCH.
- atomic dedupe: `audit.transaction()` sidecar lock + 본 파일 flock 2 단.
- forbidden endpoints hard-block: 11 패턴 정적 + dynamic 검사 + merge/approve/close/reopen stubs.
- result enum: POSTED / DEDUPED / FAILED / PENDING — 모두 import + 본 모듈 활용.

### §2 audit (anu_v2/owner_trigger_audit.py)
- append-only jsonl: `open(self._path, "a")` 만 사용.
- sidecar lock (`.lock`) + audit 본 파일 flock 2 단.
- transaction context manager: `OwnerTriggerAudit.transaction()` → `_AtomicTriggerTransaction`.
- RESULT_PENDING sentinel: 상수 정의 + caller 가 record(PENDING).
- exception 발생 시 FAILED 기록: owner_trigger_only 의 `except Exception as exc` 블록.
- **bounded/reverse scan**: `_iter_rows_reverse(max_rows=DEDUPE_SCAN_MAX_ROWS=512)` 도입.
  `_has_posted` / `_has_active_trigger` 가 reverse scan 사용 (정적 grep 으로 검증 — fixture #2).
- crash-safety fail-closed: PENDING 기록 후 process crash → 다음 runner 가 `_has_active_trigger`
  에서 PENDING 감지 → DEDUPED 판정 (fixture `test_pending_record_persists_after_simulated_crash`,
  `test_head_changed_subsequent_call_does_not_post_if_already_pending`).

### §3 executor integration (anu_v2/merge_queue_executor.py)
- minimal patch — 기존 evaluate flow 변경 0, 새 method 추가만.
- `GEMINI_STALE_ON_HEAD` 감지: `detect_gemini_stale_on_head(pr, gemini_review_commit_id)`.
- `OWNER_TRIGGER_REQUIRED` decision 생성: `emit_owner_trigger_decision(task_id, pr, decision_dir)`
  → decision.json v1 + `.owner-trigger.requested` marker.
- runner 호출 hook: `orchestrate_owner_trigger_for_stale_pr(..., owner_trigger_runner=...)` callable
  주입 (OwnerTriggerOnly 직접 import 0 — 분리 원칙).
- marker 확인: posted / failed / gemini-fresh-detected / posted-but-no-fresh-evidence.
- fresh polling: `detect_fresh_gemini_review(pr, latest_gemini_review_commit_id)`.
- auto-resume: fresh 도착 시 `mark_gemini_fresh_detected` → outcome.passed True → executor flow 재개.
- 24h+ 미도착 escalation: `record_no_fresh_evidence_after_post` → ESCALATED + auto-resume 0.

### §4 file evidence
- decision schema: `anu_v2.owner_trigger_decision.v1` (8 PASS 조건 정적 검증).
- audit jsonl: `memory/events/owner-trigger-audit.jsonl` (append-only, 영구).
- 5종 marker (전부 `merge_queue_executor` 가 생성):
  1. `<task>.owner-trigger.requested` — emit_owner_trigger_decision.
  2. `<task>.owner-trigger.posted` — record_owner_trigger_outcome(POSTED).
  3. `<task>.owner-trigger.failed` — record_owner_trigger_outcome(FAILED).
  4. `<task>.gemini-fresh-detected` — mark_gemini_fresh_detected.
  5. `<task>.posted-but-no-fresh-evidence` — record_no_fresh_evidence_after_post.
- evidence file: `<task>.owner_trigger_decision.json` (decision v1 schema 1:1).

### §5 regression fixtures (6 신규)
1. `test_owner_trigger_bootstrap_gap_pr105.py` (5 tests) — PR #105 head 08f2d29c vs stale 1e907722
   review 감지 → GEMINI_STALE_ON_HEAD + OWNER_TRIGGER_REQUIRED decision 어셀션.
2. `test_owner_trigger_fresh_medium_3_2554plus1.py` (9 tests) — Gemini fresh medium 3건 (§1~§2)
   충족 정적 + 동적 어셀션.
3. `test_owner_trigger_dedupe_same_head.py` (5 tests) — 동일 (pr, head) 반복 호출 시 2번째 DEDUPED.
4. `test_owner_trigger_head_changed_fail_closed.py` (4 tests) — head mismatch → 즉시 FAILED +
   PENDING 잔존 시 새 runner 가 DEDUPED.
5. `test_owner_trigger_token_unavailable.py` (4 tests) — token 부재 → TokenBoundaryViolation +
   audit 0 + http_post 0.
6. `test_executor_posted_but_no_fresh_evidence.py` (13 tests) — posted/failed marker, fresh 감지,
   24h+ 미도착 escalation, orchestration end-to-end.

---

## L1 스모크테스트 결과 (필수)

- **서버 재시작**: 해당없음 (라이브러리/모듈 수정. 실행 서버 X — actual_owner_token_usage=0,
  actual_github_comment_writes=0).
- **API 응답 확인**: 해당없음 (live pilot 0 — 회장 §11 명시).
- **스크린샷**: 해당없음 (UI 변경 0).
- **py_compile**: `python -m py_compile anu_v2/owner_trigger_only.py anu_v2/owner_trigger_audit.py
  anu_v2/owner_trigger_decision.py anu_v2/merge_queue_executor.py` → exit 0 (✅).
- **pytest anu_v2/tests**: 262 PASS (PR #105 baseline 95 + 6 신규 fixture 35 + 기존 anu_v2 other suite 132).
- **pytest tests/regression**: 603 PASS — 기존 regression 회귀 0.
- **forbidden path 어셀션**: PR #98~#105 branch 변경 0, scripts/dashboard/dispatch/team_prompts/
  .github/.env 변경 0, 다른 anu_v2 modules (worktree_cleanup, post_merge_smoke_runner,
  auto_gemini_triage, critical_escalation_reporter, replacement_pr_runner) 변경 0.
- **PR head SHA unchanged 어셀션**: 4e8f89795ab1 (#104) / 08f2d29ccb14 (#105) — git rev-parse 결과
  동일 유지 (rebase / force-push 흔적 0).

---

## effective diff == expected_files 1:1 어셀션

총 25 파일:

### code (4)
1. anu_v2/merge_queue_executor.py (modify — §3 minimal patch)
2. anu_v2/owner_trigger_only.py (new — §1 fix 반영)
3. anu_v2/owner_trigger_audit.py (new — §2 bounded scan 반영)
4. anu_v2/owner_trigger_decision.py (new — PR #105 baseline 그대로)

### tests carry-over (7)
5-11. test_owner_trigger_{only,decision_schema,dedupe,security_boundaries,merge_path_separation,
   race_fix,concurrency}_2554/2554plus1.py — PR #105 baseline 95 tests carry-over.
   (체크: PENDING semantic 으로 5개 test 의 row count assertion 만 업데이트.)

### tests new (6)
12-17. test_{owner_trigger_bootstrap_gap_pr105, owner_trigger_fresh_medium_3_2554plus1,
   owner_trigger_dedupe_same_head, owner_trigger_head_changed_fail_closed, owner_trigger_token_unavailable,
   executor_posted_but_no_fresh_evidence}.py — 35 신규 fixture tests.

### evidence (4)
18-21. memory/events/task-2554+2.{dispatch-decision, replacement-lineage, gemini_triage_decision,
   effective-diff}.json

### 3 docs (3)
22-24. memory/plans/tasks/task-2554+2/{plan, context-notes, checklist}.md

### report (1)
25. memory/reports/task-2554+2.md (본 문서)

---

## 3 Step Why (G1 self-review)

- 1st: PR #105 가 §1~§5 미충족이고 chain 정책상 자체 보완 금지 → 별도 PR 필요.
- 2nd: 별도 PR 안에서 5 layer 통합을 단일 단위로 박제 (Critical 7 #3 scope expansion 0).
- 3rd: replacement 만으로 §1~§2 보완은 §3~§5 capability 부재 → task-2554+3 chain 진입 위험.
  본 task 가 5 layer 통합 완성으로 capability 결함 박제 0 + chain 정책 보존 동시 달성.

---

## 모델 사용 기록

- 팀장 (마르둑, Opus 4.7 1M): 설계 / 분배 / 검토 / 통합 — 직접 코딩 포함 (Lv.4 보안 critical 특성상
  PII/token 흔적 0 어셀션 필요. Sonnet 위임 시 안전 마진 적은 PII guard 흔적 검증 비용이 더 큼).
- 외부 검증: Codex companion (gpt-5.4) — G1 gate (2회 호출: workspace_root_fallback + worktree),
  3 actionable feedback (orchestration method, report file, gemini-fresh-detected marker) 모두 반영.

---

## chain 정책 준수 어셀션

- 자동 task-2554+3 발행: 0회 (코드 / 스크립트 / 마커 어디서도 trigger X).
- 본 PR Gemini fresh review 도착 후 same-PR push: 회장 결정 대기 (push 0 박제).
- 2nd OWNER bootstrap trigger 요청: 0회 (코드 레벨 hard-block, OWNER 권한 일반 impersonation 0).
- BOT_GITHUB_TOKEN identity squash merge 외 머지 경로: 0 (admin override / force / rebase 시도 0).

---

## 후속 (회장 §명시)

task-2554+2 MERGED 후:
1. PR #105 close-safe audit (별도 read-only audit task).
2. PR #105 close (회장 결정 후 별도 cron).
3. PR #104 close-safe audit + close 가능 시점 도래.
4. 다음 우선순위: PR #103 update-branch + capability live pilot / task-2555 dispatch /
   task-2550+1 dispatch / G4 Task B 발행.

---

## 발사 시점 박제

★ 5 layer 통합 완성 + 262 anu_v2 tests PASS + 603 regression tests PASS + forbidden 0 +
PR #104/#105 head unchanged + chain 정책 100% 준수 + 외부 Codex 3 feedback 반영 완료.

PR 생성 + Gemini fresh review 대기 + CI all SUCCESS + BOT squash merge 단계로 진입 가능 상태.
