<!-- task=m-node-provider-first-config-surface/01_schema_source plan=1 tag=REVIEW_CONFIG -->

# Plan - REVIEW_CONFIG

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

`CODE_REVIEW-local-G06.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것까지가 구현이다. 최종 판정, log rename, `complete.log`, task archive는 code-review 전용이다. 선택된 Milestone의 `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 review stub의 `사용자 리뷰 요청` 섹션을 채우고 멈춘다. 직접 사용자에게 묻거나 `USER_REVIEW.md`를 만들지 않는다.

## 배경

첫 리뷰는 FAIL이다. provider-first schema 필드는 추가됐지만 `ValidateEdgeConfig`가 provider `type`과 실행 필드(`endpoint`, `base_url`, `command`)를 정확히 묶어 검증하지 않는다. 또한 `configs/edge.yaml` 상단 provider-pool 예시가 여전히 `adapters` 중복 작성을 새 배포 기본 경로처럼 설명해 SDD S01 evidence와 충돌한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/node-provider-first-config-surface.md`
- Task ids:
  - `schema-source`: provider-first Node config schema
- Completion mode: check-on-pass

## Archive Evidence Snapshot

- Archived plan: `agent-task/m-node-provider-first-config-surface/01_schema_source/plan_local_G06_0.log`
- Archived review: `agent-task/m-node-provider-first-config-surface/01_schema_source/code_review_local_G06_0.log`
- Verdict: FAIL
- Required summary:
  - `apps/edge/internal/edgevalidate/validate.go:76` accepts adapter-less providers when any one of `endpoint`, `base_url`, or `command` is set, even when the field does not match provider `type`.
  - `configs/edge.yaml:100` and `configs/edge.yaml:124` still describe adapter-backed provider-pool setup as the new-deploy/recommended path.
- Affected files:
  - `apps/edge/internal/edgevalidate/validate.go`
  - `apps/edge/internal/edgevalidate/validate_test.go`
  - `configs/edge.yaml`
  - `packages/go/config/config_test.go` if loader-level whitespace/normalization cases are added
- Verification evidence from previous loop:
  - `go test -count=1 ./packages/go/config ./apps/edge/internal/edgevalidate` passed.
  - `go test ./...` passed before review-only EOF blank-line cleanup.
  - `go run ./apps/edge/cmd/edge --config configs/edge.yaml config check` passed.
  - `git diff --check` is clean after review-only EOF blank-line cleanup.
- Narrow archive reread allowed if needed:
  - `agent-task/m-node-provider-first-config-surface/01_schema_source/plan_local_G06_0.log`
  - `agent-task/m-node-provider-first-config-surface/01_schema_source/code_review_local_G06_0.log`

## 구현 체크리스트

- [ ] `ValidateEdgeConfig`가 normalized provider `type`별 실행 필드를 엄격히 검증하고, wrong-field/unknown/whitespace type을 거부한다.
- [ ] provider-first valid/invalid table tests를 추가해 `openai_compat` 계열은 `endpoint`, `ollama`는 `base_url`, `cli`는 `command`가 필요함을 검증한다.
- [ ] `configs/edge.yaml` 상단 provider-pool 설명과 예시를 provider-first source of truth 기준으로 정리하고 `adapters` 중복 작성은 legacy/compat 설명으로 낮춘다.
- [ ] `go test -count=1 ./packages/go/config ./apps/edge/internal/edgevalidate`를 실행한다.
- [ ] `go run ./apps/edge/cmd/edge --config configs/edge.yaml config check`와 `git diff --check`를 실행한다.
- [ ] `go test ./...`를 실행하거나, 실행하지 못하면 정확한 차단 사유를 `CODE_REVIEW-local-G06.md`에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

### [REVIEW_CONFIG-1] Type-Specific Provider-First Validation

#### 문제

`apps/edge/internal/edgevalidate/validate.go:76`의 adapter-less branch는 provider-first 실행 필드 존재 여부를 `p.Endpoint == "" && p.BaseURL == "" && p.Command == ""`로만 판단한다. 이 때문에 `type: cli`에 `endpoint`만 있거나 `type: ollama`에 `command`만 있는 잘못된 config가 통과한다.

#### 해결 방법

`config.NormalizeProviderType(p.Type)` 결과를 기준으로 type별 필수 필드를 검사한다.

- `openai_compat`, `openai_api`, `vllm`, `lemonade`, `sglang`: `endpoint` 필수
- `ollama`: `base_url` 필수
- `cli`: `command` 필수
- unknown 또는 trim 후 empty type: 명확한 validation error

기존 legacy implicit fallback이 필요하면 provider-first 실행 필드가 전혀 없고, 같은 type의 enabled adapter instance가 정확히 하나일 때만 유지한다. wrong-field 조합은 fallback으로 조용히 통과시키지 않는다.

#### 수정 파일 및 체크리스트

- [ ] `apps/edge/internal/edgevalidate/validate.go`: provider-first type/field helper 또는 switch 추가.
- [ ] `apps/edge/internal/edgevalidate/validate_test.go`: valid cases와 wrong-field/missing-field/unknown-type cases 추가.
- [ ] 필요하면 `packages/go/config/config.go` 또는 `packages/go/config/config_test.go`: whitespace type normalization/validation 보강.

#### 테스트 작성

작성한다. 기존 `TestValidateEdgeConfig_ProviderFirstRejectsMissingTypeFields`에 최소 아래 cases를 추가한다.

- `type: openai_compat` + `base_url` only -> error
- `type: ollama` + `command` only -> error
- `type: cli` + `endpoint` only -> error
- `type: " "` + `endpoint` -> error
- supported aliases(`vllm`, `lemonade`, `sglang`, `openai_api`) + `endpoint` -> valid

### [REVIEW_CONFIG-2] Provider-First Config Example Consistency

#### 문제

`configs/edge.yaml`의 active node example은 provider-first로 바뀌었지만, 같은 파일 상단 설명은 여전히 `nodes[].adapters.openai_compat_instances`와 `nodes[].providers[].adapter`를 새 배포의 기본 경로처럼 설명한다. 이 파일은 SDD S01 Evidence Map의 `configs/edge.yaml provider-first example diff`에 포함되므로 상단 예시도 같은 방향이어야 한다.

#### 해결 방법

상단 provider-pool 설명을 provider-first 기준으로 갱신한다.

- `nodes[].providers[].adapter`는 legacy/compat override 또는 후속 compile 전 임시 호환 경로로 설명한다.
- provider resource 예시는 `type`, type별 실행 필드, `models[]`, `health`, `capacity`, `max_queue`, `queue_timeout_ms`, `request_timeout_ms`를 한 resource 안에 둔다.
- `adapters` 예시는 legacy/compat override 섹션으로만 남기고 recommended/new deploy wording을 제거한다.
- private endpoint, token, bearer key 원문을 추가하지 않는다.

#### 수정 파일 및 체크리스트

- [ ] `configs/edge.yaml`: provider-pool migration/comment/example 갱신.
- [ ] `configs/edge.yaml`: active provider-first example이 config check를 통과하도록 유지.

#### 테스트 작성

별도 단위 테스트는 필요하지 않다. `config check`, `git diff --check`, stale wording scan으로 검증한다.

## 최종 검증

```bash
go test -count=1 ./packages/go/config ./apps/edge/internal/edgevalidate
```

```bash
go run ./apps/edge/cmd/edge --config configs/edge.yaml config check
```

```bash
git diff --check
```

```bash
go test ./...
```
