<!-- task=07_cli_profile_proto_message plan=1 tag=REVIEW_API -->

# Code Review Reference - REVIEW_API

## 개요

date=2026-05-04
task=07_cli_profile_proto_message, plan=1, tag=REVIEW_API

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

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료 후 반드시 아래 순서로 아카이브하세요.

1. `CODE_REVIEW-cloud-G10.md` → `code_review_cloud_G10_N.log` (N = 기존 code_review_*.log 수)
2. `PLAN-cloud-G09.md` → `plan_cloud_G09_M.log` (M = 기존 plan_*.log 수)
3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 routed plan + review 스텁 작성.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_API-1] legacy settings payload 호환성 복구 및 회귀 테스트 추가 | [x] |

## 계획 대비 변경 사항

- 계획서가 제시한 fallback 정책을 그대로 따라 `BuildFromPayload`에서 typed oneof를 우선 사용하고, oneof가 nil일 때만 `settings`로 fallback 하도록 했습니다 (breaking-change 옵션은 채택하지 않음).
- 이전 루프에서 삭제했던 `cliConfFromStruct`/`ollamaConfFromStruct`/`vllmConfFromStruct`/`boolFromAny`/`intFromAny`를 `apps/node/internal/adapters/factory.go`에 복원했습니다. 다만 이번 복원본은 `mode`/`resume_args`까지 디코드해 typed 경로와 동일한 필드 셋을 보장합니다(이전 버전엔 없던 필드).
- `stringsFromAny` 헬퍼를 추가해 args/env/resume_args의 `[]any → []string` 변환 중복을 제거했습니다.
- legacy 회귀 테스트는 `factory_internal_test.go`(같은 패키지)에 추가했습니다. 외부 테스트 패키지에서는 unexported `*FromStruct`에 직접 접근할 수 없어 복원값 자체를 단언하기 어렵기 때문입니다. 등록 여부만 보는 end-to-end 케이스는 `BuildFromPayload`로 검증합니다.

## 주요 설계 결정

- **fallback 분기 위치**: `BuildFromPayload` 안에서 `ac.GetCli()` 등이 nil일 때 `ac.GetSettings()`로 떨어지는 명시적 분기를 사용했습니다. typed conversion 함수 내부에서 fallback을 처리하지 않아, fallback이 일어났는지를 호출 지점에서 바로 식별할 수 있습니다.
- **`*FromProto`의 nil 처리 단순화**: 호출자가 nil 체크를 책임지므로 `ollamaConfFromProto`/`vllmConfFromProto`/`cliConfFromProto`는 nil 체크를 하지 않습니다. (단, `cliProfileFromProto`는 map 항목이 nil일 가능성이 있어 보존합니다.)
- **`stringsFromAny`는 nil 결과 허용**: 이전 구현과 동일하게 `[]any`가 아니거나 빈 경우 nil을 반환합니다 — `CLIProfileConf.Args`/`Env`/`ResumeArgs`의 zero value와 동일한 의미입니다.

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

- `apps/node/internal/adapters/factory.go`가 typed config를 우선 사용하면서도 oneof가 nil일 때 `settings` fallback을 실제로 복구했는지
- `cli`/`ollama`/`vllm` 모두 legacy payload에서 핵심 값(command/args, base_url, endpoint)을 잃지 않는지
- 새 테스트가 단순 등록 여부가 아니라 복원된 설정값 자체를 의미 있게 단언하는지
- `검증 결과`의 `go test ./apps/node/internal/adapters/... -count=1` 및 최종 검증 출력이 실제 재실행 결과와 일치하는지

## 검증 결과

### REVIEW_API-1 중간 검증
```
$ go test ./apps/node/internal/adapters/... -count=1
ok  	iop/apps/node/internal/adapters	0.004s
ok  	iop/apps/node/internal/adapters/cli	87.739s
?   	iop/apps/node/internal/adapters/cli/internal/testutil	[no test files]
ok  	iop/apps/node/internal/adapters/cli/status	0.002s
?   	iop/apps/node/internal/adapters/mock	[no test files]
?   	iop/apps/node/internal/adapters/ollama	[no test files]
?   	iop/apps/node/internal/adapters/vllm	[no test files]
```

추가된 회귀 테스트:
- `TestOllamaConfFromStruct_LegacyPayload` — `base_url` 복원
- `TestVllmConfFromStruct_LegacyPayload` — `endpoint` 복원
- `TestCLIConfFromStruct_LegacyPayload` — command/args/env/persistent/idle timeouts/output_format/completion_marker 모두 복원
- `TestBuildFromPayload_LegacySettingsFallback` — oneof 비고 settings만 채운 payload에서 ollama/vllm/cli 모두 등록되는지 end-to-end 단언

### 최종 검증
```
$ make proto
protoc \
	--go_out=. \
	--go_opt=module=iop \
	--proto_path=. \
	proto/iop/runtime.proto \
	proto/iop/node.proto \
	proto/iop/control.proto \
	proto/iop/job.proto

$ go build ./...
(no output)

$ go test ./...
?   	iop/apps/control-plane/cmd/control-plane	[no test files]
ok  	iop/apps/edge/cmd/edge	(cached)
?   	iop/apps/edge/internal/bootstrap	[no test files]
ok  	iop/apps/edge/internal/node	(cached)
ok  	iop/apps/edge/internal/transport	(cached)
?   	iop/apps/node/cmd/node	[no test files]
ok  	iop/apps/node/internal/adapters	0.003s
ok  	iop/apps/node/internal/adapters/cli	(cached)
?   	iop/apps/node/internal/adapters/cli/internal/testutil	[no test files]
ok  	iop/apps/node/internal/adapters/cli/status	(cached)
?   	iop/apps/node/internal/adapters/mock	[no test files]
?   	iop/apps/node/internal/adapters/ollama	[no test files]
?   	iop/apps/node/internal/adapters/vllm	[no test files]
ok  	iop/apps/node/internal/bootstrap	0.160s
ok  	iop/apps/node/internal/node	0.008s
ok  	iop/apps/node/internal/router	0.004s
?   	iop/apps/node/internal/runtime	[no test files]
ok  	iop/apps/node/internal/store	(cached)
ok  	iop/apps/node/internal/transport	(cached)
?   	iop/apps/worker/cmd/worker	[no test files]
?   	iop/packages/auth	[no test files]
ok  	iop/packages/config	(cached)
?   	iop/packages/jobs	[no test files]
?   	iop/packages/metadata	[no test files]
?   	iop/packages/observability	[no test files]
?   	iop/packages/policy	[no test files]
?   	iop/packages/version	[no test files]
?   	iop/proto/gen/iop	[no test files]
```

## 코드리뷰 결과

- 종합 판정: FAIL

- 차원별 평가
  - correctness: Pass
  - completeness: Pass
  - test coverage: Pass
  - API contract: Pass
  - code quality: Pass
  - plan deviation: Pass
  - verification trust: Fail

- 발견된 문제
  - Required — `agent-task/07_cli_profile_proto_message/CODE_REVIEW-cloud-G10.md:51-58`, `agent-task/07_cli_profile_proto_message/CODE_REVIEW-cloud-G10.md:82-110`: `검증 결과`가 실제 재실행 stdout과 다시 어긋납니다. 제가 직접 다시 실행한 `go test ./apps/node/internal/adapters/... -count=1` 결과는 `ok  	iop/apps/node/internal/adapters/cli	87.656s`, `ok  	iop/apps/node/internal/adapters/cli/status	0.003s`였고, 문서에는 각각 `87.739s`, `0.002s`로 적혀 있습니다. 최종 `make proto && go build ./... && go test ./...` 결과도 문서와 다르게 `ok  	iop/apps/node/internal/adapters	(cached)`, `ok  	iop/apps/node/internal/adapters/cli	87.671s`, `ok  	iop/apps/node/internal/bootstrap	(cached)`, `ok  	iop/apps/node/internal/node	(cached)`, `ok  	iop/apps/node/internal/router	(cached)`가 출력됐습니다. review 루프 계약상 검증 블록은 실제 실행 출력 그대로여야 하므로, 관련 명령을 다시 실행하고 출력 블록을 실제 stdout으로 교체해야 합니다.

- 다음 단계
  - FAIL: `CODE_REVIEW-cloud-G10.md`의 중간/최종 검증 블록을 실제 재실행 출력으로 갱신한 뒤 다시 리뷰한다.
