<!-- task=m-iop-agent-cli-runtime/02+01_provider_catalog plan=0 tag=API -->

# Agent Provider Catalog 구현 계획

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

`CODE_REVIEW-cloud-G10.md`의 구현 에이전트 소유 섹션을 실제 내용과 출력으로 채우는 것이 필수 마지막 단계다. 검증 후 active pair를 유지하고 리뷰 준비 완료만 보고한다. 막히면 정확한 blocker/명령/output/재개 조건을 evidence 필드에 기록하며, 사용자 질문·user-input·stop 파일·상태 분류·archive/`complete.log` 처리는 하지 않는다.

## 배경

공통 runtime이 provider를 실행할 수 있어도 repo YAML에 선언한 공식 provider/model/profile을 결정적으로 discovery하고 readiness를 설명할 catalog가 필요하다. 설치·인증·model 지원 상태를 구분하고, 이미 인증된 CLI에서 동일 profile로 run/resume/cancel/status가 작동함을 증명한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md)
- Task ids:
  - `provider-catalog`: YAML provider/model/profile discovery와 lifecycle/status
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- 규칙/설계: `agent-ops/rules/project/rules.md`, `agent-ops/rules/common/rules-roadmap.md`, `agent-ops/rules/common/rules-agent-spec.md`, `agent-ops/rules/project/domain/platform-common/rules.md`, `agent-ops/rules/project/domain/node/rules.md`, `agent-ops/rules/project/domain/testing/rules.md`, `agent-roadmap/current.md`, `agent-roadmap/priority-queue.md`, `agent-roadmap/ROADMAP.md`, `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`, `agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md`, `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`.
- 계약/스펙: `agent-contract/index.md`, `agent-contract/inner/edge-node-runtime-wire.md`, `agent-contract/inner/edge-config-runtime-refresh.md`, `agent-spec/index.md`, `agent-spec/runtime/edge-node-execution.md`, `agent-spec/runtime/provider-pool-config-refresh.md`.
- 구현: `packages/go/config/provider_types.go`, `packages/go/config/load.go`, `packages/go/config/validate.go`, `packages/go/config/node_types.go`, `apps/node/internal/adapters/config_set.go`, `apps/node/internal/adapters/factory.go`, `apps/node/internal/adapters/registry.go`, `apps/node/internal/adapters/cli/profile.go`, `apps/node/internal/adapters/cli/status/status.go`, `apps/node/internal/adapters/cli/status/codex.go`, `apps/node/internal/adapters/cli/status/claude.go`, `apps/node/internal/adapters/cli/status/antigravity.go`, `apps/node/internal/adapters/cli/status/quota.go`, `configs/node.yaml`.
- 테스트: `packages/go/config/provider_catalog_config_test.go`, `packages/go/config/provider_catalog_validation_config_test.go`, `apps/node/internal/adapters/config_set_test.go`, `apps/node/internal/adapters/cli/status/status_test.go`, `apps/node/internal/adapters/cli/status/codex_test.go`, `apps/node/internal/adapters/cli/status/claude_test.go`, `apps/node/internal/adapters/cli/status/antigravity_test.go`.
- 테스트 규칙: `agent-test/local/rules.md`, `agent-test/local/platform-common-smoke.md`, `agent-test/local/node-smoke.md`, `agent-test/local/testing-smoke.md`.

### SDD 기준

- 승인/잠금 해제된 SDD의 S02와 Evidence Map S02가 기준이다.
- installed+authenticated, missing, unauthenticated, unsupported-model 네 상태를 공식 provider/model/profile id와 concrete typed error로 반환해야 한다.
- discovery/status table test와 authenticated lifecycle smoke를 API-1~3 및 최종 검증에 직접 배치했다.

### 테스트 환경 규칙

- `test_env=local`; local rules와 platform-common/node/testing profile을 읽었다. Go tests는 fresh `-count=1`, process/concurrency tests는 race를 사용한다.
- 실제 root는 `/config/workspace/iop-s0`이며 local rules의 `/config/workspace/iop` 표기와 다르다. 실제 root를 workdir로 사용하며 rule maintenance는 범위 밖이다.
- 프리플라이트: branch `dev`, HEAD `0565d2be66cc`, 사용자 소유 roadmap/SDD 변경이 존재한다. Go는 `/config/.local/bin/go`, `go1.26.2 linux/arm64`다.
- provider preflight에서 `codex 0.145.0`, `claude 2.1.220`, `agy 1.0.16`, `opencode 1.18.3` binary를 확인했고 codex/claude auth status는 성공했다. Pi help/version은 timeout, opencode auth status는 user-local log permission 오류였으므로 두 provider를 ready로 추정하지 않는다. credential/identity 원문은 evidence에 기록하지 않는다.
- authenticated smoke는 외부 provider process를 쓰므로 구현 시 `configs/iop-agent.providers.yaml`의 `smoke` profile, binary version/auth status, redacted environment를 먼저 출력하고 lifecycle call을 실행한다. 과금/네트워크가 발생할 수 있는 실제 smoke는 최소 prompt와 단일 profile로 제한한다.

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

- 기존 Edge `NodeProviderConf`/`ModelCatalogEntry` tests는 resource routing catalog를 다루며 agent CLI의 binary/auth/model/profile readiness를 다루지 않는다.
- 기존 status parser tests는 provider별 parsing은 검증하지만 YAML catalog의 stable id, duplicate/cross-reference, missing binary/auth/model mismatch matrix가 없다.
- shared runtime lifecycle은 01이 제공하며 이 계획은 catalog-selected profile이 그 구현을 사용한다는 factory/conformance와 authenticated smoke를 추가한다.

### 심볼 참조

- 기존 `NodeProviderConf`, `ModelCatalogEntry`, `CLIProfileConf`는 Edge/Node config와 tests에서 넓게 참조되므로 rename/remove하지 않는다.
- agent catalog는 별도 `AgentProviderCatalog`/`AgentProviderProfile` 타입으로 추가하고 기존 provider-pool schema와 의미를 섞지 않는다.

### 분할 판단

- 이 plan의 stable contract는 YAML declaration → validated catalog → readiness snapshot → common provider instance다. PASS 근거는 state table, factory lifecycle test, authenticated smoke다.
- predecessor `01_common_runtime_node_bridge`는 active/archived `complete.log`가 없어 현재 `missing`이다. 구현 시작 전 동일 task group의 01 completion이 필요하다.
- downstream 03은 catalog capability를, 04는 resolved provider/runtime을 소비한다.

### 범위 결정 근거

- repo-global/local config merge/watch/revision은 후속 `config-registry`; selection rules/quota/failover는 후속 epic이므로 제외한다.
- 기존 Edge model/provider catalog를 변경하지 않고 agent catalog namespace를 분리한다.
- provider authentication/credential 저장은 금지하며 외부 CLI의 기존 인증 상태만 조회한다.

### 최종 라우팅

- `evaluation_mode=first_pass`, finalizer=`finalize-task-routing` 1회.
- build `cloud/G10/routed`, review `cloud/G10/routed`; `large_indivisible_context=false`.
- positive loop risks 4개: new YAML public schema, external CLI capability variance, auth/model error classification, authenticated lifecycle smoke.
- recovery `review_rework_count=0`, `evidence_integrity_failure=false`; capability gap evidence 없음.
- canonical files: `PLAN-cloud-G10.md`, `CODE_REVIEW-cloud-G10.md`.

## 구현 체크리스트

- [ ] API-1 agent provider catalog 계약과 YAML schema/validation을 구현한다.
- [ ] API-2 binary/version/auth/model/profile discovery와 typed readiness를 구현하고 state table tests를 통과시킨다.
- [ ] API-3 catalog profile을 공통 runtime provider에 연결하고 run/resume/cancel/status conformance 및 authenticated smoke evidence를 남긴다.
- [ ] API-4 fresh/race/full 회귀와 credential-free evidence 검사를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다.

## 의존 관계 및 구현 순서

1. `01_common_runtime_node_bridge/complete.log`가 active sibling 또는 matching archive에 정확히 하나 존재해야 시작할 수 있다. 현재는 missing이다.
2. API-1 → API-2 → API-3 → API-4 순서로 진행한다.

### [API-1] Catalog 계약과 YAML schema

- 문제: `packages/go/config/provider_types.go:27-95`는 Edge resource provider이고 agent CLI command/auth/unattended/profile 계약을 표현하지 않는다.
- 해결 방법: predecessor의 `agent-runtime` inner contract에 provider profile/readiness를 확정하고, `packages/go/agentconfig`에 repo YAML의 stable provider/model/profile schema와 deterministic validation/load를 추가한다.

```go
// Before: packages/go/config/provider_types.go:27-95 — Edge resource schema only
// type NodeProviderConf struct { ID, Type, Category, Models, Command ... }
// After: agent-specific schema
// type ProviderProfile struct { ID, Provider, Model, Command string; Capabilities CapabilitySet; StatusProbe ProbeSpec }
```

- 수정 파일 및 체크리스트:
  - [ ] `agent-contract/inner/agent-runtime.md`에 catalog/readiness/lifecycle error 의미 보강.
  - [ ] `packages/go/agentconfig/catalog.go`, `load.go`, `validate.go` 구현.
  - [ ] `configs/iop-agent.providers.yaml`에 비밀 없는 provider/model/profile 기본 선언과 smoke profile 추가.
- 테스트 작성: `packages/go/agentconfig/catalog_test.go`와 `testdata/*.yaml`에 정상, duplicate id, dangling model/profile, invalid capability, unknown provider tests를 작성한다.
- 중간 검증: `/config/.local/bin/go test -count=1 ./packages/go/agentconfig/...`가 PASS해야 한다.

### [API-2] Discovery와 typed readiness

- 문제: provider별 status parser는 있으나 binary missing/auth missing/model unsupported를 한 catalog snapshot으로 정규화하지 않는다.
- 해결 방법: shared provider catalog package가 exec lookup/version/status probes를 timeout/context 하에서 수행하고 `ready | missing_binary | unauthenticated | unsupported_model | probe_error` typed 결과를 반환한다. 출력/오류는 credential redactor를 거친다.

```go
// Before: apps/node/internal/adapters/cli/status/status.go:12-70 — provider-specific raw status result
// After: stable catalog result
// Readiness{ProviderID: id, ModelID: model, ProfileID: profile, State: StateUnauthenticated, Cause: ErrAuthenticationRequired}
```

- 수정 파일 및 체크리스트:
  - [ ] `packages/go/agentprovider/catalog/catalog.go`, `discovery.go`, `readiness.go`, `redact.go` 구현.
  - [ ] provider별 probe adapter가 predecessor 공통 status API를 사용하도록 연결.
  - [ ] timeout/cancel, PATH isolation, raw credential 비노출 보장.
- 테스트 작성: fake executable/test PATH로 S02 전체 state table, timeout/cancel, deterministic ordering, redaction tests를 작성한다.
- 중간 검증: `/config/.local/bin/go test -race -count=1 ./packages/go/agentprovider/catalog/...`가 PASS해야 한다.

### [API-3] Profile factory와 authenticated lifecycle smoke

- 문제: discovery 결과가 common runtime factory와 연결되지 않으면 YAML profile이 실제 run/resume/cancel/status에 사용된다는 근거가 없다.
- 해결 방법: validated profile에서 predecessor의 shared provider instance를 만들고 동일 model/profile identity를 event/status에 유지한다. fixture conformance 후 redacted authenticated smoke를 한 profile로 수행한다.
- 수정 파일 및 체크리스트:
  - [ ] `packages/go/agentprovider/catalog/factory.go`에서 common provider 생성.
  - [ ] `packages/go/agentprovider/catalog/lifecycle_conformance_test.go` 추가.
  - [ ] `cmd/iop-provider-smoke/main.go`에 redacted preflight와 run/resume/cancel/status harness 추가.
- 테스트 작성: fake CLI normal/model mismatch/cancel/resume fixtures와 opt-in actual logged-in profile smoke를 작성한다.
- 중간 검증: `/config/.local/bin/go test -count=1 ./packages/go/agentprovider/catalog/...`가 PASS하고, 아래 smoke가 네 lifecycle operation과 redacted terminal status를 출력해야 한다.

### [API-4] 회귀/evidence 검증

- 문제: 외부 smoke output에 credential이 섞이거나 기존 config/runtime을 깨뜨릴 수 있다.
- 해결 방법: fresh package/full tests와 race를 실행하고 smoke output을 `/tmp`에 저장해 secret-like key/token/header 패턴이 없는지 검사한다.
- 수정 파일 및 체크리스트:
  - [ ] smoke output의 command/env/token/header redaction 확인.
  - [ ] 기존 `packages/go/config`와 common provider tests 회귀 확인.
- 테스트 작성: API-1~3 tests로 충분하므로 별도 파일은 추가하지 않는다.
- 중간 검증: 최종 검증 명령 전체가 PASS해야 한다.

## 수정 파일 요약

| 파일/영역 | 항목 |
|---|---|
| `agent-contract/inner/agent-runtime.md` | API-1 |
| `packages/go/agentconfig/**`, `configs/iop-agent.providers.yaml` | API-1 |
| `packages/go/agentprovider/catalog/**` | API-2, API-3 |
| `cmd/iop-provider-smoke/main.go` | API-3 |

## 최종 검증

```bash
shopt -s nullglob
predecessors=(agent-task/m-iop-agent-cli-runtime/01_common_runtime_node_bridge/complete.log agent-task/archive/[0-9][0-9][0-9][0-9]/[0-1][0-9]/m-iop-agent-cli-runtime/01_common_runtime_node_bridge/complete.log)
test "${#predecessors[@]}" -eq 1
/config/.local/bin/go test -count=1 ./packages/go/agentconfig/... ./packages/go/agentprovider/catalog/...
/config/.local/bin/go test -race -count=1 ./packages/go/agentprovider/catalog/...
/config/.local/bin/go test -count=1 ./packages/go/config/... ./packages/go/agentruntime/... ./packages/go/agentprovider/...
/config/.local/bin/go run ./cmd/iop-provider-smoke -config ./configs/iop-agent.providers.yaml -profile codex-smoke -operations status,run,resume,cancel -redact 2>&1 | tee /tmp/iop-agent-provider-smoke.log
! rg -i '(authorization:|api[_-]?key|access[_-]?token|refresh[_-]?token|bearer [a-z0-9._-]+)' /tmp/iop-agent-provider-smoke.log
git diff --check
```

기대 결과: predecessor gate와 모든 fresh/race tests가 PASS하고 smoke는 공식 provider/model/profile id와 네 operation의 terminal 결과를 남긴다. credential search는 무출력/성공하고 `git diff --check`는 무출력이어야 한다. 실제 provider가 preflight 뒤 ready가 아니면 호출하지 않고 typed blocker와 원인을 evidence에 기록하며 PASS로 주장하지 않는다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.
