<!-- task=m-agent-readable-repository-refactor/05_http_provider_adapters plan=1 tag=REVIEW_REFACTOR_ADAPTER_HTTP -->

# HTTP provider adapter 회귀 복구 계획

## 이 파일을 읽는 구현 에이전트에게

코드와 회귀 테스트를 수정한 뒤 고정 검증을 실행하고 `CODE_REVIEW-*-G??.md`의 구현 소유 섹션에 실제 출력과 변경 내용을 채운다. active 파일은 유지하고 리뷰 준비 상태만 보고한다. 선택된 Milestone의 `구현 잠금 > 결정 필요`가 구현을 막을 때만 review stub의 `사용자 리뷰 요청`을 채우고 중단한다. 직접 사용자 질문, `request_user_input`, `USER_REVIEW.md` 작성, 로그 archive, `complete.log` 작성은 금지한다. 환경·서비스·증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

리뷰에서 OpenAI-compatible model fallback과 vLLM typed `tools` 전달이 분해 과정에서 회귀한 것을 확인했다. vLLM reasoning delta 추가는 실행 코드에만 있고 두 field alias와 event 순서를 검증하지 않는다. 또한 fake vLLM full-cycle은 HTTP 400으로 실패했으므로, Node adapter 범위를 넘지 않는 선에서 실제 출력을 다시 기록해야 한다.

## 사용자 리뷰 요청 흐름

구현 중 결정 차단은 active review stub의 `사용자 리뷰 요청` 섹션에만 기록한다. 직접 사용자 질문은 금지하며, `USER_REVIEW.md` 생성 여부는 code-review가 판단한다.

## Archive Evidence Snapshot

- 이전 plan: `agent-task/m-agent-readable-repository-refactor/05_http_provider_adapters/plan_local_G07_0.log`
- 이전 review: `agent-task/m-agent-readable-repository-refactor/05_http_provider_adapters/code_review_local_G07_0.log`
- 판정: FAIL; Required 4건, Suggested/Nit 없음. vLLM stream의 고아 `ProbeProvider` 주석은 리뷰에서 제거했다.
- 영향 파일: `apps/node/internal/adapters/openai_compat/stream.go`, `apps/node/internal/adapters/openai_compat/execute_test.go`, `apps/node/internal/adapters/vllm/stream.go`, `apps/node/internal/adapters/vllm/vllm.go`, `apps/node/internal/adapters/vllm/vllm_test.go`.
- 검증 근거: 두 adapter 패키지와 `go test ./apps/node/...`, `make readability-audit`, `gofmt`, `git diff --check`는 통과했다. `GOCACHE=/tmp/iop-go-cache IOP_VLLM_MODE=fake ./scripts/e2e-openai-vllm.sh`는 `curl: (22) ... 400`으로 실패했다.
- 추가 확인이 필요할 때만 위 두 `.log`를 읽는다. `agent-task/archive/**`를 탐색하지 않는다.

## 분석 결과

### 읽은 파일

- `apps/node/internal/adapters/openai_compat/stream.go`
- `apps/node/internal/adapters/openai_compat/execute_test.go`
- `apps/node/internal/adapters/vllm/stream.go`
- `apps/node/internal/adapters/vllm/vllm.go`
- `apps/node/internal/adapters/vllm/vllm_test.go`
- `agent-test/local/rules.md`, `agent-test/local/node-smoke.md`
- `agent-ops/rules/project/domain/node/rules.md`, `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/skills/project/e2e-smoke/SKILL.md`, `scripts/e2e-openai-vllm.sh`

### SDD 기준

선택 Milestone은 `SDD: 불필요`이다. 같은 package의 동작 보존형 분해이며 API/proto/config schema를 바꾸지 않는다는 기존 사유를 유지한다.

### 테스트 환경 규칙

`test_env=local`; `agent-test/local/rules.md`와 node profile을 읽었다. `go test -count=1 ./apps/node/...`, 변경 package test, gofmt을 적용한다. HTTP adapter 실행 경로는 testing rule과 e2e-smoke 기준상 full-cycle이 필요하므로 fake vLLM script를 사용한다. local runner에서 `/config/.cache/go-build` 권한 오류가 있어 full-cycle은 `GOCACHE=/tmp/iop-go-cache`로 실행한다.

### 테스트 커버리지 공백

- OpenAI-compatible input model fallback: 미검증.
- vLLM JSON-serializable typed tools: 미검증.
- vLLM `reasoning_content`와 `reasoning`의 reasoning-delta 및 event order: 미검증.
- 실제 Edge–Node provider tunnel: 실행했으나 HTTP 400으로 실패; PASS 근거 없음.

### 심볼 참조

공개/패키지 외 심볼 변경 없음. `vllmChatRequest`는 package-private이며 `apps/node/internal/adapters/vllm` 내부에서만 사용한다.

### 분할 판단

기존 split task `05_http_provider_adapters`의 follow-up이다. 네 항목은 같은 두 adapter의 request/stream contract와 하나의 검증 실패에 결합되어 있어 새 sibling 분할은 이득이 없다. 이 task 디렉터리에 한 plan/review pair만 둔다.

### 범위 결정 근거

Node HTTP adapter와 해당 단위 테스트, 검증 출력만 수정한다. `apps/edge/**` 및 fake vLLM script의 HTTP 400 원인 수정은 이 task 범위 밖이다. 재현 시 출력만 보존하고, 소유자가 확인되기 전에는 Edge 코드를 변경하지 않는다.

### 빌드 등급

`cloud-G07`: unit tests 이후 실제 Edge–Node smoke가 실패했으며, 코드 회귀 복구와 통합 실패 소유 분리가 함께 필요하다.

## 구현 체크리스트

- [ ] OpenAI-compatible `Execute`의 input model fallback을 복원하고 request body를 검증하는 회귀 테스트를 추가한다.
- [ ] vLLM `tools` 전달의 runtime type 제한을 제거하고 `[]map[string]any` 입력을 보존하는 회귀 테스트를 추가한다.
- [ ] vLLM reasoning delta의 두 provider alias와 start/reasoning/content/complete 순서를 검증한다.
- [ ] fake vLLM Edge–Node full-cycle을 전용 Go cache로 재실행하고 PASS 출력 또는 HTTP 400의 명시적 blocker evidence를 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REFACTOR_ADAPTER_HTTP-1] OpenAI-compatible input model fallback 복원

문제: `apps/node/internal/adapters/openai_compat/stream.go:33`은 target이 비면 input model 대신 빈 target을 다시 대입한다.

해결 방법:

```go
// before: stream.go:33
if model == "" {
	model = spec.Target
}

// after
if model == "" {
	model = stringInput(spec.Input, "model")
}
```

수정 파일 및 체크리스트:

- [ ] `apps/node/internal/adapters/openai_compat/stream.go`: 기존 input model fallback을 복원한다.
- [ ] `apps/node/internal/adapters/openai_compat/execute_test.go`: `Target` 없이 input `model`만 준 요청이 provider body로 전달됨을 검증한다.

테스트 작성: `TestOpenAICompatExecuteUsesInputModelWhenTargetEmpty`를 추가해 body의 `model` assertion과 successful completion을 확인한다.

중간 검증: `GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/node/internal/adapters/openai_compat`가 통과해야 한다.

### [REVIEW_REFACTOR_ADAPTER_HTTP-2] vLLM typed tools 전달 보존

문제: `apps/node/internal/adapters/vllm/stream.go:50`과 `vllm.go:16`이 `tools`를 `[]any`로 한정해 기존 `any` request payload를 삭제한다.

해결 방법: `vllmChatRequest.Tools`를 `any`로 되돌리고 `spec.Input["tools"]`를 type assertion 없이 할당한다.

수정 파일 및 체크리스트:

- [ ] `apps/node/internal/adapters/vllm/vllm.go`: `Tools any`를 복원한다.
- [ ] `apps/node/internal/adapters/vllm/stream.go`: `[]any` assertion을 제거한다.
- [ ] `apps/node/internal/adapters/vllm/vllm_test.go`: typed slice가 JSON request의 `tools` 배열에 남는지 검증한다.

테스트 작성: `TestVllmExecutePreservesTypedTools`를 추가해 `[]map[string]any` 입력과 직렬화된 함수 tool을 assertion한다.

중간 검증: `GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/node/internal/adapters/vllm`가 통과해야 한다.

### [REVIEW_REFACTOR_ADAPTER_HTTP-3] vLLM reasoning-delta 회귀 테스트

문제: `apps/node/internal/adapters/vllm/stream.go:160`의 새 reasoning event 변환은 테스트로 보호되지 않는다.

해결 방법: SSE fixture에 `reasoning_content`, `reasoning`, content를 순서대로 넣고 emitted event type/delta/order를 assertion한다.

수정 파일 및 체크리스트:

- [ ] `apps/node/internal/adapters/vllm/vllm_test.go`: 두 alias의 precedence와 start/reasoning/content/complete event 순서를 검증한다.

테스트 작성: table-driven `TestVllmExecuteStreamsReasoningDeltas`를 추가한다. `reasoning_content`가 있을 때 `reasoning`보다 우선함도 포함한다.

중간 검증: `GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/node/internal/adapters/vllm`가 통과해야 한다.

### [REVIEW_REFACTOR_ADAPTER_HTTP-4] 실제 HTTP adapter 경로 증거 복구

문제: fake vLLM full-cycle이 Node provider tunnel 수신 뒤 HTTP 400으로 종료했다.

해결 방법: Node adapter 변경 후 동일 command를 전용 cache로 한 번 실행한다. 실패 시 `IOP_VLLM_KEEP_TMP=1`로 focused rerun하고 edge/node/fake provider output의 경로와 실제 stdout/stderr를 review stub에 기록한다. Edge 구현은 수정하지 않는다.

수정 파일 및 체크리스트:

- [ ] 코드 변경 없음; 실행 결과와 blocker 소유 범위를 review stub에 기록한다.

테스트 작성: skip. 임시 fake provider를 포함한 기존 full-cycle script를 사용한다.

중간 검증: `GOCACHE=/tmp/iop-go-cache IOP_VLLM_MODE=fake ./scripts/e2e-openai-vllm.sh`가 통과하거나, 실패 시 focused rerun evidence가 남아야 한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `apps/node/internal/adapters/openai_compat/stream.go` | REVIEW_REFACTOR_ADAPTER_HTTP-1 |
| `apps/node/internal/adapters/openai_compat/execute_test.go` | REVIEW_REFACTOR_ADAPTER_HTTP-1 |
| `apps/node/internal/adapters/vllm/vllm.go` | REVIEW_REFACTOR_ADAPTER_HTTP-2 |
| `apps/node/internal/adapters/vllm/stream.go` | REVIEW_REFACTOR_ADAPTER_HTTP-2 |
| `apps/node/internal/adapters/vllm/vllm_test.go` | REVIEW_REFACTOR_ADAPTER_HTTP-2, REVIEW_REFACTOR_ADAPTER_HTTP-3 |

## 최종 검증

```bash
gofmt -d apps/node/internal/adapters/openai_compat/stream.go apps/node/internal/adapters/openai_compat/execute_test.go apps/node/internal/adapters/vllm/stream.go apps/node/internal/adapters/vllm/vllm.go apps/node/internal/adapters/vllm/vllm_test.go
GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm
GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/node/...
GOCACHE=/tmp/iop-go-cache IOP_VLLM_MODE=fake ./scripts/e2e-openai-vllm.sh
make readability-audit
git diff --check
```

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
