# task-2699 보고서 — OWNER_GEMINI_TRIGGER_CAPABILITY_WIRING

- 팀: dev1-team (헤르메스 팀장)
- Level: Lv.3 (보안 · OWNER_TRIGGER_ONLY_CAPABILITY doctrine)
- chair_authorization_id: `CHAIR-AUTH-TASK-2699-OWNER-GEMINI-TRIGGER-CAPABILITY-WIRING-20260527-JJONGS-IMPLEMENT-001`
- 완료 목표: **`OWNER_GEMINI_TRIGGER_CAPABILITY_WIRING_COMPLETE`**
- 작성: 2026-05-27 / head SHA: `361b169980728c53082a9575168f5cd689bfb30a`

---

## S — Situation

`owner_trigger_only` 코어(검증/dedupe/redaction/decision schema v1 8조건)는 origin/main에 반영됐으나, PR #157 GEMINI_HEAD_DRIFT unblock 중 capability gap이 실증됨:
- **실제 production http_post 구현체 0건** (test mock만 존재)
- **scheduler/daemon/runner entry point 부재** (executor_scheduler.py가 자인)
- **owner_gemini_trigger_router.py origin/main 미tracked** (.worktrees/* 에만 + orphan pyc)

→ ANU 단발 트리거 시 http_post 신규 작성 = ad-hoc = doctrine 위반.

## C — Complication

코어 로직 재작성은 금지(★ http_post wiring + entry point 만 추가). BOT_GITHUB_TOKEN fallback·COMMENT_BODY 외 body·merge/push/admin endpoint·token 평문 노출·live POST 전부 금지. 보안 게이트(Codex/마아트/로키/owasp) 통과 필수.

## Q — Question

코어를 무훼손으로 유지하면서, 정식 자동 trigger 경로를 production-ready로 완성하여 ad-hoc POST를 영구 제거하려면?

## A — Answer

코어의 주입 구조(`http_post`/`token_provider` Callable)를 그대로 활용하여 **분리 모듈**로 production 구현 + entry point를 추가. 코어 0줄 수정.

---

## 1. production http_post 구현체 (endpoint/method/urllib)

**파일**: `anu_v2/owner_trigger_http_post.py` (233줄, 신규)

- `make_production_http_post(*, api_base="https://api.github.com", dry_run=False, opener=None)` → `_http_post(method, path, body, headers) -> dict`
- **method**: POST 고정 (urllib.request.Request method="POST" 하드코딩)
- **endpoint**: `/repos/{owner}/{repo}/issues/{pr}/comments` 단일 허용
- **urllib**: `urllib.request.Request` + `urlopen` (timeout 30s, `opener` 주입 가능 → test 주입성)
- **방어적 재검증**: `_http_post` 진입 즉시 `assert_endpoint_allowed(method, path)` 재호출 (코어 + http_post 이중 hard-block). merge/pulls/git-refs/contents/admin endpoint 주입 시 `ForbiddenEndpointError`.
- **dry-run**: `dry_run=True` 시 네트워크 0, `{"dry_run":True,"status":0,"endpoint":path,"method":method}` 반환.
- **redaction**: 반환 dict `response`/예외 diagnostics에 `_redact_owner_response` 적용 (= `_redact_tokens` + broader prefix `ghp_/ghs_/gho_/ghu_/ghr_/github_pat_`). `OwnerTriggerHttpError` 메시지엔 status+endpoint만 (headers/token 미포함).
- `make_owner_trigger_token_provider(env)`: `OWNER_GEMINI_TRIGGER_TOKEN`만 조회. 부재/빈값 → `TokenBoundaryViolation` (fail-closed). BOT_GITHUB_TOKEN 등 fallback 0.

## 2. entry point 연결 (CLI + scheduler)

**파일**: `anu_v2/owner_trigger_entry.py` (487줄, 신규)

- `build_runner(...)`: `OwnerTriggerOnly` 생성 + production http_post/token_provider 주입 (미주입 시 production 폴백).
- `run_single(...)`: 단발 CLI 경로 — runner 생성 → `invoke_from_scheduler` → status 반환.
- `build_scheduler(...)` / `run_scheduler_cycle(...)`: scheduler 자동 경로 — production gh/git/pytest/audit runner로 `MergeQueueExecutor` + `ExecutorScheduler` 구성 → `run_one_cycle()`.
- `_gh_snapshot_provider(owner, repo)`: `gh pr list --state open --json` 기반 IdlePRSnapshot 공급 (production).
- `main(argv)`: argparse 서브커맨드 **`trigger`**(단발) + **`scan`**(scheduler) 둘 다. token/credential stdout/stderr 출력 0.
- gh/git subprocess env에서 `OWNER_GEMINI_TRIGGER_TOKEN` 제거 (F-4 하드닝 — /proc 노출 차단).

## 3. owner_gemini_trigger_router main 반영 정리 결과

- **규명**: router.py + 의존 2개(`gemini_evidence_freshness_checker.py`, `owner_gemini_trigger_router_audit.py`)가 24개 worktree에서 **byte-identical** (md5 `a60e3c7d...`). main 미tracked + orphan `.pyc` 잔존.
- **결론**: 정본 = worktree canonical, main 반영 **누락**. orphan pyc는 잔재.
- **조치**: byte-identical 정본 3종을 worktree(→main PR)에 반영(단일화), main의 orphan pyc 2개 제거. import/AST/py_compile OK. 의존은 stdlib + anu_v2 내부만(self-contained).

## 4. 검증 8 시나리오 결과

테스트: `anu_v2/tests/test_owner_trigger_http_post_wiring_2699.py` (493줄, 9 함수)

1. mock HTTP: decision 8조건 → POST **1회**, body=="/gemini review", endpoint==issues/{pr}/comments, status POSTED → **PASS**
2. dry-run: 실제 POST 0 (urlopen 예외 패치해도 통과) → **PASS**
3. dedupe: 동일 (pr,head) 2회 → 2번째 DEDUPED, POST 총 1회 → **PASS**
4. forbidden endpoint: merge/pulls/reviews + GET/DELETE → ForbiddenEndpointError → **PASS**
5. token unavailable: env 부재/BOT_GITHUB_TOKEN-only → TokenBoundaryViolation, POST 0 → **PASS**
6. redaction: 예외/audit에 raw token 0건 → **PASS**
7. entry point: run_single mock 주입 → POST 1회 POSTED + build_scheduler smoke → **PASS**
8. regression: 기존 owner_trigger test 전부 PASS → **PASS** (226 passed)

## 5. token 누출 0 evidence (grep)

- `grep -rEn "ghp_[A-Za-z0-9]{20}|ghs_...|github_pat_..."` 신규 2파일 → **0건** (실제 token 하드코딩 0)
- L1 dry-run 실행 후 audit JSONL: `token_hash_prefix`(sha256 앞 8자) + `token_value_logged:false`만, raw token(`ghp_SECRET...`) **0건**
- `OwnerTriggerHttpError` 메시지: status+endpoint만. headers/Authorization/Bearer 미포함.
- 하드닝 후 `_redact_owner_response`: gho_/ghu_/ghr_ 포함 전 prefix `***REDACTED***`.

## 6. 기존 owner_trigger_only regression PASS

`pytest anu_v2/tests/ -k "owner_trigger"` → **226 passed, 0 failed** (신규 9 포함). 코어 3파일(owner_trigger_only/decision/audit) git diff 무변경.

## 7. PR 번호 + head SHA

- 브랜치: `task/task-2699-dev1`
- head SHA: `361b169980728c53082a9575168f5cd689bfb30a`
- **PR: #158** (https://github.com/Jeon-Jonghyuk/dev_workspace/pull/158). merge_policy=no_merge_chair_approval_required → **자동 머지 안 함, 회장 결재 대기**. Gemini PR 리뷰 자동 트리거됨.

## 8. forbidden_action_count

**0** — merge/push/PR-merge/admin endpoint 호출 경로 추가 0건. 모든 forbidden endpoint는 ForbiddenEndpointError로 차단(코어+http_post 이중). live POST 0 (mock/dry-run only).

---

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

- **서버 재시작**: 해당없음 (subprocess/CLI 캐퍼빌리티 — 서버 데몬 아님)
- **API 응답 확인**: CLI entry point 실제 구동 — `python3 -m anu_v2.owner_trigger_entry trigger --dry-run --decision-path <실 decision.json> --owner Jeon-Jonghyuk --repo dev_workspace --current-head <40hex>` → stdout `POSTED`, exit 0, **네트워크 호출 0**. audit JSONL에 PENDING→POSTED 2 record 정상 생성, raw token 0건. `--help` → {trigger, scan} 서브커맨드 노출 확인.
- **스크린샷**: 해당없음 (CLI/백엔드 — UI 없음)

## 발견 이슈 및 해결

- **F-1 [Med]** redact gho_/ghu_/ghr_ prefix 미처리 → owner_trigger_http_post.py에 `_redact_owner_response` 로컬 broader-prefix redact 추가 (auto_gemini_triage 무수정). **해결**.
- **F-4 [Low]** gh subprocess env OWNER 토큰 상속 → gh/git runner env에서 제거. **해결**.
- **F-2 [Med]** audit JSONL OS레벨 변조 dedupe 우회 → owner_trigger_audit.py는 재사용·코어 무훼손 자산 + 파일기반 audit 일반 한계(본 wiring 결함 아님). **수용 위험**, 별도 하드닝 task 권고.
- **F-3 [Low]** validate_decision pr<=0 미검증 → `_build_post_comment_path` 2차 차단 존재(defense-in-depth). decision.py 코어 무훼손. **수용**.

## 게이트 결과

- **G1 설계**: 3문서 작성 + 3 Step Why A-B-C 일관 + Codex 사전검증 **PASS** (마아트 폴백) + sanitize(PII 1건 마스킹).
- **G2 구현**: 마아트 독립검증 10/10 PASS + 로키 레드팀(F-1/F-4 하드닝, F-2/F-3 수용) + owasp/token 하드코딩 0.
- **G3 머지**: g3_independent_verifier 실행 → (finish-task.sh QC와 함께 수행).

## 모델 사용 기록

- 헤르메스(팀장, Opus): 설계/분배/검토/통합/3문서/라우터 단일화/L1 스모크/보안 판정.
- 불칸(백엔드, Sonnet): http_post + entry point 구현, F-1/F-4 하드닝.
- 아르고스(테스터, Sonnet): 검증 8 시나리오 테스트.
- 마아트(횡단 QC, Sonnet): 독립 검증. 로키(횡단 DA, Sonnet): 보안 레드팀.
- haiku 미사용 (보안 중요 작업 — 전 팀원 Sonnet 이상).

## 생성/수정 파일

신규 6파일 (2426 insertions, 코어 0 수정):
- `anu_v2/owner_trigger_http_post.py` (233)
- `anu_v2/owner_trigger_entry.py` (487)
- `anu_v2/tests/test_owner_trigger_http_post_wiring_2699.py` (493)
- `anu_v2/owner_gemini_trigger_router.py` (640, 정본 반영)
- `anu_v2/gemini_evidence_freshness_checker.py` (258, 정본 반영)
- `anu_v2/owner_gemini_trigger_router_audit.py` (315, 정본 반영)
- (정리) main orphan pyc 2개 제거

## 수정 파일별 검증 상태

(경로는 worktree 기준 상대경로 — 미머지 브랜치 `task/task-2699-dev1`, 회장 결재 후 main 반영)

| 파일 | 변경 내용 | grep 검증 | 상태 |
|------|-----------|-----------|------|
| .worktrees/task-2699-dev1/anu_v2/owner_trigger_http_post.py | production urllib http_post + token_provider + redaction | grep "make_production_http_post" OK | verified |
| .worktrees/task-2699-dev1/anu_v2/owner_trigger_entry.py | entry point CLI 단발 + scheduler 구동 | grep "build_runner" OK | verified |
| .worktrees/task-2699-dev1/anu_v2/tests/test_owner_trigger_http_post_wiring_2699.py | 검증 8 시나리오 테스트 | grep "test_mock_http_post_posts_once" OK | verified |
| .worktrees/task-2699-dev1/anu_v2/owner_gemini_trigger_router.py | router 정본 main 반영 | grep "OwnerGeminiTriggerRouter" OK | verified |
| .worktrees/task-2699-dev1/anu_v2/gemini_evidence_freshness_checker.py | router 의존 정본 반영 | grep "check_gemini_evidence_fresh" OK | verified |
| .worktrees/task-2699-dev1/anu_v2/owner_gemini_trigger_router_audit.py | router 의존 정본 반영 | grep "OwnerGeminiTriggerRouterAudit" OK | verified |

## 머지 판단

- **머지 필요**: Yes (Lv.3, merge_policy=no_merge_chair_approval_required → 회장 결재)
- **브랜치**: `task/task-2699-dev1`
- **워크트리 경로**: `/home/jay/workspace/.worktrees/task-2699-dev1`
- **머지 의견**: 코어 무훼손, 회귀 226 passed, 보안 게이트 통과(마아트 PASS + 로키 하드닝), token 누출 0, live POST 0. 머지 적합. Gemini PR 리뷰 후 회장 결재.

## 비고

성공 조건 **`OWNER_GEMINI_TRIGGER_CAPABILITY_WIRING_COMPLETE`** 충족: production http_post + entry point(CLI 단발 + scheduler) 완성 → ad-hoc POST 영구 제거 경로 확보.

### finish-task.sh 실행 결과 (★ .done 환경적 블로커)
- QC: **PASS** (qc_result=WARN, 통과 — .qc-result + .qc-done 생성).
- 머지: 스킵 (no_merge_chair_approval — PR #158 회장 결재 대기).
- **.done 생성 차단**: GIT-GATE가 main의 미커밋 6파일을 감지(전부 본 task 무관 — task-2516+1/task-2470 소유, 내 세션 17:43 이전 16:16 산물):
  `dashboard/data/naver-sa-stats.json`, `memory/specs/.spec-state-cache.json`, `memory/specs/anu-system-spec.md`, `memory/specs/anu-system-spec-changelog.md`, `tests/regression/test_replacement_pr_runner_2510.py`, `utils/replacement_pr_runner.py`
- 본인 원칙 준수: ① 다른 팀/task 파일 수정 금지 → 6파일 미접촉. ② manual forgery 금지 → .done/.merge-done 수동 생성 안 함.
- **아누 조치 요청**(followup.txt): 6파일을 소유 task가 커밋/정리 후 `bash scripts/finish-task.sh task-2699 dev1` 재실행 → .done 생성. 내 산출물은 worktree(task/task-2699-dev1)에 4커밋·푸시 완료, main 반영 무손상.

## 세션 통계
- 총 도구 호출: 0회

