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

# Plan - CONFIG

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

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

## 배경

현재 config는 `NodeProviderConf`가 catalog fields와 `Adapter` 참조를 갖고 있고, 예시는 여전히 `openai.model_routes`와 `nodes[].adapters`를 기본 경로처럼 길게 안내한다. SDD S01은 `nodes[].providers[]`만으로 type별 실행 필드, model alias/served target, capacity/queue를 검증하는 provider-first schema를 요구한다.

## 사용자 리뷰 요청 흐름

사용자 리뷰 요청은 선택된 Milestone lock 결정만 review stub의 `사용자 리뷰 요청`에 기록한다. 외부 endpoint/secret, 검증 환경, evidence 공백은 일반 검증 blocker 또는 follow-up으로 기록한다.

## 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

## 분석 결과

### 읽은 파일

- `agent-roadmap/sdd/operational-observability-provider-management/node-provider-first-config-surface/SDD.md`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `packages/go/config/config.go`
- `configs/edge.yaml`
- `apps/edge/internal/edgevalidate/validate.go`
- `agent-test/local/rules.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-test/local/edge-smoke.md`

### SDD 기준

SDD 상태는 `[승인됨]`, SDD 잠금은 `해제`다. 대상 Acceptance Scenario는 S01이며, Evidence Map은 `config schema/validation unit tests and configs/edge.yaml provider-first example diff`를 요구한다. 이 plan은 `providers[]` schema와 validation을 먼저 고정하고, mapper/dispatch compile은 `02+01_normalize_compile`과 `04+02,03_routing_status_refresh`로 넘긴다.

### 테스트 환경 규칙

`test_env=local`. `agent-test/local/rules.md`, `platform-common-smoke.md`, `edge-smoke.md`를 읽었다. 적용 명령은 `go test ./packages/go/config ./apps/edge/internal/edgevalidate`와 config 예시 검증이며, 전체 회귀는 `go test ./...`를 보조로 둔다. 원격/field 검증은 이 schema plan의 필수 조건이 아니다.

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

provider-first provider resource 안에 type별 실행 필드가 들어오는 happy path와 invalid path 테스트가 없다. 현재 validation은 `apps/edge/internal/edgevalidate/validate.go:71`에서 `providers[].adapter`를 필수로 요구해 provider-first source of truth와 충돌한다.

### 심볼 참조

rename/remove 없음. `NodeProviderConf.Adapter`는 제거하지 말고 legacy compat로 낮춘다.

### 분할 판단

SDD Evidence Map이 6개 sibling task를 지정한다. 이 plan은 schema/validation만 다루는 선행 독립 작업이며 payload compile, legacy compat, routing/status/refresh, docs, smoke는 의존 subtask로 분리한다.

### 범위 결정 근거

`NodeConfigPayload`, Edge dispatch, OpenAI handler 변경은 제외한다. schema가 먼저 안정되어야 downstream compile 계획이 정확해진다.

### 빌드 등급

`local-G06`: config 계약과 validation을 바꾸지만 범위가 `packages/go/config`, `configs/edge.yaml`, `edgevalidate`로 한정되고 Go unit test로 검증 가능하다.

## 구현 체크리스트

- [ ] `NodeProviderConf`를 provider-first schema로 확장하고 `adapter`는 optional compat field로 낮춘다.
- [ ] `LoadEdge`와 `ValidateEdgeConfig`가 provider type별 필드, served model list, capacity/queue invalid path를 검증하도록 테스트를 추가한다.
- [ ] `configs/edge.yaml`의 기본 예시를 `providers[]` source of truth 중심으로 바꾸고 `adapters` 중복 작성은 legacy/compat 섹션으로 낮춘다.
- [ ] `go test ./packages/go/config ./apps/edge/internal/edgevalidate`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [CONFIG-1] Provider-First Schema And Validation

#### 문제

`packages/go/config/config.go:112`의 `NodeProviderConf`는 provider resource identity와 catalog fields만 갖고 실행 필드는 adapter instance 아래에 남아 있다. `apps/edge/internal/edgevalidate/validate.go:71`은 provider `adapter`를 필수로 요구한다.

#### 해결 방법

`NodeProviderConf`에 type별 실행 필드를 추가한다. 최소 필드는 `endpoint`, `base_url`, `headers`, `command`, `args`, `env`, `mode`, `resume_args`, `output_format`, timeout/capacity/queue다. `type` 값은 `openai_compat`, `ollama`, `cli`, legacy provider label 후보를 canonical driver로 normalize한다. `adapter`가 비어 있어도 provider-first field가 충분하면 유효해야 한다. `adapter`가 있으면 legacy compat로 검증한다.

Before:

```go
// packages/go/config/config.go:123
Adapter string `mapstructure:"adapter" yaml:"adapter,omitempty"`
Models []string `mapstructure:"models" yaml:"models,omitempty"`
```

After:

```go
Adapter string `mapstructure:"adapter" yaml:"adapter,omitempty"` // legacy/compat override
Endpoint string `mapstructure:"endpoint" yaml:"endpoint,omitempty"`
BaseURL string `mapstructure:"base_url" yaml:"base_url,omitempty"`
Headers map[string]string `mapstructure:"headers" yaml:"headers,omitempty"`
Command string `mapstructure:"command" yaml:"command,omitempty"`
Args []string `mapstructure:"args" yaml:"args,omitempty"`
Models []string `mapstructure:"models" yaml:"models,omitempty"`
```

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

- [ ] `packages/go/config/config.go`: provider-first fields와 type별 validation 추가.
- [ ] `packages/go/config/config_test.go`: happy path와 invalid path 추가.
- [ ] `apps/edge/internal/edgevalidate/validate.go`: `adapter` 필수 규칙을 provider-first compile 가능성 기준으로 변경.
- [ ] `apps/edge/internal/edgevalidate/validate_test.go`: adapter-less provider-first valid, missing endpoint/command invalid 추가.
- [ ] `configs/edge.yaml`: provider-first 예시를 기본 경로로 정리.

#### 테스트 작성

작성한다. `TestLoadEdge_ProviderFirstNodeProvidersHappyPath`, `TestLoadEdge_ProviderFirstRejectsMissingTypeFields`, `TestValidateEdgeConfig_ProviderFirstWithoutAdapterAllowed`를 추가한다.

#### 중간 검증

```bash
go test ./packages/go/config ./apps/edge/internal/edgevalidate
```

기대 결과: PASS, provider-first happy/invalid path가 모두 검증된다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/go/config/config.go` | CONFIG-1 |
| `packages/go/config/config_test.go` | CONFIG-1 |
| `apps/edge/internal/edgevalidate/validate.go` | CONFIG-1 |
| `apps/edge/internal/edgevalidate/validate_test.go` | CONFIG-1 |
| `configs/edge.yaml` | CONFIG-1 |

## 최종 검증

```bash
go test ./packages/go/config ./apps/edge/internal/edgevalidate
```

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

Go test cache output is acceptable for `go test ./...`; schema-focused package tests should be rerun after edits. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
