<!-- task=m-agent-readable-repository-refactor/03+02_openai_provider_tools plan=0 tag=TEST -->

# Code Review Reference - TEST

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves.
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.

## 개요

date=2026-07-16
task=m-agent-readable-repository-refactor/03+02_openai_provider_tools, plan=0, tag=TEST

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/automation-runtime-bridge/milestones/agent-readable-repository-refactor.md)
- Task ids:
  - `openai-tests`: OpenAI 서버 테스트를 기능 시나리오별 파일과 feature-local double로 분해
- Completion mode: check-on-pass

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.

1. 판정을 append한다.
2. `CODE_REVIEW-local-G08.md` → `code_review_local_G08_N.log`, `PLAN-local-G08.md` → `plan_local_G08_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-agent-readable-repository-refactor/03+02_openai_provider_tools/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 연결된 Milestone 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다.
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [TEST-1] provider/tool 시나리오 분리와 server_test.go 제거 | [x] |

## 구현 체크리스트

- [x] [TEST-1] provider tunnel/pool, policy, tool validation, observability 테스트를 분리하고 원본 제거, Test 226개, 1,000줄 초과 없음, package test 통과를 함께 검증한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_{review_lane}_GNN_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 연결된 Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.

## 계획 대비 변경 사항

- 계획의 6개 파일에 더해 `apps/edge/internal/openai/provider_tunnel_auth_test.go`(274줄)를 추가했다. 계획대로 chat/Responses tunnel 시나리오를 `provider_tunnel_test.go` 하나에 모으면 provider auth forwarding 그룹(약 254줄)까지 포함되어 1,041줄로 최종 LOC gate(1,000줄 이하)를 위반한다. provider auth 검증은 tunnel dispatch 안에서도 독립된 책임이므로 tunnel sibling 파일로 분리했다. Test 이름과 assertion은 그대로 보존했다.
- 계획의 `수정 파일 요약`에 없는 `apps/edge/internal/openai/chat_handler_test.go`와 `apps/edge/internal/openai/chat_tool_synthesis_test.go`를 함께 수정했다. server_test.go 분리만 마친 시점에 최종 검증의 LOC gate가 선행 단계 산출물인 `chat_handler_test.go`(1,070줄)에서 실패했다(아래 `검증 결과`의 1차 LOC gate 출력 참조). LOC gate는 특정 파일이 아니라 `apps/edge/internal/openai/*_test.go` 전체 계약이므로, 새 파일을 만들지 않고 tool-call 합성/sentinel 테스트 그룹(기존 268~639행, 10개 Test와 helper 2개)을 같은 책임의 기존 파일 `chat_tool_synthesis_test.go`로 이동했다. 결과는 697줄 / 853줄이며 Test 이름과 본문은 변경하지 않았다.
- 검증 명령은 계획의 고정 계약을 그대로 사용했고 대체하지 않았다.

## 주요 설계 결정

- 분리 기준은 파일 크기 균등 분배가 아니라 provider 경로별 책임으로 잡았다. dispatch(catalog/pool dispatch 결정/legacy route/응답 표면), tunnel(raw passthrough byte identity, model echo rewrite, provider error, cancel/timeout/write-failure resilience, Responses tunnel 요청 형태), tunnel auth(provider auth header forwarding/필수 검증/inbound auth 분리), policy(generation/thinking/strict-output), tool validation(text tool call retry/schema), observability(metadata/log field), selection(mixed pool의 tunnel vs normalized 선택, unknown field 기반 선택)으로 나눴다.
- 각 파일은 원본 import block을 그대로 복사한 뒤 `gopls imports -w`로 미사용 import만 제거했다. 테스트 본문과 assertion은 이동만 하고 수정하지 않아 동작 보존을 유지했다.
- `server_test_support_test.go`의 공용 fake는 실제로 어떤 테스트도 설정하지 않는 dead field 4개(`submitErr`, `cancelErr`, `poolSubmitErr`, `poolTunnelFrames`)와 그 분기를 제거해 축소했다. `poolSubmitErr` 제거 과정에서 같은 함수 안에 중복으로 남아 있던 `if err == nil { err = tunnelErr }` 재적용 블록도 함께 정리했다. 실제로 사용되는 field(`submitErrAfter`, `tunnelErr`, `poolDispatchPath`, `poolSubmitResults`, snapshot accessor 계열)는 그대로 두었고, 파일은 664줄에서 642줄이 되었다.
- `server_test.go`에만 있던 local helper(`chatSidebandServer`, `chatProviderRouteServer`, `chatPassthroughServer`, `chatProviderAuthServer`, `staticOKTunnelFrames`, `responsesProviderTunnelServer`, `flakyResponseWriter`)는 support 파일로 올리지 않고, 각 helper를 쓰는 시나리오 파일에 feature-local로 두었다.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._

- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음

## 리뷰어를 위한 체크포인트

- 02 predecessor complete.log가 구현 전에 존재했는가.
- server_test.go가 제거되고 Test 226개가 보존되었는가.
- 모든 OpenAI test 파일이 1,000줄 이하이며 fake가 feature-local인가.

## 검증 결과

_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._

필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.

### 사전 baseline (분리 전 현재 checkout)
```
$ GOCACHE=/tmp/iop-go-build-cache go test -list '^Test' ./apps/edge/internal/openai | sed '/^ok[[:space:]]/d' | wc -l
226
```

### TEST-1 중간 검증
```
$ gofmt -w apps/edge/internal/openai/*_test.go
(no output)
$ test ! -e apps/edge/internal/openai/server_test.go
exit=0
$ [ "$(GOCACHE=/tmp/iop-go-build-cache go test -list '^Test' ./apps/edge/internal/openai | sed '/^ok[[:space:]]/d' | wc -l)" -eq 226 ]
count=226
exit=0
```

### 최종 검증

1차 실행에서 LOC gate가 선행 단계 산출물 `chat_handler_test.go`(1,070줄)에서 실패했다.

```
$ python3 -c "from pathlib import Path; p=Path('apps/edge/internal/openai'); bad=sorted((sum(1 for _ in f.open()),str(f)) for f in p.glob('*_test.go') if sum(1 for _ in f.open())>1000); print(bad); raise SystemExit(bool(bad))"
[(1070, 'apps/edge/internal/openai/chat_handler_test.go')]
exit=1
```

`계획 대비 변경 사항`에 기록한 대로 합성 테스트 그룹을 `chat_tool_synthesis_test.go`로 이동한 뒤 세 명령을 재실행했고 전부 통과했다.

```
$ GOCACHE=/tmp/iop-go-build-cache go test -count=1 ./apps/edge/internal/openai
ok  	iop/apps/edge/internal/openai	6.724s
exit=0
$ [ "$(GOCACHE=/tmp/iop-go-build-cache go test -list '^Test' ./apps/edge/internal/openai | sed '/^ok[[:space:]]/d' | wc -l)" -eq 226 ]
count=226
exit=0
$ python3 -c "from pathlib import Path; p=Path('apps/edge/internal/openai'); bad=sorted((sum(1 for _ in f.open()),str(f)) for f in p.glob('*_test.go') if sum(1 for _ in f.open())>1000); print(bad); raise SystemExit(bool(bad))"
[]
exit=0
```

### 분리 결과 파일 크기

```
$ wc -l apps/edge/internal/openai/*_test.go | sort -n
   127 apps/edge/internal/openai/input_estimator_test.go
   169 apps/edge/internal/openai/openai_auth_routes_models_test.go
   273 apps/edge/internal/openai/responses_handler_test.go
   274 apps/edge/internal/openai/provider_tunnel_auth_test.go
   291 apps/edge/internal/openai/log_safety_test.go
   296 apps/edge/internal/openai/identity_metering_test.go
   403 apps/edge/internal/openai/provider_observability_test.go
   435 apps/edge/internal/openai/workspace_metadata_test.go
   447 apps/edge/internal/openai/provider_dispatch_test.go
   457 apps/edge/internal/openai/cancellation_routes_test.go
   488 apps/edge/internal/openai/provider_policy_test.go
   570 apps/edge/internal/openai/provider_tool_validation_test.go
   642 apps/edge/internal/openai/server_test_support_test.go
   671 apps/edge/internal/openai/usage_metrics_test.go
   697 apps/edge/internal/openai/chat_handler_test.go
   844 apps/edge/internal/openai/provider_selection_test.go
   848 apps/edge/internal/openai/chat_stream_reasoning_test.go
   853 apps/edge/internal/openai/chat_tool_synthesis_test.go
   871 apps/edge/internal/openai/provider_tunnel_test.go
  9656 total
```

### predecessor 의존성 확인

```
$ ls agent-task/archive/2026/07/m-agent-readable-repository-refactor/02+01_openai_responses_workspace/
code_review_local_G03_2.log
code_review_local_G07_0.log
code_review_local_G07_1.log
complete.log
plan_local_G03_2.log
plan_local_G07_0.log
plan_local_G07_1.log
```

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

| Section | Owner | Note |
|---------|-------|------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
| Agent UI Completion | Mixed | Present only for plan-required agent-ui code work; implementing agent fills actual evidence, review agent applies `구현됨` status/evidence update on PASS and copies the section into `complete.log` |
| 구현 항목별 완료 여부 (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
| 구현 체크리스트 (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only; final checkbox is mandatory before saving |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless a selected Milestone `구현 잠금 > 결정 필요` item blocks implementation; do not ask the user directly during implementation; environment/secret/service blockers, generic scope changes, and evidence gaps are not user-review requests |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
| 검증 결과 (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `계획 대비 변경 사항` entry |
| 코드리뷰 결과 | Review agent appends | Not included in stub |

## 코드리뷰 결과

### 종합 판정

FAIL

### 차원별 평가

| 차원 | 평가 | 근거 |
|------|------|------|
| Correctness | Pass | HEAD 대비 226개 `Test*` 이름과 본문이 누락·추가·변경 없이 보존되었고 fresh package test가 통과했다. |
| Completeness | Fail | Roadmap Target이 요구하는 feature-local 또는 function-field 기반 test double 축소가 완료되지 않았고 시나리오 책임 배치 1건이 잘못되었다. |
| Test coverage | Pass | 기존 226개 테스트가 모두 남아 있고 `go test -count=1 ./apps/edge/internal/openai`가 통과했다. |
| API contract | Pass | production/API/wire/config 코드는 변경되지 않았고 기존 계약 테스트 본문도 보존되었다. |
| Code quality | Fail | 642줄 `server_test_support_test.go`에 normalized run, provider tunnel/pool, cancel, Ollama 제어가 하나의 package-wide fake로 남아 있다. |
| Implementation deviation | Fail | `provider_dispatch_test.go`의 pool dispatch 책임과 관계없는 일반 Chat request validation 테스트 이동이 계획 대비 변경 사항에 기록되지 않았다. |
| Verification trust | Pass | 리뷰어가 test count, 본문 보존, gofmt, LOC gate, fresh package test, `git diff --check`를 직접 재실행해 기록과 일치함을 확인했다. |

### 발견된 문제

- Required — `apps/edge/internal/openai/server_test_support_test.go:16`: `fakeRunService`가 20개 이상의 normalized/tunnel/pool/cancel/Ollama 상태를 보유하고, `SubmitProviderPool`/`buildPoolResult` 구현도 `apps/edge/internal/openai/server_test_support_test.go:161`부터 약 287줄을 차지한다. 미사용 필드 4개만 제거한 현재 구조는 Roadmap `openai-tests`의 “feature-local stub 또는 함수 필드 기반 test double로 축소”와 계획의 공용 fake 축소를 충족하지 못한다. normalized 기본 fake와 provider tunnel/pool 전용 fake를 분리하거나 interface method callback 필드 기반으로 바꿔 각 테스트가 필요한 행동만 설정하게 하고, 기존 테스트 이름·assertion·개수를 보존해야 한다.
- Required — `apps/edge/internal/openai/provider_dispatch_test.go:425`: `TestChatCompletionsRejectsUnsupportedFields` 는 provider catalog/pool dispatch를 사용하지 않고 normalized Ollama Chat 입력의 일반 validation만 검증한다. “기능 시나리오별 파일” 분해 목표와 `provider_dispatch_test.go = pool dispatch`라는 계획 책임을 어긴다. 함수 본문을 변경하지 말고 `chat_handler_test.go`로 옮기고 import/LOC/Test 226개를 재검증해야 한다.

### 다음 단계

- FAIL: 아카이브된 현 리뷰 근거를 인계한 `REVIEW_TEST` 후속 plan/review 루프에서 Required 2건을 보완한다.
