Merge branch 'feature/provider-usage-attribution-hot-path' into dev

This commit is contained in:
toki 2026-07-31 21:50:22 +09:00
commit 26feeff17c
8 changed files with 780 additions and 6 deletions

265
HANDOFF.md Normal file
View file

@ -0,0 +1,265 @@
# Handoff: Chronos Standalone Agent Runtime과 Node Domain-Agent Gateway
- 작성일: 2026-07-31
- 현재 타겟: 새 `Chronos` 프로젝트의 최종 아키텍처 확정안 검토와 이후 Milestone 설계 준비
- 다음 세션 첫 진입점: 이 문서
- 상태: 프로젝트명·저장소 경계·gateway 방향 확정, 새 저장소·구현·이전·로드맵 생성 미착수
- 기록 위치: Chronos 저장소가 아직 없으므로 이 파일만 `iop` 루트에 임시 handoff로 둔다. Chronos 초기화 후 canonical handoff와 Roadmap은 Chronos로 옮긴다.
## 사용자 확정 사항
1. 현재 `/config/workspace/iop-s0``IOP Agent CLI Runtime` Milestone은 기존 범위대로 끝까지 완료한다. 지금 구조를 중간에 재편하거나 새 원격 gateway 범위를 끌어오지 않는다.
2. `agentic-framework`는 문서·셸 중심의 가벼운 공통 agent-ops 프레임워크로 그대로 유지한다. 어디든 설치 가능한 현재 성격을 보존하고 application runtime을 추가하지 않는다.
3. 새 독립 프로젝트의 이름은 `Chronos`로 확정한다. 저장소·CLI·daemon의 기본 이름은 각각 `chronos`, `chronos`, `chronosd`로 사용한다.
4. 아래의 단계 2 이후 작업은 새 `Chronos` 프로젝트를 장기 기준 저장소이자 Roadmap/Milestone source of truth로 삼는다.
5. 다음 세션은 우선 Chronos의 최종 아키텍처와 책임 경계만 확정한다. 이 handoff에서는 새 저장소, Milestone, 구현 plan, 코드 이전을 생성하지 않는다.
6. 확정안이 정리된 뒤 Chronos 저장소에 Roadmap/Milestone을 만들고, IOP·OTO 코드 변경은 해당 Milestone이 가리키는 cross-repo 구현 작업으로 관리한다.
## 프로젝트 이름과 상징
프로젝트명은 **Chronos**로 확정한다.
사용자가 기존 skill/runtime에 일을 맡겨 실제로 얻은 가장 큰 가치는 자신의 시간이 크게 늘어난 것이다. Chronos는 단순 scheduler 명칭이 아니라 다음 경험을 상징한다.
> 일의 시간을 Chronos에게 맡기고, 내 시간을 되찾는다.
Chronos가 작업을 `Plan → Work → Review → Recovery` 순서로 계속 진행하는 동안 사용자는 작업을 상시 감시하지 않는다. 이름은 체계적으로 흐르는 작업 시간과 사용자에게 반환되는 시간을 함께 뜻한다.
- 영문 문구: `Chronos — Take your time back.`
- 한국어 문구: `일은 맡기고, 시간은 되찾다.`
- 저장소 기본명: `chronos`
- CLI 기본명: `chronos`
- daemon 기본명: `chronosd`
- runtime package/product family: `chronos-runtime`
- Node bridge kind 후보: `chronos-agent`
동명의 scheduler·workflow·AI 제품이 존재한다는 점은 인지하고 선택했다. 내부/초기 프로젝트명은 `Chronos`로 유지하고, 공개 배포 시점에만 조직 prefix, package namespace, domain·상표 충돌을 별도 검토한다. 다음 세션이 충돌만을 이유로 이름을 다시 열지 않는다.
## 최종 방향
현재 `/config/workspace/iop-s0`에서 완성할 `iop-agent` 구현을 선행 source로 삼아, 완료 후 standalone daemon/runtime의 제품 소유권을 새 Chronos 프로젝트로 이전한다. Chronos는 Node에 내장하지 않는다. 로컬 사용에서 Node는 필수가 아니다. IOP 관리 환경에서만 Node가 선택적 `domain-agent gateway`가 되어 기존 outbound Edge 연결과 로컬 Chronos 연결을 중계한다.
```text
Local standalone
CLI / Skill / Flutter / Unity
↕ versioned local control
Chronos daemon (`chronosd`)
workflow runtime과 durable state
IOP managed
Control Plane → Edge → 기존 Node outbound session
Node agent_bridge gateway
↕ local typed connection
동일한 Chronos daemon
```
책임은 다음과 같이 고정한다.
| 소유자 | 책임 |
|---|---|
| `agentic-framework` | 어디든 설치 가능한 agent-ops 공통 규칙·skill·sync framework. Chronos runtime을 포함하지 않음 |
| `Chronos` | standalone runtime core, CLI/daemon, local control, workflow adapter, durable state/replay, scoped execution, Roadmap lifecycle 조합 |
| IOP Node | 로컬 agent discovery/registration, capability·health, admission, request correlation, bounded relay, timeout/backpressure, Edge 연결 중계 |
| IOP Edge/Control Plane | 원격 principal authorization, Node/agent routing, command/event summary, audit와 운영 표면 |
| OTO | pipeline/job/artifact/log 의미와 실행 상태의 원본 |
| Flutter/Unity | runtime client. CLI를 감싸지 않고 versioned local proto-socket 계열 계약을 직접 사용 |
Node는 workflow artifact, Plan/Review 해석, project state, OTO job state의 원본을 소유하지 않는다. Node 또는 Edge 연결이 끊겨도 이미 수락된 standalone 작업은 계속되어야 한다.
## Provider 경계
외부에서는 하나의 provider/resource 계열로 발견할 수 있지만, 기존 model/CLI provider와 같은 실행 의미로 합치지 않는다.
```text
agent_bridge provider framework
├─ kind: chronos-agent
└─ kind: oto-runner
```
공유 가능한 것은 다음 lifecycle뿐이다.
- versioned registration과 stable instance identity
- capability catalog와 availability/health
- command correlation과 idempotency
- ordered event, result, cancel/stop
- disconnect/reconnect와 snapshot/replay
- capacity, timeout, bounded queue와 audit metadata
Chronos의 Plan/Review/Milestone 상태와 OTO의 pipeline/job/artifact payload는 kind별 typed driver가 소유한다. 자유형 terminal output, model prompt/delta, HTTP `ProviderTunnel` body로 변환하지 않는다.
Node의 기존 terminal/CLI 기능은 설치·bootstrap·업데이트·비상 진단 후보일 뿐 정상 제어면이 아니다. `chronosd`를 terminal에서 실행하고 stdout을 파싱하는 구조는 singleton ownership, command correlation, cancel/resume, event ordering과 crash recovery를 중복 구현하게 하므로 폐기한다.
## 제품 사용 표면
같은 runtime을 다음 범위로 독립 사용 가능해야 한다.
- Plan/Review cycle만 실행
- 하나의 Milestone 범위만 실행
- 여러 Milestone을 포함한 전체 Roadmap lifecycle 실행
- 로컬 CLI에서 수동 시작·상태·중단·재개
- agent용 Skill이 CLI 또는 안정된 client interface를 통해 같은 기능 사용
- Flutter/Unity가 local control 계약으로 상태·event·control 사용
- IOP 관리 환경에서 Node gateway를 통한 선택적 원격 상태·제어
Plan/Review cycle의 상태 의미와 artifact 규칙은 공통 core가 소유한다. 실행 위치에 따라 adapter를 분리한다.
- 로컬 workflow: plan, work, review를 동일 사용자 장비의 standalone runtime이 수행한다.
- remote user-agent workflow: plan, work, review 요청과 결과가 모두 원격 사용자 agent를 통과한다. 공통 cycle을 사용하지만 transport, executor, retry, attention/승인 경로는 별도 adapter다.
따라서 실행 지점이 같다는 이유로 두 workflow를 하나의 pipeline 구현으로 강제하지 않는다. 공통 core는 cycle state와 transition을 제공하고, local/remote adapter가 각 수행 방식을 제공한다.
## OTO에서 흡수할 것과 버릴 것
OTO에서 제품화할 핵심은 `agent가 outbound 장기 session으로 등록 → capability 보고 → server push 수신 → heartbeat/report`하는 연결 패턴이다.
흡수한다.
- session abstraction
- protocol/capability version registration
- heartbeat와 disconnect 처리
- duplicate connection 교체
- server-push command와 typed report
- execution ownership 검사
그대로 가져오지 않는다.
- legacy OTO→IOP Edge direct registration code
- OTO domain proto를 Chronos에도 공통 적용
- 빈 값 여부만 확인하는 enrollment token
- TLS, reconnect/backoff, 실제 cancel 집행이 빠진 현재 한계
- Node가 OTO scheduler나 artifact/log store가 되는 구조
OTO와 Chronos는 같은 `agent_bridge` framework 아래 서로 다른 driver/instance로 둔다.
## 로컬 연결과 보안 경계
현재 iop-agent local control에서 검증 중인 Unix socket `0600`, owner-only state root `0700`, 동일 effective UID peer 경계를 Chronos 이전 후에도 보존한다. 이 경계를 원격 통합을 위해 느슨하게 만들지 않는다.
초기 후보는 두 단계다.
1. 같은 사용자 MVP: Node companion/connector가 Chronos의 owner-only local socket을 사용한다.
2. system Node 또는 다중 사용자 제품형: 사용자 agent가 Node가 소유한 local gateway로 outbound 등록하고 session을 유지한다. Unix domain socket/Windows named pipe가 목표이며, 공통 transport가 준비되지 않은 초기 구현은 `127.0.0.1` only + ephemeral port + short-lived credential을 사용할 수 있다.
어느 경우든 사용자 장비에 외부 inbound port를 추가하지 않는다. 원격 traffic은 기존 Node→Edge outbound session 하나로 multiplex한다.
production remote mutation 전 필수 gate:
- EdgeNode transport authentication/confidentiality
- remote principal → local owner/project/workspace scope authorization
- operation allowlist와 audit
- stable `command_id`를 이용한 duplicate convergence
- ordered event relay와 cursor replay
- replay 범위를 벗어나면 fresh snapshot으로 복구
- `node online`, `bridge connected`, `agent available`, `project running` 상태 구분
- 원격 UI start/focus와 임의 shell/path/protobuf forwarding 기본 금지
현재 EdgeNode transport에는 mTLS helper가 실제 transport에 연결되지 않았으므로, 이 gate 전에는 production `project.start/stop/resume`을 열지 않는다.
## 보류·분리 항목
- local LLM 감시/advisor는 현시점 over-spec으로 보류한다. 결정적 runtime monitoring에는 LLM을 넣지 않는다.
- remote terminal은 별도 기능이다. Node agent gateway와 합치지 않는다.
- Flutter/Unity는 CLI 제어가 아니라 proto-socket 계열 계약을 사용한다.
- remote coding 유지보수는 별도 Desktop Agent를 만들지 않고 향후 Chronos의 remote user-agent workflow adapter로 흡수한다.
- Node가 Chronos process, workflow, durable state를 기본 소유하거나 Edge reconnect 시 종료시키지 않는다.
- direct specialized agent→Edge protocol은 현재 기본 경로로 부활시키지 않는다.
- Node와 Chronos의 겹쳐 보이는 코드를 성급히 공통 package로 추출하지 않는다. shared contract/SDK만 먼저 고정하고 실제로 host-neutral한 구현 경계가 증명된 뒤 추출한다.
## 단계 기준
이전 대화에서 사용한 번호는 다음을 뜻한다.
1. `/config/workspace/iop-s0`의 현재 `IOP Agent CLI Runtime` 완료
2. `Agent Runtime Ownership Extraction`
- 완료된 standalone runtime 소유권을 새 Chronos 프로젝트로 이전
- 제품·CLI·daemon 명칭을 `chronos`, `chronos`, `chronosd`로 정리
- local CLI/socket/offline 사용성 보존
- versioned control contract/SDK 경계 고정
3. `Scoped Agent Task Execution Surface`
- Plan/Review, Milestone, Roadmap 범위별 독립 실행과 종료 경계
4. `Node External Agent Provider Foundation`
- `agent_bridge` registration, discovery, health, typed command/event/replay
5. `Edge Managed Agent Routing & Security`
- EdgeNode remote control wire, authorization, audit, reconnect
6. `Roadmap Lifecycle Orchestration`
- 3번 scope를 조합하되 작은 범위 사용성을 보존
7. `OTO Provider Adapter`
8. `Remote User-Agent Workflow Bridge`
2번 이후 Roadmap/Milestone은 새 Chronos 저장소에서 관리한다. 4, 5, 7번은 구현 파일이 IOP/OTO에 있더라도 Chronos Milestone에서 cross-repo 대상과 검증을 명시한다.
3번과 6번의 local lifecycle 설계는 Node gateway와 독립적으로 진행할 수 있다. 원격 mutation만 5번 보안 gate를 선행한다.
## 다음 세션 실행 순서
1. 이 문서와 아래 `필수 탐색 경로`만 먼저 읽는다.
2. `agentic-framework`는 변경하지 않고 경량 공통 프레임워크로 유지한다. Chronos runtime, Roadmap, project rule을 이 저장소에 추가하지 않는다.
3. 새 Chronos 저장소의 위치·module namespace·초기 scaffold는 최종 아키텍처 확정 뒤 결정한다. 아직 `/config/workspace/chronos`가 존재한다고 가정하지 않는다.
4. 최종 아키텍처 문서에서 다음을 결정 가능한 형태로 고정한다.
- component/process topology
- repository와 package ownership
- versioned contract source와 generated SDK 배포
- local/managed deployment mode
- identity/authorization
- command/event/replay/cancel failure semantics
- Node/Edge/agent disconnect 시 lifecycle
5. 사용자에게 최종안을 확인받은 뒤 Chronos 저장소를 초기화하고 `create-roadmap`을 사용해 2번 이후 Milestone을 생성한다.
6. Chronos 저장소가 생기면 이 문서의 확정 사항을 그 저장소의 canonical handoff/architecture 입력으로 이전한다. `agentic-framework`에는 Chronos runtime·Roadmap을 남기지 않는다.
7. 현재 `/config/workspace/iop-s0` checkout은 Milestone 완료 정합화 전까지 수정, 이동, 정리하지 않는다. 1번 완료 evidence가 생긴 뒤 extraction 범위를 산정한다.
## 필수 탐색 경로
### 유지할 `agentic-framework` 경계
- [`README.md`](README.md): 현재 저장소가 app runtime이 아닌 agent-ops 공통 원본이라고 명시한다. 저장소 역할 확장은 의식적인 결정이어야 한다.
- [`agent-ops/rules/common/philosophy.md`](agent-ops/rules/common/philosophy.md): runtime과 LLM 책임, Roadmap과 실행 상태 경계.
- [`agent-ops/bin/sync.sh`](agent-ops/bin/sync.sh): push 대상은 `agent-ops` 공통 영역으로 제한된다. Chronos는 이 sync payload가 아니라 별도 소비 프로젝트다.
### 현재 `iop-agent` 구현과 계약
- [`../iop-s0/agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md`](../iop-s0/agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md): 현재 Milestone 범위, 제외 항목, 완료 전 남은 작업.
- [`../iop-s0/agent-contract/inner/iop-agent-cli-runtime.md`](../iop-s0/agent-contract/inner/iop-agent-cli-runtime.md): standalone/local control 계약.
- [`../iop-s0/proto/iop/agent.proto`](../iop-s0/proto/iop/agent.proto): typed envelope, `command_id`, snapshot, event sequence와 replay.
- [`../iop-s0/apps/agent/internal/localcontrol/server.go`](../iop-s0/apps/agent/internal/localcontrol/server.go): Unix socket, permission, same-UID peer credential 경계.
- [`../iop-s0/apps/agent/internal/localcontrol/service.go`](../iop-s0/apps/agent/internal/localcontrol/service.go): status와 project start/stop/resume port.
- [`../iop-s0/apps/agent/internal/taskloop/workflow.go`](../iop-s0/apps/agent/internal/taskloop/workflow.go): agent-ops Plan/Review/Milestone artifact 의존성이 집중된 workflow adapter.
- [`../iop-s0/packages/go/agentruntime/types.go`](../iop-s0/packages/go/agentruntime/types.go): 기존 유한 실행 Provider와 agent durable control의 의미 차이.
### IOP Node/Edge gateway 후보
- [`../iop-s0/apps/node/README.md`](../iop-s0/apps/node/README.md): 기존 EdgeNode transport, logical session, mTLS 미연결 상태.
- [`../iop-s0/proto/iop/runtime.proto`](../iop-s0/proto/iop/runtime.proto): `RunRequest`, `RunEvent`, `NodeCommand`, `ProviderTunnel`; 새 durable agent control을 억지로 넣지 않아야 하는 기존 wire.
- [`../iop-s0/apps/node/internal/transport/session.go`](../iop-s0/apps/node/internal/transport/session.go): 기존 단일 EdgeNode session의 message family multiplex.
- [`../iop-s0/proto/iop/control.proto`](../iop-s0/proto/iop/control.proto): `EdgeDomainAgentSummary`, `EdgeCommandRequest/Response/Event` scaffold.
- [`../iop-s0/apps/edge/internal/service/status_provider.go`](../iop-s0/apps/edge/internal/service/status_provider.go): `GetDomainAgents()`가 현재 비어 있는 integration point.
- [`../iop-s0/apps/edge/internal/service/control_command.go`](../iop-s0/apps/edge/internal/service/control_command.go): 현재 `agent.command`가 제한적 scaffold인 상태.
- [`../iop-s0/agent-roadmap/phase/control-plane-portal-ops/milestones/multi-edge-operations.md`](../iop-s0/agent-roadmap/phase/control-plane-portal-ops/milestones/multi-edge-operations.md): OTO/build-deploy를 Edge-owned domain-agent summary로 노출한다는 기존 결정.
- [`../iop-s0/agent-roadmap/phase/automation-runtime-bridge/milestones/remote-terminal-bridge-poc.md`](../iop-s0/agent-roadmap/phase/automation-runtime-bridge/milestones/remote-terminal-bridge-poc.md): remote terminal을 별도 기능으로 유지하는 경계.
### OTO 연결 패턴
- [`../oto/proto/oto/runner.proto`](../oto/proto/oto/runner.proto): registration, capability, heartbeat, push run/cancel, report 계약.
- [`../oto/apps/runner/lib/oto/agent/registration_client.dart`](../oto/apps/runner/lib/oto/agent/registration_client.dart): 현재 outbound session abstraction.
- [`../oto/apps/runner/lib/oto/agent/agent_runner.dart`](../oto/apps/runner/lib/oto/agent/agent_runner.dart): push job loop와 현재 cancel 한계.
- [`../oto/apps/runner/lib/oto/agent/edge_registration_client.dart`](../oto/apps/runner/lib/oto/agent/edge_registration_client.dart): legacy direct IOP Edge client임을 파일 자체가 명시한다.
- [`../oto/services/core/internal/runnersocket/server.go`](../oto/services/core/internal/runnersocket/server.go): runner registry, push, duplicate connection과 report ownership 패턴.
- [`../oto/services/core/internal/runnerregistry/registry.go`](../oto/services/core/internal/runnerregistry/registry.go): capability/version 검사와 현재 enrollment 검증 한계.
### 보조 컨텍스트
- 이전 Codex context ID: `019fb30f-08e6-7643-bc73-ef72a3199dcb`
- 위 context를 조회할 수 있으면 보조 근거로만 사용한다. 이 handoff의 사용자 확정 사항과 책임 경계를 우선한다.
## 작업 상태와 검증
- 이번 세션에서 구현, migration, Milestone/SDD/contract 생성은 하지 않았다.
- `/config/workspace/agentic-framework``main`, 기준 commit `afd34c3`이며 handoff 이동 후 clean 상태다.
- `/config/workspace/iop``feature/provider-usage-attribution-hot-path`이며 이동 전 clean 상태였다. 현재 변경은 untracked `HANDOFF.md` 한 건뿐이다.
- 새 Chronos 저장소는 아직 생성하지 않았다.
- `/config/workspace/iop-s0``feature/iop-agent-cli-runtime`, HEAD `4e420910`이며 원격 브랜치와 동일한 clean 상태다. Milestone 완료 정합화 전에는 해당 checkout을 임의로 수정하거나 이동하지 않는다.
- `/config/workspace/oto``master`, 기준 commit `ae2b14f`였고 확인 당시 clean 상태였다.
- 문서 작업이므로 코드 테스트는 실행하지 않는다. `git diff --check`만 검증한다.

View file

@ -19,14 +19,15 @@ IOP(Inference Operations Platform)는 Control Plane - Edge - Node 계층 구조
내부 실행 모델은 `adapter + target`을 기준으로 하며, Edge가 로컬 실행 그룹의 상태와 라우팅을 소유하고 Control Plane은 Edge를 통해 시스템을 관찰하고 제어한다.
IOP는 NomadCode에 종속된 Agent Shell이 아니라, NomadCode와 외부 agent, 운영 CLI, client, 자동화 도구가 함께 소비할 수 있는 범용 추론/자동화 운영 엔진이다.
로드맵 전반에서 OpenAI-compatible API는 외부 클라이언트의 모델 기반 호출 표면으로, A2A API는 외부 agent의 agent-to-agent 작업 위임 표면으로, IOP native protocol은 운영 제어, logical session, background run, command, lifecycle event, remote terminal session 같은 IOP 고유 기능의 기준으로 둔다.
로드맵 전반에서 OpenAI-compatible API와 Anthropic-compatible Messages API는 외부 클라이언트의 모델 기반 호출 표면으로, A2A API는 외부 agent의 agent-to-agent 작업 위임 표면으로, IOP native protocol은 운영 제어, logical session, background run, command, lifecycle event, remote terminal session 같은 IOP 고유 기능의 기준으로 둔다.
OpenAI-compatible API는 현재 chat completions baseline을 넘어 Responses API 호환 표면까지 지원해야 한다.
Anthropic-compatible Messages API는 Edge가 직접 제공해 Claude Code를 포함한 client가 별도 agent-client gateway 없이 IOP를 호출하게 하며, Chat-only upstream은 IOP의 protocol bridge로 연결한다.
IOP의 외부 실행 호출 계약은 OpenAI-compatible API 방식을 기본 표면으로 채택하고, IOP 고유의 workspace, session, agent, approval, artifact, notification 의미는 별도 `iop` wrapper field가 아니라 `metadata` 또는 IOP native endpoint의 명시 필드로 전달한다.
IOP native protocol은 proto-socket을 기본으로 하며, HTTP는 OpenAI-compatible/A2A/health/bootstrap처럼 필요한 경계에서만 사용한다.
A2A는 표면으로 유지하되, NomadCode가 A2A를 도입하는 시점은 현재 확정하지 않는다.
현재 제1 active delivery는 NomadCode가 IOP를 실행 백엔드로 사용할 수 있도록 OpenAI-compatible Responses 요청의 `metadata.workspace`, task/source metadata, 내부 workspace-bound agent 실행 경로를 먼저 안정화하는 것이다.
모델 선택, 로컬/클라우드 라우팅, 모델별 profile, token/속도/품질 최적화, 모델 호출 로그와 품질 평가는 IOP 책임으로 둔다.
모델 선택, 로컬/클라우드 라우팅, 모델별 profile, token/속도/품질 최적화, 모델 호출 로그와 품질 평가는 IOP 책임으로 둔다. Control Plane은 principal과 IOP token, 사용자별 provider credential slot의 원장을 소유하고 Edge는 principal별 route와 제한된 credential lease를 실행에 사용한다.
또한 원격지와 로컬의 Ollama, vLLM, SGLang, Lemonade 같은 추론 엔진은 단순 endpoint가 아니라 provider/device/model 조합으로 관리하고, provider별 lifecycle capability, device 상태, 모델 qualification, 테스트 결과 리포트를 운영 데이터로 축적하는 방향을 목표로 한다.
특히 로컬 모델을 우선 활용하되, cloud fallback과 품질 평가를 결합해 엔터프라이즈 모델 서비스에 가까운 운영 품질을 목표로 한다.
RAG, context 구성/압축, web search, MCP 정책, tool policy, output validation, retry/fallback은 기본 모델 서빙과 부하 라우팅이 가능해진 뒤 확장한다.
@ -67,7 +68,7 @@ Phase는 실행 순서가 아니라 도메인/책임 영역의 구조적 지도
- [진행중] 운영 관측과 Provider 관리
- 경로: [PHASE.md](phase/operational-observability-provider-management/PHASE.md)
- 요약: 사용자/토큰/사용량/로그 추적과 API/CLI/local inference provider catalog, 로컬 디바이스 provider 상태 관리, provider/device/model qualification report와 모델 lifecycle 관리 방향을 MVP 운영 축과 후속 심화 축으로 스케치한다.
- 요약: 사용자/IOP token/provider credential/사용량/로그 추적과 cloud API protocol profile, native Messages, API/CLI/local inference provider catalog, 로컬 디바이스 provider 상태 관리, provider/device/model qualification report와 모델 lifecycle 관리 방향을 MVP 운영 축과 후속 심화 축으로 스케치한다.
- [진행중] Update Plane과 자체 업데이트 기반
- 경로: [PHASE.md](phase/update-plane-self-update-foundation/PHASE.md)

View file

@ -6,9 +6,10 @@
## 목표
IOP가 여러 Edge, Node, CLI Agent, local inference provider를 운영할 때 필요한 사용자/토큰/사용량/로그/provider 상태 관찰 기준을 정리한다.
IOP가 여러 Edge, Node, CLI Agent, local inference provider와 cloud API provider를 운영할 때 필요한 사용자/토큰/credential/사용량/로그/provider 상태 및 protocol profile 기준을 정리한다.
이 Phase는 완성된 billing, enterprise IAM, provider marketplace를 바로 구현하지 않고, 1차 MVP에서 어떤 운영 데이터를 모으고 어떤 화면/명령으로 검토할지 스케치한다.
provider 확장 Phase에서 검증한 Ollama, vLLM, SGLang, Lemonade 같은 추론 엔진은 provider/device/model 조합으로 관찰하고, 후반부에서는 모델 lifecycle capability와 qualification report를 운영 데이터로 축적하는 방향을 정리한다.
cloud API provider는 Chat Completions 공통 profile과 Edge native Anthropic Messages 표면으로 수렴시키며, Control Plane이 principal token과 사용자별 provider credential slot의 원장을 소유한다.
## Milestone 흐름
@ -58,6 +59,14 @@ Phase를 가로지르는 실제 다음 작업 선택은 [전역 마일스톤 실
- 경로: [node-provider-execution-liveness-recovery](milestones/node-provider-execution-liveness-recovery.md)
- 요약: Node가 provider-originated 진행 신호의 5분 무응답을 request stall로 판정하고 provider health와 local attempt fence를 별도 확정하며, ingress recovery owner가 미커밋 요청만 기존 공통 budget 안에서 재실행한다.
- [계획] 다중 Provider Protocol Profile과 Native Anthropic Messages
- 경로: [multi-provider-protocol-profile-native-messages](milestones/multi-provider-protocol-profile-native-messages.md)
- 요약: OpenAI Chat Completions를 cloud provider 공통 driver로 두고 provider별 endpoint/path/auth/capability를 profile로 흡수하며, Edge가 Anthropic Messages 입력과 Chat bridge를 직접 소유해 Claude Code의 agent-client dependency를 제거한다.
- [계획] 사용자별 Provider Credential Slot과 Alias Routing
- 경로: [principal-provider-credential-slot-routing](milestones/principal-provider-credential-slot-routing.md)
- 요약: Control Plane을 IOP principal token과 provider credential의 원장으로 두고, 사용자/vendor별 여러 token slot과 optional alias를 명시적 model route에 결합해 선택된 credential만 안전하게 실행 경계에 주입한다.
- [스케치] 요청 실행 로그와 Usage Ledger 기반
- 경로: [request-execution-log-usage-ledger-foundation](milestones/request-execution-log-usage-ledger-foundation.md)
- 요약: 사용자 요청 하나의 device/provider/model 선택, queue/dispatch/start/first-token/end 시간, token breakdown, status/error를 구조화된 실행 로그와 usage ledger로 남기는 로그 시스템 개편 후보를 스케치한다.
@ -72,8 +81,8 @@ Phase를 가로지르는 실제 다음 작업 선택은 [전역 마일스톤 실
## Phase 경계
- Control Plane은 Edge/Node 상태를 보기 쉽게 연결하지만 Edge 내부 상태의 canonical store가 되지 않는다.
- provider adapter 구현과 endpoint별 serving 검증은 `추론 서버 provider 확장` Phase 책임으로 둔다.
- Control Plane은 principal, IOP token과 외부 provider credential의 canonical store를 소유한다. Edge/Node provider health, capacity, queue와 실행 중 상태의 canonical store는 계속 Edge이며 Control Plane이 복제 소유하지 않는다.
- Ollama/vLLM/SGLang/Lemonade 같은 local inference adapter 구현과 serving 검증은 `추론 서버 provider 확장` Phase의 완료 기준을 유지한다. cloud API protocol/profile, public Chat/Messages compatibility와 credential routing은 이 Phase 책임으로 둔다.
- provider/device/model qualification report는 provider serving path와 capacity/concurrency 기준선이 잡힌 뒤 이 Phase의 후반부에서 다룬다.
- 이 Phase는 운영 데이터와 제어 표면의 MVP 경계를 다루며, billing/chargeback, 조직 IAM, 상세 audit schema, 장기 retention 정책은 후속 구체화에서 결정한다.
- 누적 요청 컨텍스트 최적화, RAG, advisor, Context Hook, output validation 실행 모드는 `지식과 도구 최적화 확장` Phase 책임으로 둔다.

View file

@ -0,0 +1,95 @@
# Milestone: 다중 Provider Protocol Profile과 Native Anthropic Messages
## 위치
- Roadmap: [ROADMAP.md](../../../ROADMAP.md)
- Phase: [PHASE.md](../PHASE.md)
## 목표
OpenAI Chat Completions를 공통 cloud provider 실행 기준으로 두고 provider별 차이는 선언형 protocol profile overlay와 제한된 확장 driver로 흡수한다. Edge가 Anthropic Messages 입력과 응답 변환을 직접 소유하여 Claude Code가 `agent-client` gateway 없이 IOP를 호출하게 하고, 기존 Responses driver와 표면은 선택적 capability로 유지하되 공통 provider 완료 기준에는 포함하지 않는다.
## 상태
[계획]
## 승격 조건
- 없음
## 구현 잠금
- 상태: 해제
- SDD: 필요
- SDD 문서: [SDD.md](../../../sdd/operational-observability-provider-management/multi-provider-protocol-profile-native-messages/SDD.md)
- SDD 사유: 외부 HTTP/SSE 계약, provider별 endpoint/path/auth/capability profile, Anthropic↔OpenAI 변환과 Edge-Node tunnel wire를 함께 변경한다.
- 잠금 해제 조건:
- [x] SDD 잠금이 해제되어 있다.
- [x] SDD 사용자 리뷰가 없거나 승인/해결되었다.
- [x] Acceptance Scenario가 Milestone 기능 Task와 연결되어 있다.
- [x] Evidence Map이 완료 시 `Roadmap Completion`과 최종 검증 evidence로 검증 가능하게 연결되어 있다.
- 결정 필요: 없음
## 범위
- `openai_chat`, `anthropic_messages`와 기존 선택적 `openai_responses` driver가 공유하는 endpoint/path, auth header, request option, capability provider profile schema
- 공통 base profile을 상속해 endpoint/capability/extension만 덮어쓰고 runtime에서는 완전한 immutable profile로 해소되는 선언형 profile overlay
- OpenAI, Gemini, Anthropic, GLM, Kimi, MiniMax, MiMo, Grok, Seulgi GPT의 Chat Completions profile
- Anthropic, MiniMax, MiMo, Seulgi Claude처럼 native Messages를 제공하는 upstream의 `anthropic_messages` profile
- Edge의 canonical `POST /v1/messages`, `POST /v1/messages/count_tokens`, Anthropic 방식 model discovery와 기존 `/anthropic/v1/*` client 설정을 위한 migration alias
- Anthropic Messages와 OpenAI Chat Completions 사이의 system/content/tool/thinking/usage/error/stream 변환
- 기존 `openai_compat`, `seulgivibe_claude`, `seulgivibe_openai` type/config의 호환 migration
- 반복 가능한 fixture/contract test와 provider profile 구현 시 대표 모델 1개로 수행하는 일회성 live smoke
## 기능
### Epic: [protocol-core] 공통 Protocol Driver와 Profile
provider별 차이를 경로 분기 코드가 아니라 검증 가능한 profile과 protocol driver 경계로 수렴시킨다.
- [ ] [profile-contract] `openai_chat`, `anthropic_messages`와 기존 선택적 `openai_responses` driver가 공통 provider profile의 endpoint, operation path, auth declaration, model mapping, capability와 extension을 소비하고 base profile overlay를 immutable runtime profile로 해소하도록 config/runtime 계약을 만든다. 검증: overlay cycle과 잘못된 protocol/path/auth/capability 조합은 config load에서 거부되고 legacy type alias는 동일 runtime profile로 정규화된다.
- [ ] [path-resolution] base URL에 `/v1`, `/v1beta/openai`, `/api/paas/v4` 같은 provider prefix가 있어도 operation path를 중복 결합하지 않는 profile 기반 URL resolver를 적용한다. 검증: OpenAI, Gemini, GLM, Kimi, MiniMax, MiMo, Grok, Seulgi fixture의 최종 URL이 기대값과 일치한다.
- [ ] [profile-catalog] OpenAI, Gemini, Anthropic, GLM, Kimi, MiniMax, MiMo, Grok와 Seulgi의 Chat/Messages 지원 범위를 base profile+overlay catalog와 custom profile 예시로 제공한다. 검증: 각 built-in/custom profile이 공통 schema validation과 request fixture를 통과하고 미지원 operation은 dispatch 전에 명시적으로 거부된다.
- [ ] [contract-sync] provider profile config, Edge-Node tunnel operation과 public Chat/Messages 표면의 inner/outer 계약 및 현재 구현 spec을 동기화한다. 검증: contract/spec index와 구현 경로가 새 source of truth를 가리키고 중복 계약 원문이 남지 않는다.
### Epic: [messages-ingress] Edge Native Anthropic Messages
Claude Code가 IOP Edge를 Anthropic-compatible endpoint로 직접 사용하고 대상 profile에 따라 native passthrough 또는 Chat 변환을 수행하게 한다.
- [ ] [messages-surface] Edge에 canonical `POST /v1/messages`, `POST /v1/messages/count_tokens`, `GET /v1/models` Anthropic variant와 `/anthropic/v1/*` migration alias를 추가하고 IOP principal auth를 모든 표면에 공통 적용한다. 검증: `ANTHROPIC_BASE_URL`과 auth token/helper를 사용한 Claude Code 형태의 non-stream/stream/count-tokens/model-list fixture가 `agent-client` 프로세스 없이 Edge에 도달하고 `anthropic-version``/v1/models` 응답 schema를 명확히 선택한다.
- [ ] [messages-native] `anthropic_messages` upstream은 request/response/error/SSE body 의미를 보존하는 raw tunnel로 전달한다. 검증: Anthropic, MiniMax, MiMo, Seulgi Claude fixture에서 header allowlist, model rewrite, content block ordering, terminal event가 보존된다.
- [ ] [messages-chat-bridge] `openai_chat` upstream에는 Anthropic system/content/tool/tool-result/tool-choice/thinking/usage/error와 streaming event를 양방향 변환한다. 검증: text-only, mixed content, tool round-trip, reasoning, stop reason, usage와 provider error golden fixture가 Anthropic 응답 계약으로 수렴한다.
- [ ] [responses-regression] 기존 `/v1/responses` passthrough/normalized driver, body/stream 계약과 `agent-client/pi` 소비 동작을 선택적 capability로 유지한다. 검증: 기존 Responses route/stream/provider-auth 회귀 테스트가 통과하며 새 provider 공통 인증 기준에는 Responses live smoke가 요구되지 않는다.
### Epic: [qualification] Provider Qualification
지속 구독 없이 profile 구현 품질을 재현 가능한 fixture와 제한된 실호출 근거로 남긴다.
- [ ] [fixture-suite] 각 provider profile의 URL, header, model rewrite, non-stream/stream parsing과 오류 mapping을 credential-free deterministic fixture로 검증한다. 검증: 전체 fixture/contract suite가 네트워크와 provider 구독 없이 반복 실행된다.
- [ ] [live-smoke] 새 profile을 구현하거나 protocol profile이 실질적으로 바뀔 때에만 제공된 credential로 대표 모델 1개를 일회성 smoke하고 model/revision/date/result를 qualification evidence로 남긴다. 검증: live smoke는 opt-in이며 일반 local test와 CI에서 자동 실행되거나 모든 provider 구독을 요구하지 않는다.
## 완료 리뷰
- 상태: 없음
- 요청일: 없음
- 완료 근거: 계획 Milestone이며 기능 Task가 아직 충족되지 않았다.
- 검토 항목: Chat/Messages deterministic fixture, Claude Code direct Edge smoke, 기존 Responses 회귀와 provider별 일회성 qualification evidence를 확인한다.
- 리뷰 코멘트: 없음
## 범위 제외
- `/v1/responses` 공통 provider 표준화 또는 모든 provider에 대한 Responses 지원 강제
- provider credential의 저장, 사용자별 slot/alias 선택과 secret delivery
- provider 간 자동 fallback, token slot 자동 rotation/round-robin
- provider별 비공개 기능을 공통 schema에 강제로 승격하는 작업
- 상시 live provider CI와 모든 provider 계정/구독 유지
## 작업 컨텍스트
- 관련 경로: `apps/edge/internal/openai`, `apps/edge/internal/service`, `apps/node/internal/adapters`, `packages/go/config`, `proto/iop/runtime.proto`, `agent-client/claude`, `agent-client/pi`, `agent-contract`
- 표준선(선택): 공통 cloud API 기준은 Chat Completions이고 provider별 차이는 base profile+overlay로 흡수한다. runtime dispatch는 상속 관계가 아니라 검증 완료된 concrete profile만 소비하며, 공통 schema로 표현할 수 없는 차이만 protocol driver extension으로 제한한다.
- 표준선(선택): Anthropic Messages는 Edge가 직접 노출하며 native Messages upstream은 passthrough, Chat-only upstream은 Edge-owned bridge를 사용한다. `agent-client/claude`는 migration 참고 구현이지 최종 runtime dependency가 아니다.
- 표준선(선택): 기존 Responses 구현은 유지하지만 이 Milestone의 provider 공통 qualification baseline과 정기 live smoke 대상은 아니다.
- 선행 작업: 없음
- 후속 작업: [사용자별 Provider Credential Slot과 Alias Routing](principal-provider-credential-slot-routing.md)
- 확인 필요: 없음

View file

@ -0,0 +1,111 @@
# Milestone: 사용자별 Provider Credential Slot과 Alias Routing
## 위치
- Roadmap: [ROADMAP.md](../../../ROADMAP.md)
- Phase: [PHASE.md](../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 원장으로 수렴시킨다.
- [ ] [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에 남지 않는다.
- [ ] [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한다.
- [ ] [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으로 소유하고 명시적으로 선택하게 한다.
- [ ] [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이면 저장 또는 활성화를 거부한다.
- [ ] [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한다.
- [ ] [model-discovery] `/v1/models`의 OpenAI/Anthropic variant가 인증 principal에게 허용된 active route만 반환한다. 검증: principal별 list가 격리되고 disabled/revoked/expired slot과 충돌 alias가 노출되지 않으며 Claude Code가 반환 route를 선택할 수 있다.
- [ ] [explicit-selection] 요청 `model`은 principal별 route를 명시 선택하고 한 route 안에서 token slot을 자동 round-robin, fallback 또는 vendor 교차 대체하지 않는다. 검증: 같은 model의 두 slot 중 요청 route에 결합된 slot만 선택되고 그 slot 실패는 다른 slot의 암묵적 사용으로 이어지지 않는다.
- [ ] [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에 노출하지 않고 선택된 실행 경계에서만 사용한다.
- [ ] [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만 관찰된다.
- [ ] [secure-transport] credential을 수신하거나 운반하는 Client-Control Plane, Control Plane-Edge와 Edge-Node 연결에 peer 인증과 전송 기밀성을 강제한다. 검증: 평문/미인증/잘못된 peer 연결에서는 credential management와 cloud dispatch가 차단되고 TLS test identity로만 성공한다.
- [ ] [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에 없다.
- [ ] [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이 나타나지 않는다.
- [ ] [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 구독 없이 검증한다.
- [ ] [compat-migration] credential-plane 비활성 배포에서는 기존 `principal_tokens[]`/`provider_auth`가 동작하고, 활성 배포에서는 Control Plane projection이 유일한 원장이 되어 caller-supplied raw provider token을 명시적으로 거부한다. 검증: mode별 migration/rollback fixture에서 두 원장을 혼합하거나 IOP token을 provider token으로 재사용하지 않는다.
- [ ] [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에 포함되지 않는다.
- [ ] [slot-smoke] Seulgi를 포함한 vendor/model/slot fixture를 상시 실행하고 구현 시 제공된 대표 credential slot 1개로 Chat 또는 Messages E2E를 일회성 smoke한다. 검증: 일반 CI는 외부 credential/구독 없이 통과하고 live 결과에는 provider, model, slot reference, date, revision과 성공/실패만 남는다.
- [ ] [contract-ops] Control Plane-Edge/Edge-Node credential projection, public auth/model routing 계약, 운영 redaction/revocation 절차와 현재 구현 spec을 동기화한다. 검증: contract/spec index가 canonical 문서를 가리키며 raw secret이 문서 예시와 test artifact에 없다.
## 완료 리뷰
- 상태: 없음
- 요청일: 없음
- 완료 근거: 계획 Milestone이며 기능 Task가 아직 충족되지 않았다.
- 검토 항목: CP persistence/management, token/slot isolation, explicit alias selection, confidential transport와 credential lease, slot usage attribution, revocation, migration 및 대표 slot 일회성 smoke evidence를 확인한다.
- 리뷰 코멘트: 없음
## 범위 제외
- 동일 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)
- 후속 작업: 없음
- 확인 필요: 없음

View file

@ -87,3 +87,9 @@
28. [Node Provider 실행 Liveness 관측과 안전 복구](phase/operational-observability-provider-management/milestones/node-provider-execution-liveness-recovery.md)
Node가 5분간 provider 진행이 없는 request를 health와 분리 판정하고 local attempt를 fence한 뒤 기존 recovery owner가 안전한 요청만 공통 budget 안에서 재실행한다.
30. [다중 Provider Protocol Profile과 Native Anthropic Messages](phase/operational-observability-provider-management/milestones/multi-provider-protocol-profile-native-messages.md)
Chat Completions 공통 driver와 provider profile을 정리하고 Edge가 Anthropic Messages를 직접 흡수해 Claude Code가 agent-client 없이 IOP를 호출하게 한다.
31. [사용자별 Provider Credential Slot과 Alias Routing](phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
Control Plane이 principal token과 provider credential을 소유하고 사용자별 multi-token slot과 명시적 model route/alias를 안전하게 실행 credential로 연결한다.

View file

@ -0,0 +1,127 @@
# SDD: 다중 Provider Protocol Profile과 Native Anthropic Messages
## 위치
- Milestone: [Milestone 문서](../../../phase/operational-observability-provider-management/milestones/multi-provider-protocol-profile-native-messages.md)
- Phase: [PHASE.md](../../../phase/operational-observability-provider-management/PHASE.md)
## 상태
[승인됨]
## SDD 잠금
- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목: 없음
## 문제 / 비목표
- 문제: 현재 `openai_compat` adapter는 Chat/Responses operation path를 코드에서 조합하고 여러 vendor type을 같은 adapter type으로만 정규화하므로 Gemini의 `/v1beta/openai` 같은 base path, provider별 operation/auth/capability 차이를 안전하게 표현하기 어렵다. Anthropic Messages는 Edge에 없고 `agent-client/claude/iop-claude-gateway.py`가 Seulgi native passthrough와 OpenAI Chat 변환을 대신 소유해 Claude Code의 IOP 직접 호출 경로가 성립하지 않는다.
- 비목표:
- `/v1/responses`를 모든 provider가 구현해야 하는 공통 표준으로 승격하거나 기존 Responses pipeline을 재설계하는 일
- 사용자/provider credential 저장, slot alias, rotation과 secret delivery
- vendor 간 자동 fallback, quota 최적화와 provider marketplace
- provider별 beta/private 기능을 공통 request schema에 무손실로 강제 통합하는 일
## Source of Truth
| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | [Milestone 문서](../../../phase/operational-observability-provider-management/milestones/multi-provider-protocol-profile-native-messages.md) | 목표, provider 범위와 완료 Task |
| Public API | `apps/edge/internal/openai`, [OpenAI-compatible API 계약](../../../../agent-contract/outer/openai-compatible-api.md) | Chat/Responses 기존 표면과 새 Messages ingress host |
| Provider Runtime | `apps/edge/internal/service`, `apps/node/internal/adapters/openai_compat` | profile 선택, raw tunnel, URL/auth/model rewrite 실행 |
| Wire | `proto/iop/runtime.proto`, [Edge-Node Runtime Wire 계약](../../../../agent-contract/inner/edge-node-runtime-wire.md) | operation-neutral provider tunnel request/frame |
| Config | `packages/go/config`, [Edge Config/Refresh 계약](../../../../agent-contract/inner/edge-config-runtime-refresh.md) | protocol profile schema, alias normalization과 validation |
| Migration Evidence | `agent-client/claude/iop-claude-gateway.py`, `agent-client/pi/install.sh` | 현재 Messages bridge와 Responses 소비 동작의 참고 구현 |
| External Provider | [Claude API overview](https://platform.claude.com/docs/en/api/overview), [Claude Code LLM gateway 설정](https://docs.anthropic.com/en/docs/claude-code/llm-gateway), 각 provider 공식 API 문서와 live qualification record | profile별 operation/path/header/capability revision 기준 |
| User Decision | 2026-07-31 사용자 대화 | Chat Completions 공통 기준, Edge native Messages 흡수, Responses 현행 유지, 구현 시점 한정 live smoke |
## State Machine
| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| request/received | Chat, Messages, count-tokens, models 또는 기존 Responses 요청 수신 | request/authenticated / request/rejected | ingress method/path와 principal auth |
| request/authenticated | IOP principal 인증 성공 | request/route-resolved / request/rejected | request `model`, principal context와 model catalog |
| request/route-resolved | model route가 하나의 provider profile과 upstream model로 확정됨 | request/native-tunnel / request/bridge / request/rejected | profile protocol/capability snapshot |
| request/native-tunnel | ingress와 upstream protocol이 같고 operation이 지원됨 | response/streaming / response/terminal | raw body/header allowlist와 model rewrite |
| request/bridge | Anthropic Messages ingress가 `openai_chat` upstream을 선택함 | response/streaming / response/terminal / request/rejected | validated Messages↔Chat transform |
| request/rejected | path, model, capability, content block 또는 auth가 유효하지 않음 | response/terminal | public protocol별 typed error |
| response/streaming | upstream response start 뒤 body/event가 도착함 | response/streaming / response/terminal | protocol driver parser와 ordered emitter |
| response/terminal | complete, provider error, parse error, cancel 또는 timeout 확정 | 없음 | ingress protocol의 exactly-once terminal |
## Interface Contract
- 계약 원문: 기존 Chat/Responses는 [OpenAI-compatible API 계약](../../../../agent-contract/outer/openai-compatible-api.md), provider tunnel은 [Edge-Node Runtime Wire 계약](../../../../agent-contract/inner/edge-node-runtime-wire.md)을 따른다. 구현 시 native Messages 원문은 `agent-contract/outer/anthropic-compatible-api.md`에 새로 만들고 index에 등록한다.
- 입력:
- public operation: `GET /v1/models`, `POST /v1/chat/completions`, `POST /v1/messages`, `POST /v1/messages/count_tokens`, 기존 `POST /v1/responses`다. `agent-client/claude` migration을 위해 `/anthropic/v1/messages`, `/anthropic/v1/messages/count_tokens`, `/anthropic/v1/models`를 같은 handler의 compatibility alias로 둘 수 있으나 canonical 계약은 `/v1/*`다.
- protocol profile: stable profile id, driver(`openai_chat|anthropic_messages|openai_responses`), base URL, operation별 상대/절대 path, auth header/scheme declaration, supported operation/capability, upstream model mapping과 제한된 extension options를 가진다. `openai_responses`는 기존 선택적 driver이며 공통 qualification baseline이 아니다.
- profile overlay: top-level reusable profile catalog를 `nodes[].providers[]` resource가 profile id로 참조한다. profile은 optional base profile 하나를 확장하고 endpoint/path/auth declaration/capability/extension만 override할 수 있으며, cycle/unknown base/conflicting driver는 config error다. runtime과 Node payload에는 상속 정보가 아니라 validation이 끝난 concrete profile만 전달한다.
- Chat profile catalog: OpenAI, Gemini, Anthropic OpenAI-compatible, GLM, Kimi, MiniMax, MiMo, Grok와 Seulgi GPT를 `openai_chat` driver로 표현한다.
- Messages profile catalog: Anthropic, MiniMax, MiMo와 Seulgi Claude처럼 native Messages가 검증된 endpoint만 `anthropic_messages` operation을 선언한다. 한 vendor가 두 protocol을 제공하면 별도 profile 또는 명시 capability로 표현하고 vendor 이름으로 protocol을 추정하지 않는다.
- URL resolution: operation path가 절대 URL이면 그대로 사용하고 상대 path면 normalized base URL에 한 번만 결합한다. `/v1`, `/v1beta/openai`, `/api/paas/v4`, `/anthropic/v1` prefix는 문자열 suffix heuristic으로 추정하지 않고 profile operation path가 원문이다.
- Messages auth: milestone 구현 시 기존 principal resolver를 추상화해 bearer와 `x-api-key`를 같은 IOP credential로 판정한다. Control Plane 원장 전환과 충돌 정책의 최종 소유자는 후속 credential-slot Milestone이다.
- model discovery: 공식 Anthropic 요청에 필수인 `anthropic-version` header가 있는 `GET /v1/models``/anthropic/v1/models`에는 Anthropic-compatible list/model shape를, header가 없는 일반 `/v1/models`에는 기존 OpenAI shape를 반환한다. 양쪽 모두 같은 internal model route snapshot을 사용하며 vendor/`User-Agent` 추정으로 schema를 선택하지 않는다.
- Claude Code direct config: `ANTHROPIC_BASE_URL`은 Edge canonical root 또는 `/anthropic` compatibility mount를 가리키고, `ANTHROPIC_AUTH_TOKEN` 또는 `apiKeyHelper`가 반환한 IOP token을 principal auth에 사용한다. `Authorization``X-Api-Key`가 함께 오면 같은 token이어야 한다.
- Messages bridge input: top-level system, text/image content block, assistant content, tool definition, `tool_use`, `tool_result`, `tool_choice`, stop/max token, temperature와 profile capability가 선언한 thinking block을 Chat request로 변환한다. 변환 불가능한 block/option은 조용히 제거하지 않고 dispatch 전 `invalid_request_error`로 거부한다.
- Anthropic version/beta headers: ingress는 지원하는 `anthropic-version`을 검증하고 알려진 `anthropic-beta` capability만 native upstream에 전달한다. Chat bridge가 의미를 구현하지 못하는 beta는 무시하지 않고 unsupported error로 거부한다.
- count tokens: native upstream capability가 있으면 tunnel로 전달하고 Chat-only profile은 model profile에 등록된 deterministic counter/estimator를 사용한다. counter가 없으면 임의 provider 호출로 대체하지 않고 명시적인 unsupported error를 반환한다.
- 출력:
- native Messages tunnel은 status, content type, Anthropic SSE event/body 순서와 provider error body를 public allowlist 안에서 보존한다.
- Chat bridge는 Chat text/reasoning/tool call/finish reason/usage/error를 Anthropic message object 또는 `message_start` → content block events → `message_delta``message_stop` 순서로 변환한다.
- usage는 input/output/cache/reasoning 중 양쪽 protocol에 대응되는 필드만 보존하고 알 수 없는 값을 만들어내지 않는다.
- profile qualification record는 provider/profile/model, official-doc revision 또는 확인일, smoke date, result와 sanitized failure만 저장한다.
- Responses 보존:
- `/v1/responses` route, `openai_responses` normalized/passthrough body/stream response와 `agent-client/pi` 소비는 현행 동작을 유지한다.
- provider 공통 완료 판단은 Chat fixture와 profile이 선언한 Messages fixture를 기준으로 하며 Responses 지원이나 live smoke를 강제하지 않는다.
- 금지:
- vendor 이름 분기만으로 endpoint/path/protocol을 선택하거나 모든 type을 의미 없는 단일 `openai_compat` capability로 취급하지 않는다.
- native Messages response를 불필요하게 JSON decode/re-encode하거나 Chat bridge에서 알 수 없는 content/tool block을 silently drop하지 않는다.
- `agent-client/claude` gateway를 Claude Code → IOP production 호출의 필수 hop으로 남기지 않는다.
- live provider credential을 fixture, tracked config, CI secret requirement와 qualification artifact에 남기지 않는다.
## Acceptance Scenarios
| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `profile-contract` | valid/invalid driver, base overlay, operation, auth와 legacy provider type config가 있음 | config load와 runtime normalization을 수행함 | valid overlay는 immutable concrete profile이 되고 cycle/conflict/invalid 조합은 거부되며 legacy alias는 같은 profile id로 수렴한다. |
| S02 | `path-resolution` | `/v1`, `/v1beta/openai`, `/api/paas/v4`, `/anthropic/v1` base/path fixture가 있음 | operation URL을 resolve함 | prefix 중복/손실 없이 profile에 선언된 정확한 URL이 만들어진다. |
| S03 | `profile-catalog` | OpenAI, Gemini, Anthropic, GLM, Kimi, MiniMax, MiMo, Grok와 Seulgi Chat/Messages built-in/custom profile fixture가 있음 | 각 operation의 admission과 request build를 실행함 | 선언한 operation만 허용되고 header/model/path가 provider fixture와 일치한다. |
| S04 | `contract-sync` | protocol/config/wire 구현이 완료됨 | contract/spec drift check를 수행함 | public/inner contract와 spec index가 구현 source of truth를 정확히 가리킨다. |
| S05 | `messages-surface` | `ANTHROPIC_BASE_URL`, auth token/helper, `anthropic-version`, models, Messages와 count-tokens 요청이 있음 | canonical root와 `/anthropic` mount를 `agent-client` 없이 Edge에 직접 요청함 | 동일 principal 인증과 명시적 Anthropic model schema 선택 뒤 native 또는 bridge 실행으로 진입한다. |
| S06 | `messages-native` | native Messages non-stream/stream/error fixture가 있음 | raw tunnel로 요청함 | model/auth rewrite 외 body와 SSE ordering이 보존되고 terminal이 한 번만 발생한다. |
| S07 | `messages-chat-bridge` | text/image/tool/thinking/usage/error Chat fixtures가 있음 | Messages↔Chat bridge를 왕복함 | 지원 의미는 Anthropic 계약으로 복원되고 미지원 block은 dispatch 전에 거부된다. |
| S08 | `responses-regression` | 기존 Responses normalized/passthrough/provider-auth fixture가 있음 | 선택적 `openai_responses` profile/runtime test와 함께 실행함 | 기존 status/body/stream 의미와 Pi 소비 경로가 바뀌지 않는다. |
| S09 | `fixture-suite` | 외부 network와 credential이 없는 test 환경임 | 전체 provider contract suite를 실행함 | 모든 deterministic profile test가 구독 없이 반복 통과한다. |
| S10 | `live-smoke` | 구현 중인 profile의 opt-in credential과 대표 model 1개가 제공됨 | 한정 live smoke를 수동 실행함 | sanitized qualification record가 남고 일반 CI에는 live 의존성이 생기지 않는다. |
## Evidence Map
| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|----------|-------------------|------------------|---------------------------|
| S01-S03 | config validation, URL resolver와 provider request golden tests | `agent-task/m-multi-provider-protocol-profile-native-messages/...` | `profile-contract`, `path-resolution`, `profile-catalog` Task id와 provider별 table result |
| S04 | contract/spec index check와 implementation path search | `agent-task/m-multi-provider-protocol-profile-native-messages/...` | `contract-sync` Task id와 drift 없음 근거 |
| S05-S07 | Claude Code request fixture, native tunnel bytes/SSE와 Messages↔Chat golden tests | `agent-task/m-multi-provider-protocol-profile-native-messages/...` | `messages-surface`, `messages-native`, `messages-chat-bridge` Task id별 direct Edge evidence |
| S08 | 기존 Responses route/stream/provider-auth regression suite | `agent-task/m-multi-provider-protocol-profile-native-messages/...` | `responses-regression` Task id와 기존 test 통과 근거 |
| S09 | credential-free full provider fixture suite | `agent-task/m-multi-provider-protocol-profile-native-messages/...` | `fixture-suite` Task id와 network-disabled test 결과 |
| S10 | opt-in one-shot command와 sanitized qualification record | `agent-task/m-multi-provider-protocol-profile-native-messages/...` | `live-smoke` Task id, provider/profile/model/date/result; secret 없음 |
## Cross-repo Dependencies
- 없음
## Drift Check
- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 code-review/complete.log에서 검증 가능하다.
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다.
## 사용자 리뷰 이력
- 2026-07-31: 사용자가 Chat Completions 공통 기준, Edge의 Anthropic Messages 직접 흡수, Responses 현행 유지와 구현 시점 한정 smoke를 확정했다.
## 작업 컨텍스트
- 표준선: protocol driver는 wire/stream semantics를, provider profile은 endpoint/path/auth/model/capability 차이를 소유한다. 공통 profile로 표현할 수 없는 검증된 차이만 extension hook으로 추가한다.
- 후속 SDD: [SDD.md](../principal-provider-credential-slot-routing/SDD.md)

View file

@ -0,0 +1,160 @@
# SDD: 사용자별 Provider Credential Slot과 Alias Routing
## 위치
- Milestone: [Milestone 문서](../../../phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
- Phase: [PHASE.md](../../../phase/operational-observability-provider-management/PHASE.md)
## 상태
[승인됨]
## SDD 잠금
- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목: 없음
## 문제 / 비목표
- 문제: 현재 IOP bearer identity는 Edge YAML의 `openai.principal_tokens[]` hash mapping이 원장이고, 외부 provider token은 caller가 `X-IOP-Provider-Authorization` header로 매번 전달한다. 이 구조는 Control Plane이 사용자와 provider credential을 소유하지 않으며, 한 사용자가 같은 vendor/model의 여러 token을 독립 slot과 alias로 관리하거나 Claude Code가 IOP token만으로 직접 호출할 수 없게 한다.
- 비목표:
- 여러 token slot의 자동 rotation, round-robin, failover 또는 quota 최적화
- 여러 vendor를 한 model alias 뒤에서 자동 대체하는 routing policy
- 조직 역할 기반 IAM, billing/chargeback, provider credential marketplace
- 모든 provider 계정의 상시 구독과 정기 live smoke
## Source of Truth
| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | [Milestone 문서](../../../phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md) | 목표, credential slot과 migration 완료 Task |
| Control Plane Store | `apps/control-plane`, `database.url` repository | principal, IOP token hash, slot/binding/ciphertext/revision의 canonical store |
| Client-Control Plane | `apps/control-plane/internal/wire`, `apps/client`, [Client-Control Plane Wire 계약](../../../../agent-contract/inner/client-control-plane-wire.md) | principal/token/slot/binding/alias management와 authorization |
| Control Plane-Edge | `apps/control-plane/internal/wire`, `apps/edge/internal/controlplane`, [Control Plane-Edge Wire 계약](../../../../agent-contract/inner/control-plane-edge-wire.md) | auth/routing projection과 bounded credential lease |
| Edge Ingress | `apps/edge/internal/openai` | principal authentication, route resolution와 model discovery |
| Edge-Node | `apps/edge/internal/service`, `apps/node/internal/node`, `proto/iop/runtime.proto`, [Edge-Node Runtime Wire 계약](../../../../agent-contract/inner/edge-node-runtime-wire.md) | confidential sensitive payload 전달과 adapter injection |
| Current Compatibility | `packages/go/config`, `configs/edge.yaml`, [Edge Config/Refresh 계약](../../../../agent-contract/inner/edge-config-runtime-refresh.md) | 현재 Edge-local principal hash와 caller provider-auth migration 입력 |
| User Decision | 2026-07-31 사용자 대화 | Control Plane ownership, 사용자/vendor별 multi-token slot, optional alias, IOP token 기반 호출, Seulgi 포함, implicit rotation 없음 |
## State Machine
| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| iop-token/issued | principal에 새 IOP token을 발급하고 hash만 commit함 | iop-token/active | one-time raw token 반환과 token revision |
| iop-token/active | active hash가 유효 projection에 포함됨 | iop-token/revoked / iop-token/expired | Control Plane token lifecycle |
| iop-token/revoked | revoke revision이 commit됨 | 없음 | 이후 projection/인증에서 제거, 재활성화 대신 재발급 |
| slot/draft | principal이 vendor/profile/secret/model binding을 등록함 | slot/active / slot/rejected | alias, key, profile, binding validation |
| slot/active | encrypted secret revision과 한 개 이상의 valid binding이 있음 | slot/disabled / slot/rotated / slot/revoked | slot lifecycle command |
| slot/rotated | 새 ciphertext revision이 commit됨 | slot/active | 이전 lease는 만료 또는 revision rejection으로 수렴 |
| slot/disabled | 운영자가 일시 중지함 | slot/active / slot/revoked | 새 route/lease 발급 차단, 명시 re-enable 가능 |
| slot/revoked | slot을 영구 폐기함 | 없음 | route와 새 lease 제거 |
| projection/fresh | Edge가 더 큰 generation의 signed/authorized projection을 적용함 | projection/fresh / projection/stale | expiry와 generation order |
| projection/stale | expiry 전 refresh에 실패하거나 expiry가 지남 | projection/fresh / request/rejected | expiry 뒤 CP-managed principal auth/route fail closed |
| request/authenticated | inbound IOP token이 fresh projection의 principal과 매칭됨 | request/route-resolved / request/rejected | request model과 principal-scoped route catalog |
| request/route-resolved | route id/alias가 정확히 한 slot, protocol profile과 upstream model을 가리킴 | request/lease-ready / request/rejected | active slot/binding/revision과 provider resource selector |
| request/lease-ready | authenticated confidential channel에서 받은 bounded credential lease가 valid함 | request/dispatched / request/rejected | principal/slot/route/target/revision/expiry validation |
| request/dispatched | adapter가 실행 직전에 secret을 주입함 | request/terminal | upstream response, cancel, timeout 또는 error |
## Interface Contract
- 계약 원문: principal/slot management는 [Client-Control Plane Wire 계약](../../../../agent-contract/inner/client-control-plane-wire.md), Control Plane projection/lease는 [Control Plane-Edge Wire 계약](../../../../agent-contract/inner/control-plane-edge-wire.md), sensitive runtime payload는 [Edge-Node Runtime Wire 계약](../../../../agent-contract/inner/edge-node-runtime-wire.md), 현재 principal/provider auth migration은 [OpenAI-compatible API 계약](../../../../agent-contract/outer/openai-compatible-api.md)과 [Edge Config/Refresh 계약](../../../../agent-contract/inner/edge-config-runtime-refresh.md)을 갱신한다. 구현 시 Anthropic public auth/model route는 protocol Milestone이 만드는 `agent-contract/outer/anthropic-compatible-api.md`에 연결한다.
- Control Plane persistence:
- `principal`: stable principal ref, display alias와 status를 가진다.
- `iop_token`: stable token ref, cryptographically random한 high-entropy raw token의 SHA-256 digest, principal ref, status, revision과 timestamps를 가진다. raw token은 발급 시 한 번만 반환하고 저장하지 않으며 Edge는 같은 digest를 계산해 projection과 매칭한다.
- `provider_credential_slot`: stable slot id, principal ref, optional user alias, vendor, credential kind, encrypted typed secret material, secret/key revision, status와 timestamps를 가진다. slot은 protocol profile이나 model을 직접 소유하지 않는다.
- `provider_model_binding`: stable route id, slot id, provider profile id, upstream model, optional route alias, provider resource selector, capability/allowlist와 status를 가진다. binding profile의 vendor/auth declaration이 slot의 vendor/credential kind와 호환되어야 하며, 한 slot은 Chat/Messages를 포함한 여러 compatible profile/model binding을 가질 수 있고 같은 upstream model은 서로 다른 slot의 route로 각각 존재할 수 있다.
- alias uniqueness: slot alias는 principal 안에서 유일하다. client `model` 선택에 쓰는 route id/route alias도 principal 안에서 유일해야 하며 중복 또는 다의적 해석은 저장/활성화 단계에서 fail closed한다. single-model slot은 slot alias를 route alias로 직접 쓸 수 있고 multi-model slot은 각 binding의 고유 route id/alias를 사용한다.
- Control Plane management:
- host-local Control Plane operator CLI가 principal과 최초 IOP token을 bootstrap한다. 이 CLI는 Control Plane host의 deployment admin 권한과 DB/service boundary 안에서만 실행하며 unauthenticated remote admin endpoint를 만들지 않는다.
- 이후 principal은 IOP token으로 인증해 자신의 추가 token ref, credential slot, model binding과 alias를 create/list/update/rotate/disable/revoke한다. provider raw secret은 create/rotate 입력에서만 받고 이후 list/get 응답에는 secret, ciphertext와 token digest를 반환하지 않는다.
- operator의 secret-blind disable/revoke/recovery는 host-local CLI로 제한하고 raw provider secret 조회/reveal operation은 제공하지 않는다. 마지막 active IOP token 폐기처럼 principal의 자기 접근을 끊는 operation은 명시 확인을 요구하되 조직 RBAC와 세부 remote operator role model은 이 Milestone 범위가 아니다.
- mutation은 expected revision을 받아 stale update를 conflict로 거부하고, alias/binding validation과 encrypted secret commit이 모두 성공한 뒤 새 revision을 publish한다.
- Secret at rest:
- provider secret은 authenticated encryption envelope의 ciphertext, nonce, key id/version과 AAD metadata만 DB에 저장한다.
- master/envelope key material은 deployment secret manager 또는 동등한 외부 secret source가 제공하고 tracked YAML, DB와 API payload에 저장하지 않는다. key source가 없으면 slot secret 생성/rotation/활성화를 거부한다.
- Edge projection:
- projection은 generation, issued/expiry time, active IOP token hash→principal mapping, principal별 route/slot reference, profile/upstream model과 status revision만 포함한다. provider ciphertext/plaintext와 encryption key를 포함하지 않는다.
- Edge는 더 큰 generation만 atomic apply하고 bounded memory cache를 사용한다. refresh 실패 중에도 expiry 전 projection은 사용할 수 있지만 expiry 뒤 CP-managed principal 인증/선택은 identity projection unavailable typed error로 fail closed한다. health와 별도 Edge-local admin auth 표면은 이 projection에 종속시키지 않는다.
- Inbound auth:
- OpenAI Chat/Responses는 `Authorization: Bearer <IOP token>`을 사용한다. Anthropic Messages는 같은 bearer 또는 `x-api-key: <IOP token>`을 허용해 Claude Code의 API-key helper와 직접 연결한다.
- bearer와 `x-api-key`가 동시에 있으면 두 값이 같은 active token이어야 한다. 불일치, unknown, disabled 또는 revoked token은 route/model lookup과 provider dispatch 전에 `401`로 종료한다.
- caller metadata/user field와 caller-supplied provider token은 principal 또는 slot 선택 source가 아니다.
- Route and alias resolution:
- public request의 `model`은 인증 principal의 canonical route id 또는 unique route alias만 해석한다. slot alias는 model binding의 route alias 생성/표시에 사용할 수 있지만 실행 시 항상 하나의 concrete route id, slot id, protocol profile id와 upstream model로 고정된다.
- `/v1/models`의 OpenAI/Anthropic response variant는 그 principal의 active binding만 반환한다. 다른 principal, disabled/revoked slot과 expired projection의 route는 숨긴다.
- binding의 provider resource selector는 기존 Edge provider pool 안에서 같은 profile/model을 실행할 Node/provider resource 후보를 제한한다. capacity/load 기반 실행 위치 선택은 동일 credential slot을 유지하며 token slot rotation으로 취급하지 않는다.
- 같은 vendor/model의 `claude-1`, `claude-2` route가 있으면 요청된 route의 slot만 사용한다. quota/auth/provider failure가 발생해도 다른 slot 또는 vendor로 자동 전환하지 않는다.
- Secure credential delivery:
- provider secret을 입력·운반하는 Client-Control Plane, Control Plane-Edge와 Edge-Node 연결은 server identity verification과 전송 기밀성을 제공해야 한다. system peer는 enrollment-bound identity로 상호 인증하고 principal client는 TLS 위에서 IOP token으로 인증한다. 현재 평문 TCP/WS 연결은 credential-bearing operation에 사용할 수 없다.
- Edge는 route resolution 뒤 Control Plane에서 principal/slot/secret revision/route/target/expiry에 묶인 짧은 credential lease를 받고, 동일 scope와 revision 안에서만 bounded memory cache를 사용할 수 있다. Control Plane availability가 없고 유효 lease도 없으면 cloud dispatch를 거부한다.
- credential lease는 wire의 dedicated sensitive payload로 구분하고 generic `headers`, metadata, config refresh, status snapshot과 event payload에 raw secret을 싣지 않는다. transport/debug interceptor도 sensitive payload body를 기록하지 않는다.
- adapter는 request 실행 직전에 request-local lease secret을 provider profile의 auth header로 주입하고 terminal/cancel 뒤 보존하지 않는다. 관측에는 slot ref와 secret revision만 허용한다.
- slot disable/revoke/rotation projection을 받은 뒤 새 lease와 새 dispatch를 차단한다. 이미 upstream에 dispatch된 요청은 강제 replay/cancel하지 않지만 그 lease는 새 attempt/retry에 재사용하지 않는다.
- Attribution:
- route resolution 결과의 `credential_slot_ref``credential_revision`은 request 동안 immutable dispatch binding으로 유지한다. 기존 `token_ref`는 inbound IOP token을 뜻하므로 provider credential slot 식별자로 재사용하지 않는다.
- provider usage/실행 관측은 safe stable slot ref로 분리 집계할 수 있지만 user alias, raw secret, verifier/ciphertext와 lease id는 metric label에 넣지 않는다.
- Responses compatibility:
- credential plane 활성 뒤 기존 Responses도 같은 principal route와 credential resolver를 사용하지만 Responses request/response/stream schema는 변경하지 않는다.
- Compatibility migration:
- credential plane이 명시적으로 비활성인 배포만 기존 Edge `openai.principal_tokens[]`와 request-time `openai.provider_auth`를 사용할 수 있다.
- credential plane 활성 시 Control Plane projection이 유일한 identity/slot 원장이다. legacy mapping과 충돌하면 병합하거나 우선순위를 추정하지 않고 startup/apply를 거부하며 caller provider-auth header는 무시가 아니라 명시적으로 거부한다.
- IOP bearer token을 upstream provider credential로 재사용하지 않는다.
- 금지:
- provider raw token, encryption key 또는 runtime auth header를 tracked config, generic protobuf map, status/error body, log, metric, trace와 qualification record에 남기지 않는다.
- slot alias만 보고 vendor/model을 추정하거나 같은 model의 다른 slot으로 암묵적으로 회전하지 않는다.
- stale/out-of-order projection이나 만료/변조/다른 target용 lease로 dispatch하지 않는다.
## Acceptance Scenarios
| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `principal-store` | operator bootstrap과 신규 principal의 IOP token 발급/폐기 요청이 있음 | Control Plane restart를 포함해 lifecycle을 실행함 | raw token은 한 번만 반환되고 SHA-256 digest/revision만 영속화되며 폐기 token은 복구되지 않는다. |
| S02 | `auth-projection` | fresh, stale, expired, revoked와 out-of-order projection이 있음 | Edge가 refresh와 인증을 수행함 | 더 큰 fresh generation만 atomic apply되고 expiry/revocation 뒤 CP-managed 외부 호출은 fail closed한다. |
| S03 | `surface-auth` | bearer, x-api-key, 동시 일치/불일치 token이 있음 | OpenAI/Anthropic ingress를 호출함 | 같은 principal 인증만 성공하고 실패는 provider/model 조회 전에 401로 끝난다. |
| S04 | `slot-store` | 한 principal의 같은 vendor에 두 provider token/alias와 한 token의 Chat/Messages profile 사용이 있음 | slot 생성/rotation/disable/revoke를 실행함 | credential은 protocol과 분리된 stable slot/revision으로 저장되고 compatible profile 재사용만 허용되며 secret은 ciphertext만 남고 충돌 alias는 거부된다. |
| S05 | `model-binding` | 같은 upstream Claude model을 쓰는 `claude-1`, `claude-2` slot과 multi-profile/model slot이 있음 | route/binding을 활성화하고 lookup함 | 각 route가 정확히 한 slot/profile/model로 수렴하고 multi-profile/model binding도 독립 route id로 선택된다. |
| S06 | `model-discovery` | 서로 다른 route 권한을 가진 두 principal이 있음 | OpenAI/Anthropic model list를 요청함 | 각 principal은 자신의 active route만 보고 Claude Code가 반환 alias/route를 선택할 수 있다. |
| S07 | `explicit-selection` | 같은 model에 두 active slot이 있고 하나가 quota/auth 실패함 | 첫 slot route로 요청함 | 요청 slot만 한 번 사용되고 다른 slot/vendor로 암묵적 fallback하지 않는다. |
| S08 | `credential-management` | host-local operator bootstrap과 principal create/list/update/rotate/disable/revoke 요청이 있음 | CLI와 Control Plane management operation을 실행함 | remote bootstrap은 없고 principal 범위/revision이 강제되며 secret은 입력 뒤 조회/로그에 반환되지 않는다. |
| S09 | `secret-at-rest` | provider token 저장, key rotation과 restart가 있음 | DB/API/log를 검사하고 decrypt fixture를 수행함 | ciphertext와 safe metadata만 저장되며 올바른 key revision만 복호화된다. |
| S10 | `secure-transport` | TLS identity가 올바르거나 없거나 다른 peer인 Client/Edge/Node 연결이 있음 | credential-bearing operation과 cloud dispatch를 시도함 | 인증·기밀 연결만 허용되고 평문/잘못된 peer에서는 secret을 보내기 전에 차단된다. |
| S11 | `credential-lease` | valid lease와 다른 principal/route/target, expired/revoked/tampered lease가 있음 | Edge/Node 실행 경계가 검증함 | valid scope/revision만 dedicated sensitive payload로 전달되고 나머지는 upstream 호출 전에 거부된다. |
| S12 | `adapter-injection` | valid Chat/Messages profile과 credential lease가 있음 | provider fixture endpoint를 호출함 | 기대 auth header만 upstream에 도달하고 wire debug/log/status/response에는 raw secret이 없다. |
| S13 | `revocation` | in-flight 요청과 아직 dispatch되지 않은 요청 중 slot revoke/rotation이 도착함 | 새 projection을 적용함 | in-flight는 기존 attempt만 terminal까지 가고 새 lease/dispatch/retry는 새 revision 없이는 차단된다. |
| S14 | `compat-migration` | legacy-only와 credential-plane-enabled config가 있음 | load/migrate/rollback을 실행함 | legacy-only는 유지되고 enabled mode는 CP만 원장으로 사용하며 dual-source 충돌은 거부되고 Responses schema는 유지된다. |
| S15 | `slot-attribution` | 같은 principal/model을 서로 다른 credential slot으로 호출함 | dispatch와 provider usage를 관측함 | 별도 `credential_slot_ref`로 분리 귀속되고 inbound `token_ref`와 alias/raw secret이 섞이지 않는다. |
| S16 | `slot-smoke` | deterministic Seulgi/vendor fixture와 opt-in 대표 provider credential 1개가 있음 | 일반 CI와 구현 시 one-shot E2E를 각각 수행함 | CI는 구독 없이 통과하고 live evidence에는 safe slot ref/revision/result만 남는다. |
| S17 | `contract-ops` | persistence/management/secure wire/auth 구현이 완료됨 | contract/spec/redaction runbook drift check를 수행함 | index와 계약이 구현 경계를 가리키고 raw secret 예시가 없다. |
## Evidence Map
| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|----------|-------------------|------------------|---------------------------|
| S01-S03 | CP repository lifecycle, projection generation/expiry와 dual-header ingress tests | `agent-task/m-principal-provider-credential-slot-routing/...` | `principal-store`, `auth-projection`, `surface-auth` Task id별 persistence/auth 결과 |
| S04-S08 | multi-slot, alias collision, multi-model binding, principal discovery/no-fallback와 management authorization tests | `agent-task/m-principal-provider-credential-slot-routing/...` | `slot-store`, `model-binding`, `model-discovery`, `explicit-selection`, `credential-management` Task id별 isolation 근거 |
| S09-S13 | encrypted-store inspection, TLS peer matrix, credential lease scope, upstream header와 revocation race tests | `agent-task/m-principal-provider-credential-slot-routing/...` | `secret-at-rest`, `secure-transport`, `credential-lease`, `adapter-injection`, `revocation` Task id와 secret scan 결과 |
| S14 | legacy/CP mode config and migration table tests | `agent-task/m-principal-provider-credential-slot-routing/...` | `compat-migration` Task id와 dual-source rejection/Responses regression 근거 |
| S15 | two-slot dispatch/usage attribution test와 metric label allowlist check | `agent-task/m-principal-provider-credential-slot-routing/...` | `slot-attribution` Task id, slot별 usage와 inbound token ref 분리 근거 |
| S16 | credential-free provider fixtures와 opt-in representative one-shot record | `agent-task/m-principal-provider-credential-slot-routing/...` | `slot-smoke` Task id, provider/model/safe slot ref/date/revision/result |
| S17 | contract/spec index check와 repository-wide credential leak scan | `agent-task/m-principal-provider-credential-slot-routing/...` | `contract-ops` Task id, drift 없음과 raw secret 미검출 근거 |
## Cross-repo Dependencies
- 없음
## Drift Check
- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 code-review/complete.log에서 검증 가능하다.
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다.
## 사용자 리뷰 이력
- 2026-07-31: 사용자가 Control Plane credential ownership, 사용자/vendor별 multi-token slot, optional alias, IOP token 기반 호출, Seulgi 포함과 implicit rotation 제외를 확정했다.
## 작업 컨텍스트
- 표준선: Control Plane DB가 identity/credential metadata의 원장이고 Edge는 secret-free projection을 소비한다. credential-bearing management와 runtime 경로는 authenticated confidential transport를 전제로 하며 실행 target은 scope/revision/expiry가 제한된 request-local lease만 사용한다. Edge-local provider health/capacity/runtime state의 원장은 계속 Edge다.
- 후속 SDD: 없음