<!-- task=m-agent-readable-repository-refactor/02_config_modules plan=0 tag=REFACTOR_CONFIG -->

# Config 책임 중심 모듈 분해 계획

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 구현의 필수 마지막 단계다. 계획의 검증을 실제로 실행하고 출력과 변경 내용을 채운 뒤 active 파일을 그대로 두고 리뷰 준비 완료를 보고한다. 최종 판정, log/archive 이동, `complete.log` 작성은 code-review 스킬만 수행한다.

선택 Milestone의 `구현 잠금 > 결정 필요`가 구현을 막을 때만 review stub의 `사용자 리뷰 요청`에 정확한 링크와 근거를 기록하고 code-review를 위해 중단한다. 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하거나, 구현 에이전트가 `USER_REVIEW.md`, archive log, `complete.log`를 만들면 안 된다. 환경·secret·서비스 차단, 일반 범위 변경, 후속 에이전트가 보완할 수 있는 evidence 공백은 사용자 리뷰 요청 사유가 아니다.

## 배경

`packages/go/config/config.go`는 1,170줄 안에 Node/Edge/provider/adapter 타입, load/default, normalization, validation을 함께 둔다. 외부 YAML/config 계약과 공개 Go 식별자를 그대로 유지하면서 같은 package 안에서 변경 이유별 파일로 분리해 config 작업의 읽기 범위를 줄인다.

## 사용자 리뷰 요청 흐름

선택 Milestone 잠금 결정만 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 active review stub의 `사용자 리뷰 요청`에 기록한다. 구현 중 직접 사용자 질문은 금지되며, 요청의 유효성 검증과 실제 `USER_REVIEW.md` 작성은 code-review 책임이다.

## 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:
  - `config-modules`: config 타입, load/default, normalization, validation 책임 분해
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `packages/go/config/config.go`
- `packages/go/config/node_config_test.go`
- `packages/go/config/edge_runtime_config_test.go`
- `packages/go/config/edge_cli_config_test.go`
- `packages/go/config/edge_openai_config_test.go`
- `packages/go/config/provider_catalog_config_test.go`
- `packages/go/config/provider_catalog_validation_config_test.go`
- `configs/edge.yaml`
- `configs/node.yaml`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `agent-contract/outer/openai-compatible-api.md`
- `agent-spec/runtime/provider-pool-config-refresh.md`

### SDD 기준

Milestone은 `SDD: 불필요`다. 외부 config schema와 runtime 의미를 바꾸지 않는 동일 package 내 동작 보존형 분리이기 때문이다.

### 테스트 환경 규칙

- `test_env=local`.
- `agent-test/local/rules.md`와 매칭 profile `agent-test/local/platform-common-smoke.md`, `agent-test/local/testing-smoke.md`를 읽었다.
- 현재 runner는 `/config/workspace/iop`, branch `main`, HEAD `01dc2ef`, Go `go1.26.2 linux/arm64`다.
- worktree에는 선행 직접 작업인 Ollama 파일 분해가 있으므로 이를 보존한다.
- 기본 Go cache는 권한 오류가 있어 모든 Go 검증에 `GOCACHE=/tmp/iop-go-cache`를 사용한다.
- package 명령은 `GOCACHE=/tmp/iop-go-cache go test -count=1 ./packages/go/config`로 사전 실행되어 통과했다.
- config 실행 계약의 full-cycle evidence는 구현 후 `e2e-smoke` 스킬 `mode=bin-diagnostic`으로 `scripts/dev/edge.sh`와 `scripts/dev/node.sh`를 각각 실행해 확보한다. 임시 config와 동적 local port를 쓰며 tracked `configs/*.yaml`은 수정하지 않는다.

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

- Node/Edge defaults와 load: `node_config_test.go`, `edge_runtime_config_test.go`, `edge_cli_config_test.go`가 커버한다.
- OpenAI config와 credential-free validation: `edge_openai_config_test.go`가 커버한다.
- provider/model catalog, normalization, legacy conflict, long-context validation: 두 provider catalog test 파일이 커버한다.
- 기능 변경은 없으므로 새 테스트는 만들지 않는다. 파일 분리 뒤 기존 test name과 assertion 수가 유지되어야 한다.

### 심볼 참조

none. 공개·내부 식별자를 rename/remove하지 않는다.

### 분할 판단

split decision policy를 먼저 평가했다. 이 작업은 `packages/go/config` 단일 package/소유 경계이고, 공개 API rollout이나 선행 subtask가 없으며 모든 변경이 동일한 package test로 검증된다. 별도 subtask로 나누면 한 시점에 같은 선언을 두 파일에 중복하거나 중간 compile failure를 만들므로 단일 plan이 안전하다. Milestone 전체는 split task group을 사용하되 이 subtask 자체에는 추가 분할을 적용하지 않는다.

### 범위 결정 근거

- `configs/*.yaml`의 field와 값, 공개 type/function 이름, YAML/mapstructure tag는 변경하지 않는다.
- `apps/edge/internal/configrefresh`, mapper, Node adapter 소비 코드는 call-site 변경이 없으므로 수정하지 않고 소비자 테스트만 실행한다.
- config schema 개선, validation 정책 변경, secret/default 변경은 현재 리팩터링 범위에서 제외한다.

### 빌드 등급

`local-G06`. 1,170줄과 6개 test 파일을 함께 다루지만 단일 package의 기계적 이동이며 로컬 회귀로 계약 보존을 결정적으로 검증할 수 있다.

## 구현 체크리스트

- [ ] Node/Edge/provider/adapter 공개 타입과 작은 type method를 책임별 type 파일로 이동하고 식별자·tag를 보존한다.
- [ ] `Load`, `LoadEdge`, Node/Edge default 설정을 load/default 파일로 이동하고 load 순서를 보존한다.
- [ ] adapter normalization과 provider/model/OpenAI validation을 각 책임 파일로 이동하고 error text/order를 보존한다.
- [ ] 기존 config 및 소비자 회귀, readability ratchet, repo 내부 Edge–Node 진단을 통과시킨다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REFACTOR_CONFIG-1] Config 타입 계약 분리

문제: `packages/go/config/config.go:41`부터 `:596`까지 Node/Edge/provider/adapter 타입과 작은 method가 load/validation 구현 사이에 섞여 있다.

해결 방법:

```go
// Before: packages/go/config/config.go:41
type NodeConfig struct { ... }
// ... NodeProviderConf, ModelCatalogEntry, EdgeOpenAIConf, AdaptersConf ...
```

```go
// After
// node_types.go: NodeConfig, transport/reconnect/runtime
// edge_types.go: EdgeConfig와 입력 표면/공통 Edge 타입
// provider_types.go: NodeProviderConf, ModelCatalogEntry와 provider helper
// adapter_types.go: AdaptersConf와 provider/CLI adapter config 타입
```

수정 파일 및 체크리스트:

- [ ] `packages/go/config/node_types.go`를 만들고 Node 전용 타입을 이동한다.
- [ ] `packages/go/config/edge_types.go`를 만들고 Edge surface 타입을 이동한다.
- [ ] `packages/go/config/provider_types.go`를 만들고 provider/model catalog 타입을 이동한다.
- [ ] `packages/go/config/adapter_types.go`를 만들고 adapter config 타입을 이동한다.
- [ ] YAML/mapstructure tag와 exported doc comment를 그대로 보존한다.

테스트 작성: skip. 공개 계약과 assertion은 기존 6개 config test 파일이 이미 직접 검증한다.

중간 검증:

```bash
gofmt -d packages/go/config/*.go
GOCACHE=/tmp/iop-go-cache go test -count=1 ./packages/go/config
```

기대 결과: formatting diff가 없고 package test가 통과한다.

### [REFACTOR_CONFIG-2] Load와 defaults 분리

문제: `packages/go/config/config.go:598`의 `Load`, `:613`의 `LoadEdge`, `:1130` 이후 defaults가 validation/normalization과 떨어져 있어 load 흐름을 한 번에 읽을 수 없다.

해결 방법:

```go
// Before: packages/go/config/config.go:613
func LoadEdge(cfgFile string) (*EdgeConfig, error) {
    // read, unmarshal, defaults, normalize, validate가 한 파일에 존재
}
```

```go
// After: load.go
func Load(...)
func LoadEdge(...)
func setDefaults(...)
func setEdgeDefaults(...)
```

수정 파일 및 체크리스트:

- [ ] `packages/go/config/load.go`에 Viper load와 defaults를 배치한다.
- [ ] `LoadEdge`의 normalize/validate 호출 순서와 error wrapping을 변경하지 않는다.

테스트 작성: skip. default/override/load 순서는 기존 Node, Edge runtime, CLI, OpenAI test가 검증한다.

중간 검증:

```bash
GOCACHE=/tmp/iop-go-cache go test -count=1 ./packages/go/config -run 'TestLoad|TestEdgeRefresh|TestCLIProfile|TestCompletionMarker'
```

기대 결과: 선택 테스트가 모두 통과한다.

### [REFACTOR_CONFIG-3] Normalization과 validation 분리

문제: `packages/go/config/config.go:195`, `:323`, `:714`, `:741`, `:913`, `:935`, `:970`, `:1015`의 validation/normalization이 서로 다른 config 타입 선언 사이에 흩어져 있다.

해결 방법:

```go
// Before: packages/go/config/config.go
func (p NodeProviderConf) Validate() error
func (e ModelCatalogEntry) Validate(...) error
func normalizeAdapters(...) error
func CheckProviderLegacyConflict(...) error
```

```go
// After
// normalize.go: NormalizeAgentKind/ProviderType/Adapters와 legacy instance promotion
// validate.go: provider/model/OpenAI/legacy conflict/long-context validation
```

수정 파일 및 체크리스트:

- [ ] `packages/go/config/normalize.go`에 normalization 계열을 이동한다.
- [ ] `packages/go/config/validate.go`에 validation과 validation-only helper를 이동한다.
- [ ] validation 순서, exact error text, exported function signature를 보존한다.
- [ ] 원래 `config.go`가 비면 삭제하고 선언 중복이 없음을 확인한다.

테스트 작성: skip. provider catalog와 validation test가 정상/경계/오류 순서를 이미 커버한다.

중간 검증:

```bash
GOCACHE=/tmp/iop-go-cache go test -count=1 ./packages/go/config -run 'Provider|ModelCatalog|Normalize|PrincipalToken|LongContext'
```

기대 결과: 선택 테스트가 모두 통과한다.

### [REFACTOR_CONFIG-4] 소비자·실행 경로 회귀

문제: config 파일 이동은 Go compiler 기준으로는 투명하지만 Edge refresh/mapper와 Node adapter factory가 공개 타입·함수를 실제 소비하므로 package test만으로 import/call-site 회귀를 닫을 수 없다.

해결 방법: config 소비 패키지, 전체 공통 package, readability ratchet을 실행하고 임시 config 기반 repo 내부 Edge–Node 진단에서 register/message/command 흐름을 확인한다.

수정 파일 및 체크리스트:

- [ ] 소스 추가 수정 없이 소비자 테스트를 실행한다.
- [ ] `e2e-smoke` 스킬의 `mode=bin-diagnostic`, `scope=config structure preservation` 절차로 메시지 2회와 `/nodes`, `/capabilities`, `/transport`, `/sessions`를 확인한다.

테스트 작성: skip. 이 항목은 기존 소비자와 실제 entrypoint 검증이다.

중간 검증:

```bash
GOCACHE=/tmp/iop-go-cache go test -count=1 ./packages/go/... ./apps/edge/internal/configrefresh ./apps/edge/internal/node ./apps/node/internal/adapters
make readability-audit
```

기대 결과: 모든 package가 통과하고 readability ratchet이 새/증가 위반 없이 통과한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `packages/go/config/config.go` | REFACTOR_CONFIG-1, REFACTOR_CONFIG-2, REFACTOR_CONFIG-3 |
| `packages/go/config/node_types.go` | REFACTOR_CONFIG-1 |
| `packages/go/config/edge_types.go` | REFACTOR_CONFIG-1 |
| `packages/go/config/provider_types.go` | REFACTOR_CONFIG-1 |
| `packages/go/config/adapter_types.go` | REFACTOR_CONFIG-1 |
| `packages/go/config/load.go` | REFACTOR_CONFIG-2 |
| `packages/go/config/normalize.go` | REFACTOR_CONFIG-3 |
| `packages/go/config/validate.go` | REFACTOR_CONFIG-3 |

## 최종 검증

```bash
gofmt -d packages/go/config/*.go
GOCACHE=/tmp/iop-go-cache go test -count=1 ./packages/go/config
GOCACHE=/tmp/iop-go-cache go test -count=1 ./packages/go/... ./apps/edge/internal/configrefresh ./apps/edge/internal/node ./apps/node/internal/adapters
make readability-audit
git diff --check
```

모두 종료 코드 0이어야 한다. 이어서 `e2e-smoke` 스킬 `mode=bin-diagnostic`, `scope=config structure preservation`로 임시 config를 사용해 startup/register, 같은 session의 메시지 2회 relay 순서, `/nodes`, `/capabilities`, `/transport`, `/sessions` 성공 또는 명시 unsupported 결과를 기록한다. Go test cache는 허용하지 않으므로 `-count=1`을 유지한다.

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