# Milestone: 사용자별 Provider Credential Slot과 Alias Routing

## 위치

- Roadmap: [ROADMAP.md](../../../../ROADMAP.md)
- Phase: [PHASE.md](../../../../phase/operational-observability-provider-management/PHASE.md)

## 목표

Control Plane을 IOP principal token과 외부 provider credential의 원장으로 두고, 한 사용자가 같은 vendor에 여러 credential slot을 소유할 수 있게 한다. 호출자는 IOP token과 principal별 model route/alias만 사용하며 Edge는 선택된 slot의 credential을 안전하게 실행 경계에 주입하고, 자동 slot rotation이나 caller-supplied provider token 없이 OpenAI Chat, Anthropic Messages와 기존 Responses를 호출한다.

## 상태

[완료]

## 승격 조건

- 없음

## 구현 잠금

- 상태: 해제
- SDD: 필요
- SDD 문서: [SDD.md](../../../sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md)
- SDD 사유: Control Plane 영속 원장, IOP token 발급/폐기, provider secret 암호화와 Edge/Node 전달, principal별 model discovery/routing 및 인증 실패 정책을 함께 변경한다.
- 잠금 해제 조건:
  - [x] SDD 잠금이 해제되어 있다.
  - [x] SDD 사용자 리뷰가 없거나 승인/해결되었다.
  - [x] Acceptance Scenario가 Milestone 기능 Task와 연결되어 있다.
  - [x] Evidence Map이 완료 시 `Roadmap Completion`과 최종 검증 evidence로 검증 가능하게 연결되어 있다.
- 결정 필요: 없음

## 범위

- Control Plane 영속 저장소의 principal, IOP bearer token hash/revision/revocation 원장
- 사용자별 provider credential slot의 stable ID, optional alias, vendor/credential kind, encrypted secret revision, enabled/revoked 상태
- credential slot, provider protocol profile과 upstream model을 묶어 principal별 client-facing model route를 만드는 binding
- 같은 vendor/model에 여러 slot을 연결한 별도 route와 alias collision/ambiguity의 fail-closed 처리
- Control Plane에서 Edge로 전달되는 secret-free auth/routing projection과 generation/expiry/revocation
- principal/operator가 자신의 IOP token과 provider slot/model binding/alias를 생성·조회·변경·폐기하는 Control Plane 관리 operation
- credential-bearing Client-Control Plane, Control Plane-Edge와 Edge-Node 경로의 인증·기밀 transport
- 선택된 slot credential의 bounded runtime lease와 provider adapter 직전 auth header 주입
- OpenAI Chat, Anthropic Messages, 기존 Responses의 공통 principal auth와 principal-filtered model discovery
- 선택된 provider credential slot ref/revision의 dispatch·usage attribution
- 기존 Edge `openai.principal_tokens[]`와 request header 기반 `openai.provider_auth`의 명시적 compatibility/migration mode
- Seulgi Claude/GPT를 포함한 provider slot fixture와 구현 시 대표 slot 1개의 일회성 E2E smoke

## 기능

### Epic: [principal-auth] Control Plane Principal Token 원장

IOP bearer token의 발급, 인증 projection과 폐기를 Control Plane 원장으로 수렴시킨다.

- [x] [principal-store] Control Plane DB에 principal과 IOP token hash, token reference, status/revision을 저장하고 raw IOP token은 발급 시 한 번만 반환한다. 검증: 발급/조회/폐기/restart persistence test에서 raw token이 DB, API 조회, log와 metric에 남지 않는다.
- [x] [auth-projection] Control Plane이 Edge에 principal token hash와 route 권한의 generation/expiry projection을 동기화하고 Edge가 bounded cache로 인증한다. 검증: fresh/stale/revoked/out-of-order generation fixture에서 fresh projection만 사용하고 expiry 뒤 CP-managed 외부 호출은 fail closed한다.
- [x] [surface-auth] OpenAI-compatible 표면은 `Authorization: Bearer`, Anthropic-compatible 표면은 같은 IOP token의 bearer 또는 `x-api-key` 입력을 동일 principal로 인증한다. 검증: 두 헤더가 같은 token이면 성공하고 불일치, 미등록, 폐기 token은 provider dispatch 전에 `401`로 종료된다.

### Epic: [credential-slots] Provider Credential Slot 원장과 Route

사용자가 vendor별 여러 token을 독립 slot으로 소유하고 명시적으로 선택하게 한다.

- [x] [slot-store] Control Plane이 principal별 credential slot의 stable `slot_id`, optional alias, vendor/credential kind, encrypted secret revision과 lifecycle을 protocol profile과 분리해 저장한다. 검증: 같은 principal/vendor에 여러 slot과 한 slot의 여러 compatible profile binding을 허용하고 secret envelope key가 없거나 중복 alias/invalid binding이면 저장 또는 활성화를 거부한다.
- [x] [model-binding] 각 client-facing `route_id`가 정확히 하나의 principal, credential slot, provider protocol profile과 upstream model에 결합되고 optional slot alias를 선택 표면에 반영한다. 검증: 같은 upstream model에 `claude-1`, `claude-2` 같은 별도 slot route와 한 slot의 여러 compatible protocol/model route를 만들 수 있으며 ambiguous alias, incompatible profile과 다른 principal의 route는 fail closed한다.
- [x] [model-discovery] `/v1/models`의 OpenAI/Anthropic variant가 인증 principal에게 허용된 active route만 반환한다. 검증: principal별 list가 격리되고 disabled/revoked/expired slot과 충돌 alias가 노출되지 않으며 Claude Code가 반환 route를 선택할 수 있다.
- [x] [explicit-selection] 요청 `model`은 principal별 route를 명시 선택하고 한 route 안에서 token slot을 자동 round-robin, fallback 또는 vendor 교차 대체하지 않는다. 검증: 같은 model의 두 slot 중 요청 route에 결합된 slot만 선택되고 그 slot 실패는 다른 slot의 암묵적 사용으로 이어지지 않는다.
- [x] [credential-management] host-local Control Plane operator CLI로 principal/최초 IOP token을 bootstrap하고, 인증 principal은 자신의 추가 token/slot/model binding/alias만 관리하는 Control Plane operation을 제공한다. 검증: bootstrap/create/list/update/rotate/disable/revoke와 cross-principal authorization fixture에서 secret은 입력 시에만 수신되고 이후 조회에는 반환되지 않는다.

### Epic: [secret-delivery] Credential 보호와 실행 주입

Control Plane 원장의 provider secret을 일반 config/header projection에 노출하지 않고 선택된 실행 경계에서만 사용한다.

- [x] [secret-at-rest] provider raw token을 deployment secret manager가 공급한 key로 envelope-encrypt하여 Control Plane DB에 저장하고 key/원문을 tracked config와 DB row에 평문으로 두지 않는다. 검증: encrypt/decrypt/key-version/restart fixture와 저장소 inspection에서 ciphertext와 metadata만 관찰된다.
- [x] [secure-transport] credential을 수신하거나 운반하는 Client-Control Plane, Control Plane-Edge와 Edge-Node 연결에 peer 인증과 전송 기밀성을 강제한다. 검증: 평문/미인증/잘못된 peer 연결에서는 credential management와 cloud dispatch가 차단되고 TLS test identity로만 성공한다.
- [x] [credential-lease] Edge는 slot metadata만 캐시하고 선택된 slot/revision에 대한 bounded credential lease를 Control Plane에서 받아 dedicated sensitive payload로 실행 경계에 전달한다. 검증: 다른 principal/slot/route/target, 만료·폐기·변조 lease는 adapter 호출 전에 거부되고 raw secret이 generic header/metadata projection에 없다.
- [x] [adapter-injection] Node/Edge provider adapter는 유효 lease의 request-local secret을 실행 직전에 profile의 auth header로 주입하고 request 종료 뒤 credential reference만 관측한다. 검증: upstream fixture에는 올바른 header가 도달하지만 proto debug, status, error, log, metric과 response에는 raw secret이 나타나지 않는다.
- [x] [revocation] slot disable/revoke 또는 secret rotation은 revision을 증가시키고 이전 projection/lease를 bounded expiry 안에 무효화한다. 검증: in-flight 정책, 새 요청 차단과 out-of-order refresh가 deterministic clock test에서 정의된 순서로 수렴한다.

### Epic: [migration-ops] 호환 Migration과 Qualification

기존 Edge-local token/header 전달에서 Control Plane 원장으로 안전하게 전환하고 상시 provider 구독 없이 검증한다.

- [x] [compat-migration] credential-plane 비활성 배포에서는 기존 `principal_tokens[]`/`provider_auth`가 동작하고, 활성 배포에서는 Control Plane projection이 유일한 원장이 되어 caller-supplied raw provider token을 명시적으로 거부한다. 검증: mode별 migration/rollback fixture에서 두 원장을 혼합하거나 IOP token을 provider token으로 재사용하지 않는다.
- [x] [slot-attribution] 선택된 credential `slot_ref`와 secret revision을 immutable dispatch binding과 usage/실행 관측에 별도 safe field로 전달하고 기존 inbound IOP `token_ref`와 혼동하지 않는다. 검증: 같은 model의 두 slot 호출이 provider usage에 분리 귀속되고 alias/raw secret은 metric label에 포함되지 않는다.
- [x] [slot-smoke] Seulgi를 포함한 vendor/model/slot fixture를 상시 실행하고 구현 시 제공된 대표 credential slot 1개로 Chat 또는 Messages E2E를 일회성 smoke한다. 검증: 일반 CI는 외부 credential/구독 없이 통과하고 live 결과에는 provider, model, slot reference, date, revision과 성공/실패만 남는다.
- [x] [contract-ops] Control Plane-Edge/Edge-Node credential projection, public auth/model routing 계약, 운영 redaction/revocation 절차와 현재 구현 spec을 동기화한다. 검증: contract/spec index가 canonical 문서를 가리키며 raw secret이 문서 예시와 test artifact에 없다.

## 완료 리뷰

- 상태: 통과
- 요청일: 2026-08-02
- 완료 근거: 같은 Milestone task group의 archive `complete.log` 10개를 집계해 17개 기능 Task의 capability·검증과 SDD S01~S17 Evidence Map 충족을 확인했다.
- 코드 감사: Control Plane credential lease가 외부 호환용 가변 built-in profile map을 다시 읽던 작은 일관성 문제를 수정했다. 서비스 생성 시 독립 profile catalog snapshot을 보관하고 전역 map 변경 뒤에도 기존 유효 route의 lease 발급이 유지되는 회귀 테스트를 추가했다.
- 동기화 근거: canonical 로그 4개의 generation header와 최종 PASS를 확인했고, 관련 Go package race test, Client 42 tests와 Flutter analyze, `go vet`, protobuf Go/Dart 재생성 무변경, 실제 3-process secure-delivery와 deterministic credential-slot smoke를 모두 통과했다. 비용 guard에 따라 이미 승인·기록된 대표 provider one-shot은 재호출하지 않았다.
- Spec sync: 완료 — [agent-spec index](../../../../../agent-spec/index.md)의 Control Plane operations, provider-pool config refresh, OpenAI-compatible surface, Edge-Node execution 문서가 현재 구현 경계를 이미 반영하며 이번 snapshot 안정화는 공개 동작·계약을 바꾸지 않아 추가 spec 본문 변경이 필요하지 않았다.
- 비차단 저장소 관찰: 전체 `go test ./...`는 현재 실행 환경의 Unix socket 경로/xattr confinement 제약과 이 Milestone 변경 전부터 존재한 Agent Task parity checksum drift에서 실패했다. 해당 경로는 현재 Milestone 커밋에서 변경되지 않았고 credential 관련 Control Plane·Edge·Node 패키지는 전체 실행에서도 통과했으며, `streamgate`는 독립 재실행에서 통과했다.
- 남은 차단: 기능·구현 잠금·SDD gate와 workspace milestone dependency 차단은 없다.
- 검토 항목: SDD S01~S17, secret-at-rest/transport/lease/revocation 경계, principal별 route 격리, migration·attribution, contract/spec 정합성
- 리뷰 코멘트: 코드레벨 종료 검토를 통과해 Milestone과 SDD를 archive했다.

## 범위 제외

- 동일 slot 또는 여러 slot의 자동 rotation, round-robin, quota 기반 선택과 failover
- 여러 provider를 하나의 외부 model alias 뒤에 숨기는 자동 vendor fallback
- billing/chargeback, 조직 역할 기반 IAM과 provider marketplace
- raw provider token을 caller header, tracked YAML, Node static config에 저장하는 방식
- 모든 provider 계정의 상시 구독과 정기 live smoke CI

## 작업 컨텍스트

- 관련 경로: `apps/control-plane`, `apps/client`, `apps/edge/internal/controlplane`, `apps/edge/internal/openai`, `apps/edge/internal/service`, `apps/node/internal/adapters`, `packages/go/config`, `proto/iop`, `configs/control-plane.yaml`, `configs/edge.yaml`, `agent-contract`
- 표준선(선택): Control Plane은 principal, IOP token과 provider credential의 canonical owner다. Edge는 유효 generation의 secret-free projection과 bounded credential lease만 소비하고 Edge-local provider runtime 상태의 원장은 계속 Edge다.
- 표준선(선택): credential slot은 자동 선택 풀이 아니라 명시적 route 대상이다. 하나의 slot은 여러 upstream model binding을 가질 수 있고, 같은 upstream model도 서로 다른 slot/route로 동시에 노출할 수 있다. provider resource load balancing은 같은 선택 slot을 유지하는 실행 위치 선택이며 token slot rotation이 아니다.
- 표준선(선택): raw IOP token은 발급 시 한 번만 반환하고 verifier만 저장한다. provider token은 복호화 가능한 ciphertext로 저장하며, authenticated confidential transport 안의 bounded request-local lease 외에는 Control Plane 밖으로 평문 전달하지 않는다.
- 선행 작업: [다중 Provider Protocol Profile과 Native Anthropic Messages](multi-provider-protocol-profile-native-messages.md)
- 후속 작업: 없음
- 확인 필요: 없음
