fix(protocol-profile): 요청 경계와 후속 계획을 정리한다
Anthropic 전용 API key가 일반 OpenAI 요청에 적용되지 않도록 경계를 좁히고 잘못된 절대 operation URL을 fail-closed로 거부해야 한다.\n\n완료 Milestone archive와 principal credential 후속 agent-task를 같은 기준선에 맞춘다.
This commit is contained in:
parent
f2306f4dc8
commit
892dc58369
33 changed files with 4173 additions and 69 deletions
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
## 위치
|
||||
|
||||
- Roadmap: [ROADMAP.md](../../../ROADMAP.md)
|
||||
- Phase: [PHASE.md](../PHASE.md)
|
||||
- Roadmap: [ROADMAP.md](../../../../ROADMAP.md)
|
||||
- Phase: [PHASE.md](../../../../phase/operational-observability-provider-management/PHASE.md)
|
||||
|
||||
## 목표
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ OpenAI Chat Completions를 공통 cloud provider 실행 기준으로 두고 prov
|
|||
|
||||
## 상태
|
||||
|
||||
[진행중]
|
||||
[완료]
|
||||
|
||||
## 승격 조건
|
||||
|
||||
|
|
@ -47,34 +47,35 @@ OpenAI Chat Completions를 공통 cloud provider 실행 기준으로 두고 prov
|
|||
|
||||
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를 가리키고 중복 계약 원문이 남지 않는다.
|
||||
- [x] [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로 정규화된다.
|
||||
- [x] [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이 기대값과 일치한다.
|
||||
- [x] [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 전에 명시적으로 거부된다.
|
||||
- [x] [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가 요구되지 않는다.
|
||||
- [x] [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를 명확히 선택한다.
|
||||
- [x] [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가 보존된다.
|
||||
- [x] [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 응답 계약으로 수렴한다.
|
||||
- [x] [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이 실질적으로 바뀌고 관련 fixture/contract 검증으로 판단할 수 없을 때에만 host-local SOPS credential을 process-local 변수로 주입해 대표 모델 1개에 한 번의 짧은 generation smoke를 실행하고 provider/profile/model/revision/date/result를 sanitized qualification evidence로 남긴다. provider API credential은 IOP inbound principal token과 혼용하지 않는다. 검증: live smoke는 opt-in이며 일반 local test·CI·문서/secret 관리 변경·자동 재시도에서 호출되지 않고, 고정된 짧은 입력·한 단어 응답·작은 provider별 output cap만 사용하며, raw credential은 config·artifact·로그에 남지 않는다.
|
||||
- [x] [fixture-suite] 각 provider profile의 URL, header, model rewrite, non-stream/stream parsing과 오류 mapping을 credential-free deterministic fixture로 검증한다. 검증: 전체 fixture/contract suite가 네트워크와 provider 구독 없이 반복 실행된다.
|
||||
- [x] [live-smoke] 새 profile을 구현하거나 protocol profile이 실질적으로 바뀌고 관련 fixture/contract 검증으로 판단할 수 없을 때에만 host-local SOPS credential을 process-local 변수로 주입해 대표 모델 1개에 한 번의 짧은 generation smoke를 실행하고 provider/profile/model/revision/date/result를 sanitized qualification evidence로 남긴다. provider API credential은 IOP inbound principal token과 혼용하지 않는다. 검증: live smoke는 opt-in이며 일반 local test·CI·문서/secret 관리 변경·자동 재시도에서 호출되지 않고, 고정된 짧은 입력·한 단어 응답·작은 provider별 output cap만 사용하며, raw credential은 config·artifact·로그에 남지 않는다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
- 상태: 없음
|
||||
- 요청일: 없음
|
||||
- 완료 근거: 계획 Milestone이며 기능 Task가 아직 충족되지 않았다.
|
||||
- 검토 항목: Chat/Messages deterministic fixture, Claude Code direct Edge smoke, 기존 Responses 회귀와 provider별 일회성 qualification evidence를 확인한다.
|
||||
- 리뷰 코멘트: 없음
|
||||
- 상태: 통과
|
||||
- 요청일: 2026-08-01
|
||||
- 완료 근거: [protocol core 완료 기록](../../../../../agent-task/archive/2026/08/m-multi-provider-protocol-profile-native-messages/01_protocol_core/complete.log), [Messages ingress 완료 기록](../../../../../agent-task/archive/2026/08/m-multi-provider-protocol-profile-native-messages/02+01_messages_ingress/complete.log), [contract/spec 동기화 완료 기록](../../../../../agent-task/archive/2026/08/m-multi-provider-protocol-profile-native-messages/05+03,04_contract_spec_sync/complete.log), [fixture suite 완료 기록](../../../../../agent-task/archive/2026/08/m-multi-provider-protocol-profile-native-messages/06+01,02_fixture_suite/complete.log), [live smoke 완료 기록](../../../../../agent-task/archive/2026/08/m-multi-provider-protocol-profile-native-messages/07+05,06_live_smoke/complete.log)가 기능 Task 10개 전체에 PASS 및 검증 근거를 직접 기록한다.
|
||||
- 검토 항목: 현재 코드·inner/outer 계약·구현 스펙과 관련 커밋 `f2306f4d`에서 profile overlay, Edge Anthropic ingress/native·bridge, Responses capability 보존이 동일하게 확인됐다. 종료 감사에서 Anthropic 전용 `x-api-key`와 schema 선택 범위를 Anthropic surface로 제한하고, host 없는 absolute operation URL을 config load에서 거부하도록 보완한 뒤 관련 Go test, race, vet, Pi 설치 테스트와 credential-free Lemonade E2E를 통과했다.
|
||||
- Spec sync: update not needed — `edge-node-execution`, `provider-pool-config-refresh`, `openai-compatible-surface` 현재 구현 스펙이 profile 해소, public auth/schema 선택, Edge-Node 실행 경계를 이미 반영하며 이번 보완은 문서화된 계약을 좁혀 지킨다.
|
||||
- 리뷰 코멘트: 기능 Task, 구현 잠금, SDD gate, code audit와 spec sync gate가 모두 충족되었고 현재 Milestone 범위의 남은 차단 항목은 없다.
|
||||
|
||||
## 범위 제외
|
||||
|
||||
|
|
@ -101,5 +102,5 @@ Claude Code가 IOP Edge를 Anthropic-compatible endpoint로 직접 사용하고
|
|||
- qualification 재시도 근거 (2026-08-01): Kimi `openai_chat` direct upstream smoke의 첫 `kimi-k3` 호출은 `max_completion_tokens=64`에서 `finish_reason=length`와 completion 188 tokens를 반환해 실패했다. 사용자가 명시적으로 승인한 한 번의 재시도에서 `max_completion_tokens=256`, 같은 고정 한 단어 입력/응답 제약으로 HTTP 200, `finish_reason=stop`, 한 단어 응답을 확인했다. API가 반환한 model identifier는 `kimi-k3`이고 token usage는 prompt 124, completion 109, total 233이었다. 256보다 큰 상한과 자동 재시도는 금지한다.
|
||||
- qualification 상태: Gemini, OpenAI, Anthropic, Kimi의 각 1개 profile이 수동 direct-upstream smoke를 통과했다. 이는 Edge/Node 경유 검증이나 profile 구현 완료를 뜻하지 않으며, 나머지 provider qualification은 여전히 미완료다.
|
||||
- 선행 작업: 없음
|
||||
- 후속 작업: [사용자별 Provider Credential Slot과 Alias Routing](principal-provider-credential-slot-routing.md)
|
||||
- 후속 작업: [사용자별 Provider Credential Slot과 Alias Routing](../../../../phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
|
||||
- 확인 필요: 없음
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
## 위치
|
||||
|
||||
- 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)
|
||||
- Phase: [PHASE.md](../../../../phase/operational-observability-provider-management/PHASE.md)
|
||||
|
||||
## 상태
|
||||
|
||||
|
|
@ -29,10 +29,10 @@
|
|||
| 영역 | 기준 | 메모 |
|
||||
|------|------|------|
|
||||
| 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 |
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
|
@ -52,7 +52,7 @@
|
|||
|
||||
## 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에 등록한다.
|
||||
- 계약 원문: 기존 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이 아니다.
|
||||
|
|
@ -124,4 +124,4 @@
|
|||
## 작업 컨텍스트
|
||||
|
||||
- 표준선: 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)
|
||||
- 후속 SDD: [SDD.md](../../../../sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md)
|
||||
|
|
@ -51,6 +51,10 @@ Phase를 가로지르는 실제 다음 작업 선택은 [전역 마일스톤 실
|
|||
- 경로: [provider-usage-attribution-hot-path](../../archive/phase/operational-observability-provider-management/milestones/provider-usage-attribution-hot-path.md)
|
||||
- 요약: OpenAI-compatible token usage를 actual provider·served model·실행 시도에 귀속하고, 승인된 model group의 query-time rollup과 Grafana 운영 조회를 정착시켰다.
|
||||
|
||||
- [완료] 다중 Provider Protocol Profile과 Native Anthropic Messages
|
||||
- 경로: [multi-provider-protocol-profile-native-messages](../../archive/phase/operational-observability-provider-management/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 부하 메트릭과 Live Queue Dashboard
|
||||
- 경로: [provider-load-metrics-queue-dashboard](milestones/provider-load-metrics-queue-dashboard.md)
|
||||
- 요약: provider별 capacity 사용률, in-flight, queue 적체, queue wait를 Prometheus time series와 Grafana dashboard로 노출해 시간대별 live 부하 분석을 가능하게 한다.
|
||||
|
|
@ -59,10 +63,6 @@ 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만 안전하게 실행 경계에 주입한다.
|
||||
|
|
|
|||
|
|
@ -106,6 +106,6 @@ Control Plane 원장의 provider secret을 일반 config/header projection에
|
|||
- 표준선(선택): 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)
|
||||
- 선행 작업: [다중 Provider Protocol Profile과 Native Anthropic Messages](../../../archive/phase/operational-observability-provider-management/milestones/multi-provider-protocol-profile-native-messages.md)
|
||||
- 후속 작업: 없음
|
||||
- 확인 필요: 없음
|
||||
|
|
|
|||
|
|
@ -4,92 +4,86 @@
|
|||
|
||||
## 실행 순서
|
||||
|
||||
1. [다중 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를 호출하게 한다.
|
||||
|
||||
2. [사용자별 Provider Credential Slot과 Alias Routing](phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
|
||||
1. [사용자별 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로 연결한다.
|
||||
|
||||
3. [IOP Agent CLI Runtime](phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md)
|
||||
현재 Python 감시·dispatcher와 Node CLI runtime의 전체 동등성을 단일 Go CLI Provider·AgentTaskManager 및 독립 `iop-agent` binary로 이전한다.
|
||||
|
||||
4. [OpenAI-compatible 출력 검증 필터](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
|
||||
2. [OpenAI-compatible 출력 검증 필터](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-output-validation-filters.md)
|
||||
실제 의미 필터 전에 deterministic diagnostic mock으로 실제 Stream Evidence Gate의 pass·observe-only·blocking recovery를 관측하는 smoke를 통과시키고, OpenAI-compatible single-stream 반복과 incoming request history에 누적된 assistant 반복, JSON contract 검증/repair 경로를 안정화한다.
|
||||
|
||||
5. [OpenAI-compatible Incomplete Tool Call Syntax Gate](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-incomplete-tool-call-syntax-gate.md)
|
||||
3. [OpenAI-compatible Incomplete Tool Call Syntax Gate](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-incomplete-tool-call-syntax-gate.md)
|
||||
terminal provider 응답의 incomplete tool-call syntax를 deterministic하게 판정한다.
|
||||
|
||||
6. [OpenAI-compatible Runtime Output Integrity Filter](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-runtime-output-integrity-filter.md)
|
||||
4. [OpenAI-compatible Runtime Output Integrity Filter](phase/knowledge-tool-optimization-extension/milestones/openai-compatible-runtime-output-integrity-filter.md)
|
||||
terminal output invariant와 공통 filter/retry pipeline을 정의한다.
|
||||
|
||||
7. [LLM 판별 기반 Missing Tool Call 재시도 Gate](phase/knowledge-tool-optimization-extension/milestones/llm-judged-missing-tool-call-retry-gate.md)
|
||||
5. [LLM 판별 기반 Missing Tool Call 재시도 Gate](phase/knowledge-tool-optimization-extension/milestones/llm-judged-missing-tool-call-retry-gate.md)
|
||||
tool 사용 의도 누락 케이스를 LLM judge와 buffered retry 후보로 검토한다.
|
||||
|
||||
8. [Tool Call 판정 모델 Gate 리뷰](phase/knowledge-tool-optimization-extension/milestones/tool-call-validator-model-gate-review.md)
|
||||
6. [Tool Call 판정 모델 Gate 리뷰](phase/knowledge-tool-optimization-extension/milestones/tool-call-validator-model-gate-review.md)
|
||||
schema만으로 어려운 tool-call 후보에 validator 모델을 쓸지 검토한다.
|
||||
|
||||
9. [Provider 부하 메트릭과 Live Queue Dashboard](phase/operational-observability-provider-management/milestones/provider-load-metrics-queue-dashboard.md)
|
||||
7. [Provider 부하 메트릭과 Live Queue Dashboard](phase/operational-observability-provider-management/milestones/provider-load-metrics-queue-dashboard.md)
|
||||
Edge provider-pool의 capacity, in-flight, queued와 queue wait를 Prometheus/Grafana로 관측해 provider별 live 부하와 적체·회복을 분석한다.
|
||||
|
||||
10. [Pi CLI Provider Integration](phase/automation-runtime-bridge/milestones/pi-cli-provider-integration.md)
|
||||
8. [Pi CLI Provider Integration](phase/automation-runtime-bridge/milestones/pi-cli-provider-integration.md)
|
||||
Pi를 Node CLI provider 실행 후보에 추가하고 OpenAI-compatible route smoke로 안정화한다.
|
||||
|
||||
11. [CLI Agent Group Grade Routing](phase/automation-runtime-bridge/milestones/cli-agent-group-grade-routing.md)
|
||||
9. [CLI Agent Group Grade Routing](phase/automation-runtime-bridge/milestones/cli-agent-group-grade-routing.md)
|
||||
lane/grade 파일명과 `metadata.agent_group.task_file` 기반 CLI agent group 라우팅 계약을 정리한다.
|
||||
|
||||
12. [에이전트 작업 루프 오케스트레이션 MVP](phase/automation-runtime-bridge/milestones/agent-workflow-loop-orchestration-mvp.md)
|
||||
10. [에이전트 작업 루프 오케스트레이션 MVP](phase/automation-runtime-bridge/milestones/agent-workflow-loop-orchestration-mvp.md)
|
||||
일반 사용자 요청을 direct/Plan/Milestone으로 분류하고, 사용자 agent의 tool call로 만든 작업 파일을 IOP가 읽어 다음 실행·리뷰·완료 단계까지 연결한다.
|
||||
|
||||
13. [Provider 사용량 알림과 운영 표면](phase/automation-runtime-bridge/milestones/provider-usage-notification-operations-surface.md)
|
||||
11. [Provider 사용량 알림과 운영 표면](phase/automation-runtime-bridge/milestones/provider-usage-notification-operations-surface.md)
|
||||
공통 runtime의 quota/status/failure event를 소비해 macOS·Desktop·후속 외부 채널에 전달하는 알림과 이력 표면을 스케치한다.
|
||||
|
||||
14. [단계 호출과 검증 최적화 MVP](phase/knowledge-tool-optimization-extension/milestones/knowledge-tool-validation-optimization.md)
|
||||
12. [단계 호출과 검증 최적화 MVP](phase/knowledge-tool-optimization-extension/milestones/knowledge-tool-validation-optimization.md)
|
||||
planner/generator/verifier 단계 호출과 runtime schema 검증 실행 모드를 스케치한다.
|
||||
|
||||
15. [원격 코딩/유지보수 작업 환경](phase/automation-runtime-bridge/milestones/remote-workspace-operations-environment.md)
|
||||
13. [원격 코딩/유지보수 작업 환경](phase/automation-runtime-bridge/milestones/remote-workspace-operations-environment.md)
|
||||
workspace-bound execution 기반 원격 코딩/유지보수 운영 경계를 스케치한다.
|
||||
|
||||
16. [Personal Local Edge 패키징과 배포 모드 프로파일](phase/personal-edge-packaging-deployment/milestones/personal-local-edge-deployment-profiles.md)
|
||||
14. [Personal Local Edge 패키징과 배포 모드 프로파일](phase/personal-edge-packaging-deployment/milestones/personal-local-edge-deployment-profiles.md)
|
||||
personal/server/fleet 배포 모드와 capability gate 경계를 스케치한다.
|
||||
|
||||
17. [요청 실행 로그와 Usage Ledger 기반](phase/operational-observability-provider-management/milestones/request-execution-log-usage-ledger-foundation.md)
|
||||
15. [요청 실행 로그와 Usage Ledger 기반](phase/operational-observability-provider-management/milestones/request-execution-log-usage-ledger-foundation.md)
|
||||
요청별 provider/model 선택, timing, token, status/error를 구조화된 ledger로 남기는 기반을 스케치한다.
|
||||
|
||||
18. [Update Plane 안정 프로토콜](phase/update-plane-self-update-foundation/milestones/update-plane-stable-protocol.md)
|
||||
16. [Update Plane 안정 프로토콜](phase/update-plane-self-update-foundation/milestones/update-plane-stable-protocol.md)
|
||||
hello/status, manifest, command, event, recovery 최소 계약을 스케치한다.
|
||||
|
||||
19. [Host-local Manager 기반 자체 업데이트](phase/update-plane-self-update-foundation/milestones/host-local-manager-self-update.md)
|
||||
17. [Host-local Manager 기반 자체 업데이트](phase/update-plane-self-update-foundation/milestones/host-local-manager-self-update.md)
|
||||
manager/updater의 release staging, 검증, restart, rollback 실행 모델을 정리한다.
|
||||
|
||||
20. [Edge/Node 롤아웃과 복구 정책](phase/update-plane-self-update-foundation/milestones/edge-node-rollout-recovery-policy.md)
|
||||
18. [Edge/Node 롤아웃과 복구 정책](phase/update-plane-self-update-foundation/milestones/edge-node-rollout-recovery-policy.md)
|
||||
Edge/Node rolling update, 실패/재연결/rollback 보고 정책을 스케치한다.
|
||||
|
||||
21. [Provider Runtime 설정과 모델 획득 오케스트레이션](phase/operational-observability-provider-management/milestones/provider-runtime-model-acquisition-orchestration.md)
|
||||
19. [Provider Runtime 설정과 모델 획득 오케스트레이션](phase/operational-observability-provider-management/milestones/provider-runtime-model-acquisition-orchestration.md)
|
||||
provider runtime launch/profile, model download/cache/verification 경계를 스케치한다.
|
||||
|
||||
22. [Provider-Device-Model Qualification 리포트와 Lifecycle 관리](phase/operational-observability-provider-management/milestones/provider-device-model-qualification-report.md)
|
||||
20. [Provider-Device-Model Qualification 리포트와 Lifecycle 관리](phase/operational-observability-provider-management/milestones/provider-device-model-qualification-report.md)
|
||||
provider/device/model별 compatibility, performance, quality, lifecycle 리포트 경계를 정리한다.
|
||||
|
||||
23. [Provider 입력 컨텍스트 선택과 축소](phase/knowledge-tool-optimization-extension/milestones/request-context-assembly-optimization.md)
|
||||
21. [Provider 입력 컨텍스트 선택과 축소](phase/knowledge-tool-optimization-extension/milestones/request-context-assembly-optimization.md)
|
||||
provider dispatch 전에 무관한 과거 요청-답변 단위를 제거하고, 유지한 답변·tool/search 결과 안에서도 필요한 문단·코드 블록·구간만 남기는 입력 context 최적화를 스케치한다.
|
||||
|
||||
24. [장기 기억과 RAG 업데이트 사이클 (2차)](phase/knowledge-tool-optimization-extension/milestones/long-term-memory-rag-second-wave.md)
|
||||
22. [장기 기억과 RAG 업데이트 사이클 (2차)](phase/knowledge-tool-optimization-extension/milestones/long-term-memory-rag-second-wave.md)
|
||||
repo 장기 기억, RAG 저장소, update cycle, MCP 기반 context 절약 후보를 스케치한다.
|
||||
|
||||
25. [Advisor와 Context Hook 확장 (2차)](phase/knowledge-tool-optimization-extension/milestones/advisor-context-hook-second-wave.md)
|
||||
23. [Advisor와 Context Hook 확장 (2차)](phase/knowledge-tool-optimization-extension/milestones/advisor-context-hook-second-wave.md)
|
||||
advisor 역할과 여러 기능을 실행 흐름에 연결하는 Context Hook 경계를 스케치한다.
|
||||
|
||||
26. [oto 자동화 스케줄러와 CI-CD 연동 (2차)](phase/automation-runtime-bridge/milestones/oto-automation-scheduler-second-wave.md)
|
||||
24. [oto 자동화 스케줄러와 CI-CD 연동 (2차)](phase/automation-runtime-bridge/milestones/oto-automation-scheduler-second-wave.md)
|
||||
oto 기반 자동화, scheduler, CI-CD 연동 후보를 스케치한다.
|
||||
|
||||
27. [Flutter Desktop Control UI](phase/automation-runtime-bridge/milestones/flutter-desktop-control-ui.md)
|
||||
25. [Flutter Desktop Control UI](phase/automation-runtime-bridge/milestones/flutter-desktop-control-ui.md)
|
||||
`iop-agent` local proto-socket을 소비해 YAML 전체 설정과 project·실행·오류·로그를 관리하는 macOS Flutter 설정·운영 UI를 제공한다.
|
||||
|
||||
28. [Unity 3D Desktop Character](phase/automation-runtime-bridge/milestones/unity-3d-desktop-character.md)
|
||||
26. [Unity 3D Desktop Character](phase/automation-runtime-bridge/milestones/unity-3d-desktop-character.md)
|
||||
같은 local proto-socket을 독립적으로 소비하고 작업 상태를 투명 배경 3D 캐릭터와 animation으로 표현하는 macOS Unity client를 제공한다.
|
||||
|
||||
29. [IOP Hot Path One-shot 실행 경로](phase/knowledge-tool-optimization-extension/milestones/iop-hot-path-one-shot-execution.md)
|
||||
27. [IOP Hot Path One-shot 실행 경로](phase/knowledge-tool-optimization-extension/milestones/iop-hot-path-one-shot-execution.md)
|
||||
외부 `model=iop` 요청을 Gemini 3.6 Flash와 RTX 5090 `ornith-fast`의 bounded one-shot 경로로 처리해 최대 속도와 실사용 품질의 균형을 맞춘다.
|
||||
|
||||
30. [Node Provider 실행 Liveness 관측과 안전 복구](phase/operational-observability-provider-management/milestones/node-provider-execution-liveness-recovery.md)
|
||||
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 안에서 재실행한다.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/01_principal_store plan=0 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `Implementation Checklist`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## Overview
|
||||
|
||||
date=2026-08-01
|
||||
task=m-principal-provider-credential-slot-routing/01_principal_store, plan=0, tag=API
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
|
||||
- Task ids:
|
||||
- `principal-store`: principal 및 IOP token hash 원장
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## For the Review Agent
|
||||
|
||||
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
|
||||
|
||||
Compare implementation of each item against source files and verify that output in `Verification Results` matches code.
|
||||
Review completion means the following steps are finished:
|
||||
|
||||
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
|
||||
2. Archive `CODE_REVIEW-cloud-G06.md` → `code_review_cloud_G06_0.log` and `PLAN-local-G06.md` → `plan_local_G06_0.log`.
|
||||
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/01_principal_store/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
|
||||
4. If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata. Roadmap state check and `update-roadmap` calls are runtime responsibilities.
|
||||
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Item Completion
|
||||
|
||||
| Item | Status |
|
||||
|------|---------|
|
||||
| API-1 Open and migrate the credential store | [ ] |
|
||||
| API-2 Enforce principal token lifecycle and secret hygiene | [ ] |
|
||||
| API-3 Preserve startup compatibility and prove package integrity | [ ] |
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Add the Control Plane credential-store connection, schema migration, and clean shutdown boundary.
|
||||
- [ ] Implement atomic principal/token issuance, lookup, disable, and irreversible revoke lifecycle with digest-only persistence.
|
||||
- [ ] Wire the optional database-backed store into Control Plane startup while preserving database-unconfigured legacy startup.
|
||||
- [ ] Run fresh repository, startup, vet, and diff verification with the declared executable Go cache paths.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
## Review-Only Checklist
|
||||
|
||||
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
|
||||
> Implementing agents must not modify or check this section.
|
||||
|
||||
- [ ] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
|
||||
- [ ] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
|
||||
- [ ] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G06_0.log`.
|
||||
- [ ] Archive active `PLAN-*-G??.md` to `plan_local_G06_0.log`.
|
||||
- [ ] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
|
||||
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
|
||||
- [ ] If PASS, move active task directory `agent-task/m-principal-provider-credential-slot-routing/01_principal_store/` to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/01_principal_store/` and update this checklist at the final archive path.
|
||||
- [ ] If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata for runtime, without modifying roadmap or directly calling `update-roadmap`.
|
||||
- [ ] If PASS for split work, remove empty active parent `agent-task/m-principal-provider-credential-slot-routing/` or verify it was kept due to remaining siblings/files.
|
||||
- [ ] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
_Record any deviations from the plan and the rationale here._
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
_Record key design decisions here._
|
||||
|
||||
## Reviewer Checkpoints
|
||||
|
||||
- Confirm the configured PostgreSQL path never silently falls back and the empty URL preserves legacy startup.
|
||||
- Inspect schema/transactions for digest-only storage, monotonic revision, and irreversible revoke.
|
||||
- Confirm raw tokens are absent from DB queries, list/get types, captured logs, metrics labels, and errors.
|
||||
- Re-run restart persistence and race evidence before PASS.
|
||||
|
||||
## Verification Results
|
||||
|
||||
Paste actual stdout/stderr beneath every command. Do not summarize reconstructed output; if output is too long, record the saved output path and exact capture command.
|
||||
|
||||
### API-1
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/credentialstore ./apps/control-plane/cmd/control-plane`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-2
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialstore`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-3
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -run '^$' ./apps/control-plane/...`
|
||||
|
||||
_Pending._
|
||||
|
||||
### Final Verification
|
||||
|
||||
1. `command -v go && go version && go env GOROOT`
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache`
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go mod tidy`
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/...`
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialstore`
|
||||
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/...`
|
||||
7. `git diff --check`
|
||||
|
||||
_Pending actual output for each command._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Section Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
|
||||
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
|
||||
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
|
||||
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section |
|
||||
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholder text with actual content |
|
||||
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
|
||||
| Verification Results (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `Deviations from Plan` entry |
|
||||
| Code Review Result | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/01_principal_store plan=0 tag=API -->
|
||||
|
||||
# Principal Store
|
||||
|
||||
## For the Implementing Agent
|
||||
|
||||
Filling the implementation-owned sections of `CODE_REVIEW-cloud-G06.md` is mandatory. Run every verification command, paste actual notes/output, keep both active files in place, and report ready for review; only the code-review skill may finalize, rename logs, write `complete.log`, or archive the task. If blocked, record only the exact blocker, attempted commands/output, and resume condition in the implementation-owned evidence fields; do not ask the user, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
|
||||
## Background
|
||||
|
||||
Control Plane currently accepts `database.url` but only logs that it is configured; there is no durable credential-plane repository. Principal tokens are Edge config entries, so issuance, revocation, revision fencing, restart persistence, and one-time raw-token return cannot be enforced by Control Plane.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
|
||||
- Task ids:
|
||||
- `principal-store`: principal 및 IOP token hash 원장
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## Analysis
|
||||
|
||||
### Files Read
|
||||
|
||||
- `go.mod`
|
||||
- `go.sum`
|
||||
- `docker-compose.yml`
|
||||
- `configs/control-plane.yaml`
|
||||
- `apps/control-plane/cmd/control-plane/main.go`
|
||||
- `apps/control-plane/cmd/control-plane/server.go`
|
||||
- `apps/control-plane/cmd/control-plane/config_test.go`
|
||||
- `apps/edge/internal/openai/principal.go`
|
||||
- `apps/node/internal/store/store.go`
|
||||
- `apps/node/internal/store/store_test.go`
|
||||
|
||||
### SDD Criteria
|
||||
|
||||
- SDD: `agent-roadmap/sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md` (`승인`, 구현 잠금 해제).
|
||||
- Target: S01 / `principal-store`.
|
||||
- Evidence Map row: S01-S03의 CP repository lifecycle 근거 중 S01 persistence/auth 결과.
|
||||
- 따라서 checklist는 원문 token의 1회 반환, SHA-256 digest만 영속화, revision/status 전이, revoke 불가역성, process reopen persistence, DB/API/log/metric 비노출 검증을 한 저장소 경계에 묶는다.
|
||||
|
||||
### Verification Context
|
||||
|
||||
- 별도 handoff는 없었다. 저장소 근거는 `go.mod`, Control Plane config/server, `docker-compose.yml`, project/domain/test rules와 `agent-test/local`의 control-plane smoke profile에서 도출했다.
|
||||
- Local runner: `/config/workspace/iop-s0`, branch `feature/multi-provider-protocol-profile-native-messages`, HEAD `f2306f4dc8cabd7ff3c2085c016897f0bb337cc6`, Linux aarch64.
|
||||
- `go version go1.26.2 linux/arm64`; PostgreSQL URL은 compose의 `IOP_DATABASE_URL`, 테스트는 기존 `modernc.org/sqlite`로 process reopen을 재현한다. PostgreSQL runtime driver는 현재 manifest에 없어 `pgx/v5/stdlib` 추가가 필요하다.
|
||||
- 현재 셸의 기본 `GOCACHE`는 권한이 없는 임시 경로이고 `/tmp`는 `noexec`이다. 저장소의 ignored `.cache/go-cache`와 `.cache/go-build`를 명시하면 관련 package compile preflight가 PASS했다.
|
||||
- 외부 provider, 원격 runner, secret, port가 필요하지 않다. Confidence: high.
|
||||
|
||||
### Test Coverage Gaps
|
||||
|
||||
- 기존 Control Plane 테스트는 config redaction만 확인하고 DB open/migration을 다루지 않는다: 신규 startup wiring test 필요.
|
||||
- 기존 Node SQLite store는 reopen 패턴만 제공하며 principal token의 one-time return/revoke/revision을 다루지 않는다: 신규 repository tests 필요.
|
||||
- raw token의 log/metric 비노출 검사는 없다: logger observer를 사용하는 신규 test 필요.
|
||||
|
||||
### Symbol References
|
||||
|
||||
- Rename/remove 없음. `run` 호출은 `apps/control-plane/cmd/control-plane/main.go:82` 한 곳이며 signature를 유지한다.
|
||||
|
||||
### Split Judgment
|
||||
|
||||
- `01_principal_store`: DB 연결·migration·principal/token transaction을 제공하고 restart persistence tests로 독립 PASS한다.
|
||||
- `02+01_credential_catalog`: 01의 FK/source-of-truth를 사용해 slot/route catalog를 독립 검증한다.
|
||||
- `03+01,02_projection_auth`: 01/02 snapshot을 wire로 보내 Edge에서 인증한다.
|
||||
- `04+03_principal_routes`: 적용된 projection만 읽어 discovery/selection을 검증한다.
|
||||
- `05+01,02_management_core`: 01/02 service를 principal-scoped in-process operations로 노출한다.
|
||||
- `06+01,02_host_local_bootstrap`: 01의 first-principal issuance를 host-local CLI로 노출한다.
|
||||
- 본 packet은 predecessor가 없으며, principal과 최초 token 발급을 한 transaction으로 유지해야 raw-token one-time invariant가 독립 PASS하므로 더 나누지 않는다.
|
||||
|
||||
### Scope Rationale
|
||||
|
||||
- Edge projection, HTTP ingress, slot/route schema, management wire/CLI, provider secret delivery는 후속 packet 소유이므로 제외한다.
|
||||
- `agent-ops/rules/common/**`, `agent-ops/skills/common/**`, roadmap/SDD/spec 문서는 수정하지 않는다.
|
||||
- SQLite는 deterministic repository test dialect이고 production URL은 PostgreSQL이다; 별도 migration framework 도입은 하지 않는다.
|
||||
|
||||
### Final Routing
|
||||
|
||||
- `status=routed`, `evaluation_mode=first-pass`, finalizer=`finalize-task-policy.sh`, mode=`pair`.
|
||||
- Build closures: scope/context/verification/evidence/ownership/decision 모두 true. Scores `2+1+1+1+1=G06`; base/route=`local-fit`, lane=`local`, canonical=`PLAN-local-G06.md`.
|
||||
- Review closures 모두 true. Scores `2+1+1+1+1=G06`; route=`official-review`, lane=`cloud`, model=`gpt-5.6-sol` xhigh, canonical=`CODE_REVIEW-cloud-G06.md`.
|
||||
- `large_indivisible_context=false`; matched loop risk=`temporal_state` (1); rework=0; evidence integrity failure=false; capability gap 없음.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Add the Control Plane credential-store connection, schema migration, and clean shutdown boundary.
|
||||
- [ ] Implement atomic principal/token issuance, lookup, disable, and irreversible revoke lifecycle with digest-only persistence.
|
||||
- [ ] Wire the optional database-backed store into Control Plane startup while preserving database-unconfigured legacy startup.
|
||||
- [ ] Run fresh repository, startup, vet, and diff verification with the declared executable Go cache paths.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
### [API-1] Open and migrate the credential store
|
||||
|
||||
**Problem:** `apps/control-plane/cmd/control-plane/server.go:29-31` only logs `database.url`; no connection, migration, or close path exists.
|
||||
|
||||
**Solution:** Add a `database/sql` store that selects PostgreSQL for `postgres://`/`postgresql://` and SQLite only for tests, pings before use, runs idempotent schema migrations, and closes with server lifetime. Add `github.com/jackc/pgx/v5/stdlib`; keep secrets out of connection logs.
|
||||
|
||||
Before (`apps/control-plane/cmd/control-plane/server.go:29-31`):
|
||||
|
||||
```go
|
||||
if cfg.Database.URL != "" {
|
||||
logger.Info("control-plane database configured", databaseLogFields(cfg.Database.URL)...)
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
store, err := credentialstore.Open(ctx, cfg.Database.URL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open credential store: %w", err)
|
||||
}
|
||||
if store != nil {
|
||||
defer store.Close()
|
||||
}
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `go.mod`: add the PostgreSQL stdlib driver.
|
||||
- [ ] `go.sum`: record resolved driver checksums.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/store.go`: implement dialect selection, ping, transaction helper, and close.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/schema.go`: implement idempotent principal/token schema and constraints.
|
||||
- [ ] `apps/control-plane/cmd/control-plane/server.go`: open/close the optional store.
|
||||
- [ ] `apps/control-plane/cmd/control-plane/credential_store_test.go`: prove configured failure and unconfigured compatibility.
|
||||
|
||||
**Test Strategy:** Write `TestOpenMigratesAndReopensCredentialStore`, `TestRunRejectsInvalidDatabaseURL`, and `TestRunAllowsUnconfiguredDatabase`. Use a temp SQLite file and ephemeral listener addresses; assert schema persists and invalid configured DB fails before serving.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/credentialstore ./apps/control-plane/cmd/control-plane` must pass.
|
||||
|
||||
### [API-2] Enforce principal token lifecycle and secret hygiene
|
||||
|
||||
**Problem:** `apps/edge/internal/openai/principal.go:86-137` matches static config hashes, but Control Plane has no issuer, revision, durable status, or revocation source of truth.
|
||||
|
||||
**Solution:** Generate 32 random bytes, encode only the one-time response, persist SHA-256 digest plus stable token reference, principal reference, status, revision, and timestamps in one transaction. List/get return metadata only. Disable/revoke use compare-and-swap revision; revoke is irreversible and no method reconstructs raw token.
|
||||
|
||||
Before (`apps/control-plane/cmd/control-plane/server.go:15-18`):
|
||||
|
||||
```go
|
||||
func run(ctx context.Context, cfg controlPlaneConfig, logger *zap.Logger) error {
|
||||
mux := newHTTPMux()
|
||||
|
||||
wireEndpoint := wire.Endpoint{Listen: cfg.Server.WireListen}
|
||||
```
|
||||
|
||||
After domain boundary:
|
||||
|
||||
```go
|
||||
issued, err := store.CreatePrincipalWithToken(ctx, credentialstore.CreatePrincipalInput{Alias: alias})
|
||||
// issued.RawToken is returned once; stored TokenRecord contains Digest only.
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/internal/credentialstore/principal.go`: define records, status validation, issuance, lookup/list, disable, and revoke transactions.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/principal_test.go`: cover issuance, duplicate alias, CAS revision, disable/revoke, reopen, and raw-token absence.
|
||||
|
||||
**Test Strategy:** Write `TestCreatePrincipalReturnsRawTokenOnce`, `TestPrincipalTokenLifecycleUsesRevisionCAS`, `TestRevokedTokenCannotBeReenabled`, and `TestPrincipalTokenPersistsAcrossReopen`. Inspect DB columns and captured zap entries to assert neither raw token nor a reversible form appears.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialstore` must pass with fresh execution.
|
||||
|
||||
### [API-3] Preserve startup compatibility and prove package integrity
|
||||
|
||||
**Problem:** `apps/control-plane/cmd/control-plane/main.go:87-97` defaults `database.url` to empty, so unconditional store construction would regress existing health/status use.
|
||||
|
||||
**Solution:** Treat empty URL as credential plane unavailable while leaving legacy server startup unchanged; treat any non-empty invalid/unreachable URL as fatal. Do not silently fall back from configured PostgreSQL to SQLite.
|
||||
|
||||
Before (`apps/control-plane/cmd/control-plane/main.go:92`):
|
||||
|
||||
```go
|
||||
cfg.Database.URL = ""
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
// Empty URL preserves legacy startup; a configured URL must open successfully.
|
||||
store, err := credentialstore.Open(ctx, cfg.Database.URL)
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/cmd/control-plane/server.go`: preserve nil-store compatibility and fail configured startup closed.
|
||||
- [ ] `apps/control-plane/cmd/control-plane/credential_store_test.go`: assert both branches and redacted errors/logs.
|
||||
|
||||
**Test Strategy:** Reuse the explicit startup tests from API-1; no external PostgreSQL test is required because SQL behavior is covered through SQLite and driver selection/config failure is unit-tested.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -run '^$' ./apps/control-plane/...` must compile all Control Plane packages.
|
||||
|
||||
## Modified Files Summary
|
||||
|
||||
| File | Items |
|
||||
|------|-------|
|
||||
| `go.mod` | API-1 |
|
||||
| `go.sum` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/store.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/schema.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/principal.go` | API-2 |
|
||||
| `apps/control-plane/internal/credentialstore/principal_test.go` | API-2 |
|
||||
| `apps/control-plane/cmd/control-plane/server.go` | API-1, API-3 |
|
||||
| `apps/control-plane/cmd/control-plane/credential_store_test.go` | API-1, API-3 |
|
||||
| `agent-task/m-principal-provider-credential-slot-routing/01_principal_store/CODE_REVIEW-cloud-G06.md` | API-1, API-2, API-3 |
|
||||
|
||||
## Final Verification
|
||||
|
||||
Run from `/config/workspace/iop-s0`; fresh results are required and Go test cache output is not acceptable.
|
||||
|
||||
1. `command -v go && go version && go env GOROOT` — Go resolves and reports the preflighted toolchain.
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache` — executable workspace-local temp/cache paths exist.
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go mod tidy` — dependency files are canonical with no unrelated dependency drift.
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/...` — no vet findings.
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialstore` — lifecycle, restart, redaction, and race tests pass.
|
||||
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/...` — all Control Plane tests pass.
|
||||
7. `git diff --check` — no whitespace errors.
|
||||
|
||||
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/02+01_credential_catalog plan=1 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `Implementation Checklist`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## Overview
|
||||
|
||||
date=2026-08-01
|
||||
task=m-principal-provider-credential-slot-routing/02+01_credential_catalog, plan=1, tag=API
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
|
||||
- Task ids:
|
||||
- `slot-store`: principal별 provider credential slot 원장
|
||||
- `model-binding`: route와 slot/profile/upstream model의 단일 결합
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## Archive Evidence Snapshot
|
||||
|
||||
- Prior pair: `agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/plan_local_G07_0.log`, `agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/code_review_cloud_G07_0.log`.
|
||||
- Verdict/evidence: implementation had not started and no verdict or verification output existed.
|
||||
- Replan reason: predecessor verification assumed an active `complete.log` even though PASS archives the predecessor, and envelope validation did not prove that the referenced key exists.
|
||||
- Roadmap carryover: S04 `slot-store` and S05 `model-binding` remain the completion targets.
|
||||
|
||||
## For the Review Agent
|
||||
|
||||
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
|
||||
|
||||
Compare implementation of each item against source files and verify that output in `Verification Results` matches code.
|
||||
Review completion means the following steps are finished:
|
||||
|
||||
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
|
||||
2. Archive `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_1.log` and `PLAN-local-G07.md` → `plan_local_G07_1.log`.
|
||||
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/02+01_credential_catalog/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
|
||||
4. If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata. Roadmap state check and `update-roadmap` calls are runtime responsibilities.
|
||||
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Item Completion
|
||||
|
||||
| Item | Status |
|
||||
|------|---------|
|
||||
| API-1 Persist key-backed opaque credential slots | [ ] |
|
||||
| API-2 Bind every route to one compatible slot/profile/model | [ ] |
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Persist opaque encrypted-secret revisions and slot lifecycle only when the envelope key id is registered, while enforcing principal-scoped aliases and S04 lifecycle fixtures.
|
||||
- [ ] Persist route bindings that converge to exactly one principal, slot, compatible protocol profile, and upstream model, with S05 ambiguity and cross-principal fixtures.
|
||||
- [ ] Run fresh repository race, restart, Control Plane regression, archive-predecessor, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
## Review-Only Checklist
|
||||
|
||||
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
|
||||
> Implementing agents must not modify or check this section.
|
||||
|
||||
- [ ] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
|
||||
- [ ] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
|
||||
- [ ] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G07_1.log`.
|
||||
- [ ] Archive active `PLAN-*-G??.md` to `plan_local_G07_1.log`.
|
||||
- [ ] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
|
||||
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
|
||||
- [ ] If PASS, move active task directory `agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/` to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/02+01_credential_catalog/` and update this checklist at the final archive path.
|
||||
- [ ] If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata for runtime, without modifying roadmap or directly calling `update-roadmap`.
|
||||
- [ ] If PASS for split work, remove empty active parent `agent-task/m-principal-provider-credential-slot-routing/` or verify it was kept due to remaining siblings/files.
|
||||
- [ ] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
_Record any deviations from the plan and the rationale here._
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
_Record key design decisions here._
|
||||
|
||||
## Reviewer Checkpoints
|
||||
|
||||
- Verify the archived 01 predecessor completion before implementation evidence.
|
||||
- Confirm unknown key ids fail before transaction commit and no plaintext/decrypt API exists.
|
||||
- Inspect composite ownership FKs, alias uniqueness, profile compatibility, and revision CAS.
|
||||
- Re-run multi-slot, multi-route, restart, and cross-principal fixtures before PASS.
|
||||
|
||||
## Verification Results
|
||||
|
||||
Paste actual stdout/stderr beneath every command; do not summarize or reconstruct it.
|
||||
|
||||
### API-1
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/credentialstore -run 'CredentialSlot|EnvelopeKey'`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-2
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/credentialstore -run 'Route|Binding'`
|
||||
|
||||
_Pending._
|
||||
|
||||
### Final Verification
|
||||
|
||||
1. `test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/01_principal_store/complete.log' -print | wc -l)" -eq 1`
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache`
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/internal/credentialstore`
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialstore`
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/...`
|
||||
6. `git diff --check`
|
||||
|
||||
_Pending actual output for each command._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Section Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
|
||||
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
|
||||
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
|
||||
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section |
|
||||
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholder text with actual content |
|
||||
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
|
||||
| Verification Results (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `Deviations from Plan` entry |
|
||||
| Code Review Result | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/02+01_credential_catalog plan=1 tag=API -->
|
||||
|
||||
# Credential Slot and Route Catalog
|
||||
|
||||
## For the Implementing Agent
|
||||
|
||||
Filling implementation-owned sections of `CODE_REVIEW-cloud-G07.md` is mandatory. Run every verification command, paste actual output, keep the active pair in place, and report ready for review; only code-review may finalize, rename logs, write `complete.log`, or archive. If blocked, record only exact blocker evidence and the resume condition in implementation-owned fields; do not ask the user, call user-input tools, create stop files, or classify the next state.
|
||||
|
||||
## Background
|
||||
|
||||
Provider credentials need a principal-scoped durable slot independent of protocol profiles, and every client route must converge to one slot/profile/model tuple. The store boundary must accept only encrypted envelopes whose key id is recognized; accepting a merely non-empty key id would let an unusable or fabricated envelope become active.
|
||||
|
||||
## Archive Evidence Snapshot
|
||||
|
||||
- Prior pair: `agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/plan_local_G07_0.log`, `agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/code_review_cloud_G07_0.log`.
|
||||
- Verdict/evidence: implementation had not started and no verdict or verification output existed.
|
||||
- Replan reason: predecessor verification assumed an active `complete.log` even though PASS archives the predecessor, and envelope validation did not prove that the referenced key exists.
|
||||
- Roadmap carryover: S04 `slot-store` and S05 `model-binding` remain the completion targets.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
|
||||
- Task ids:
|
||||
- `slot-store`: principal별 provider credential slot 원장
|
||||
- `model-binding`: route와 slot/profile/upstream model의 단일 결합
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## Analysis
|
||||
|
||||
### Files Read
|
||||
|
||||
- `apps/control-plane/cmd/control-plane/server.go`
|
||||
- `apps/node/internal/store/store.go`
|
||||
- `apps/node/internal/store/store_test.go`
|
||||
- `packages/go/config/config.go`
|
||||
- `packages/go/config/protocol_profile.go`
|
||||
- `packages/go/config/protocol_profile_test.go`
|
||||
|
||||
### SDD Criteria
|
||||
|
||||
- SDD: `agent-roadmap/sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md` (`승인`, 잠금 해제).
|
||||
- Targets: S04 / `slot-store`, S05 / `model-binding`; Evidence Map S04-S08 중 multi-slot, alias collision, multi-model binding.
|
||||
- Checklist와 verification은 동일 principal/vendor 복수 slot, registered key의 opaque ciphertext envelope, alias uniqueness, 한 slot의 compatible multi-profile/model route, cross-principal/incompatible/ambiguous binding 거부를 직접 증명한다.
|
||||
|
||||
### Verification Context
|
||||
|
||||
- handoff 없음. manifest, protocol-profile resolver/tests, local Control Plane test rules에서 검증을 도출했다.
|
||||
- Go 1.26.2 linux/arm64. `/tmp`는 noexec이고 기본 Go cache는 접근 불가하므로 ignored `.cache/go-build`와 `.cache/go-cache`를 명시한다.
|
||||
- Predecessor 01은 현재 미완료다. 구현 시 runtime scheduler가 active 또는 archived completion을 해결하며, verification은 허용된 동일 task-group archive 후보를 정확히 한 건 확인한다.
|
||||
- External provider, raw secret, production key manager는 필요 없다. Tests use a deterministic fake key registry and opaque ciphertext. Confidence: high.
|
||||
|
||||
### Test Coverage Gaps
|
||||
|
||||
- 기존 protocol profile tests는 credential kind/vendor compatibility와 slot ownership을 다루지 않는다.
|
||||
- multi-slot, registered/missing key, alias collision, route ownership, restart persistence tests가 없다.
|
||||
|
||||
### Symbol References
|
||||
|
||||
- Rename/remove 없음. Existing `config.ResolveProtocolProfile`은 validation dependency로만 추가 호출한다.
|
||||
|
||||
### Split Judgment
|
||||
|
||||
- Slot envelope/key registration, principal ownership, route FK/compatibility는 활성화 transaction의 한 invariant이므로 S04/S05를 같은 packet으로 유지한다.
|
||||
- Predecessor 01 `01_principal_store`: active/archived `complete.log` missing, ambiguity 없음.
|
||||
- 03, 05, 06은 이 packet의 immutable slot/route store API를 소비한다.
|
||||
|
||||
### Scope Rationale
|
||||
|
||||
- Encryption/decryption, deployment key loading/rotation, secure management transport는 Epic 3이다. 이 packet은 `EnvelopeKeyRegistry`로 key id 존재만 확인하고 plaintext/decrypt API를 만들지 않는다.
|
||||
- Edge projection/route resolution과 management adapters는 후속 packets이다.
|
||||
|
||||
### Final Routing
|
||||
|
||||
- `status=routed`, `evaluation_mode=isolated-reassessment`, finalizer=`finalize-task-policy.sh`, pair mode.
|
||||
- Build closures all true; scores `2+2+1+1+1=G07`; base/route=`local-fit`, local, `PLAN-local-G07.md`.
|
||||
- Review closures all true; scores `2+2+1+1+1=G07`; `official-review`, cloud, `gpt-5.6-sol` xhigh, `CODE_REVIEW-cloud-G07.md`.
|
||||
- `large_indivisible_context=false`; risks=`temporal_state`, `boundary_contract`, `variant_product` (3); rework=0; integrity failure=false; capability gap 없음.
|
||||
|
||||
## Dependencies and Execution Order
|
||||
|
||||
1. `01_principal_store` must complete first; runtime accepts its active or archived `complete.log`.
|
||||
2. Implement envelope/key registry and slot lifecycle before route bindings.
|
||||
3. No dependency absent from `02+01_credential_catalog` may be introduced.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Persist opaque encrypted-secret revisions and slot lifecycle only when the envelope key id is registered, while enforcing principal-scoped aliases and S04 lifecycle fixtures.
|
||||
- [ ] Persist route bindings that converge to exactly one principal, slot, compatible protocol profile, and upstream model, with S05 ambiguity and cross-principal fixtures.
|
||||
- [ ] Run fresh repository race, restart, Control Plane regression, archive-predecessor, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
### [API-1] Persist key-backed opaque credential slots
|
||||
|
||||
**Problem:** no Control Plane slot store exists, and checking only a non-empty `key_id` cannot satisfy S04's missing-envelope-key rejection.
|
||||
|
||||
**Solution:** Add constrained slot and immutable secret-revision tables. Add a source-compatible store option for `EnvelopeKeyRegistry`; the existing `Open` path installs an unavailable registry so production slot secret mutations fail closed until Epic-3 key wiring exists. Creation/rotation/activation rejects unknown key ids before transaction commit. Persist only algorithm, key id/version, nonce, ciphertext, AAD metadata, lifecycle, and revision.
|
||||
|
||||
Before (`apps/control-plane/cmd/control-plane/server.go:29-31`):
|
||||
|
||||
```go
|
||||
if cfg.Database.URL != "" {
|
||||
logger.Info("control-plane database configured", databaseLogFields(cfg.Database.URL)...)
|
||||
}
|
||||
```
|
||||
|
||||
After domain contract:
|
||||
|
||||
```go
|
||||
import "context"
|
||||
|
||||
type EnvelopeKeyRegistry interface {
|
||||
HasEnvelopeKey(context.Context, string, uint64) (bool, error)
|
||||
}
|
||||
if !known { return CredentialSlot{}, ErrEnvelopeKeyUnavailable }
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/internal/credentialstore/schema.go`: add slot/secret revision tables and constraints.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/store.go`: add source-compatible key-registry option and unavailable-by-default implementation.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/secret_envelope.go`: define opaque envelope and key-registry boundary.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/slot.go`: implement create/list/rotate/disable/enable/revoke with revision CAS and registered-key validation.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/slot_test.go`: cover same-vendor slots, registered/missing key, alias collision, lifecycle, ciphertext inspection, and reopen.
|
||||
|
||||
**Test Strategy:** Write `TestCredentialSlotsAllowSameVendorMultipleSlots`, `TestCredentialSlotRejectsUnknownEnvelopeKey`, `TestCredentialSlotRotationRequiresRegisteredKey`, `TestCredentialSlotAliasUniqueWithinPrincipal`, and `TestCredentialSlotLifecyclePersists` using fake key registry and opaque bytes.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/credentialstore -run 'CredentialSlot|EnvelopeKey'` passes.
|
||||
|
||||
### [API-2] Bind every route to one compatible slot/profile/model
|
||||
|
||||
**Problem:** the global model catalog has no durable principal route id and cannot fail closed on cross-principal or ambiguous alias selection.
|
||||
|
||||
**Solution:** Add route records with principal+slot composite ownership, canonical route id, optional normalized alias, profile id, upstream model, resource selector, status, and revision. Resolve profiles through `config.ResolveProtocolProfile`, validate vendor/auth compatibility, and enforce uniqueness transactionally.
|
||||
|
||||
Before (`packages/go/config/protocol_profile.go`, resolver contract):
|
||||
|
||||
```go
|
||||
func ResolveProtocolProfile(selector, legacyType string, catalog map[string]ProtocolProfileConf) (ConcreteProtocolProfile, error)
|
||||
```
|
||||
|
||||
After usage:
|
||||
|
||||
```go
|
||||
profile, err := config.ResolveProtocolProfile(input.ProfileID, input.Vendor, config.BuiltInProtocolProfiles)
|
||||
if err != nil { return RouteBinding{}, ErrIncompatibleProfile }
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/internal/credentialstore/schema.go`: add composite FKs and principal-scoped uniqueness constraints.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/route.go`: implement create/list/update/disable/revoke and strict lookup.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/route_test.go`: cover same upstream model/two slots, one slot/multiple routes, incompatible profile, ambiguous alias, and cross-principal rejection.
|
||||
|
||||
**Test Strategy:** Write `TestRoutesDisambiguateSameUpstreamModelBySlot`, `TestSlotSupportsMultipleCompatibleRoutes`, `TestRouteRejectsCrossPrincipalSlot`, `TestRouteRejectsIncompatibleProfile`, and `TestRouteAliasCollisionFailsClosed`.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/credentialstore -run 'Route|Binding'` passes.
|
||||
|
||||
## Modified Files Summary
|
||||
|
||||
| File | Items |
|
||||
|------|-------|
|
||||
| `apps/control-plane/internal/credentialstore/schema.go` | API-1, API-2 |
|
||||
| `apps/control-plane/internal/credentialstore/store.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/secret_envelope.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/slot.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/slot_test.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/route.go` | API-2 |
|
||||
| `apps/control-plane/internal/credentialstore/route_test.go` | API-2 |
|
||||
| `agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/CODE_REVIEW-cloud-G07.md` | API-1, API-2 |
|
||||
|
||||
## Final Verification
|
||||
|
||||
Run from `/config/workspace/iop-s0`; cached Go results are not acceptable.
|
||||
|
||||
1. `test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/01_principal_store/complete.log' -print | wc -l)" -eq 1` — exactly one archived predecessor completion exists.
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache` — executable Go paths exist.
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/internal/credentialstore` — no vet findings.
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialstore` — S04/S05 lifecycle, isolation, restart, and race tests pass.
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/...` — Control Plane regression suite passes.
|
||||
6. `git diff --check` — no whitespace errors.
|
||||
|
||||
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/02+01_credential_catalog plan=0 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `Implementation Checklist`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## Overview
|
||||
|
||||
date=2026-08-01
|
||||
task=m-principal-provider-credential-slot-routing/02+01_credential_catalog, plan=0, tag=API
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
|
||||
- Task ids:
|
||||
- `slot-store`: principal별 provider credential slot 원장
|
||||
- `model-binding`: route와 slot/profile/upstream model의 단일 결합
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## For the Review Agent
|
||||
|
||||
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
|
||||
|
||||
Compare implementation of each item against source files and verify that output in `Verification Results` matches code.
|
||||
Review completion means the following steps are finished:
|
||||
|
||||
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
|
||||
2. Archive `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_0.log` and `PLAN-local-G07.md` → `plan_local_G07_0.log`.
|
||||
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/02+01_credential_catalog/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
|
||||
4. If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata. Roadmap state check and `update-roadmap` calls are runtime responsibilities.
|
||||
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Item Completion
|
||||
|
||||
| Item | Status |
|
||||
|------|---------|
|
||||
| API-1 Persist opaque credential slots independently of profiles | [ ] |
|
||||
| API-2 Bind each client route to one slot/profile/model | [ ] |
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Add opaque encrypted-secret revisions and principal-scoped credential-slot lifecycle with strict alias constraints.
|
||||
- [ ] Add route bindings that converge to exactly one principal, slot, compatible protocol profile, and upstream model.
|
||||
- [ ] Prove multi-slot, multi-profile/model, collision, cross-principal, lifecycle, and restart behavior with fresh repository tests.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
## Review-Only Checklist
|
||||
|
||||
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
|
||||
> Implementing agents must not modify or check this section.
|
||||
|
||||
- [ ] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
|
||||
- [ ] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
|
||||
- [ ] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G07_0.log`.
|
||||
- [ ] Archive active `PLAN-*-G??.md` to `plan_local_G07_0.log`.
|
||||
- [ ] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
|
||||
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
|
||||
- [ ] If PASS, move active task directory `agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/` to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/02+01_credential_catalog/` and update this checklist at the final archive path.
|
||||
- [ ] If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata for runtime, without modifying roadmap or directly calling `update-roadmap`.
|
||||
- [ ] If PASS for split work, remove empty active parent `agent-task/m-principal-provider-credential-slot-routing/` or verify it was kept due to remaining siblings/files.
|
||||
- [ ] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
_Record any deviations from the plan and the rationale here._
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
_Record key design decisions here._
|
||||
|
||||
## Reviewer Checkpoints
|
||||
|
||||
- Verify the 01 predecessor complete log before evaluating implementation evidence.
|
||||
- Confirm the repository accepts only opaque encrypted envelopes and has no plaintext/decrypt API.
|
||||
- Inspect composite FKs/unique constraints and application checks for principal ownership, alias normalization, and profile compatibility.
|
||||
- Re-run same-vendor multi-slot, same-upstream multi-route, restart, CAS, and cross-principal failure fixtures.
|
||||
|
||||
## Verification Results
|
||||
|
||||
Paste actual stdout/stderr beneath every command; do not summarize or reconstruct it.
|
||||
|
||||
### API-1
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/credentialstore -run 'CredentialSlot|EncryptedSecret'`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-2
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/credentialstore -run 'Route|Binding'`
|
||||
|
||||
_Pending._
|
||||
|
||||
### Final Verification
|
||||
|
||||
1. `test -f agent-task/m-principal-provider-credential-slot-routing/01_principal_store/complete.log`
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache`
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/internal/credentialstore`
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialstore`
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/...`
|
||||
6. `git diff --check`
|
||||
|
||||
_Pending actual output for each command._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Section Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
|
||||
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
|
||||
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
|
||||
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section |
|
||||
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholder text with actual content |
|
||||
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
|
||||
| Verification Results (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `Deviations from Plan` entry |
|
||||
| Code Review Result | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/02+01_credential_catalog plan=0 tag=API -->
|
||||
|
||||
# Credential Slot and Route Catalog
|
||||
|
||||
## For the Implementing Agent
|
||||
|
||||
Filling the implementation-owned sections of `CODE_REVIEW-cloud-G07.md` is mandatory. Run every verification command, paste actual notes/output, keep the active pair in place, and report ready for review; finalization belongs only to the code-review skill. If blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields; do not ask the user, call user-input tools, create stop files, classify state, rename logs, write `complete.log`, or archive.
|
||||
|
||||
## Background
|
||||
|
||||
Provider credentials are currently coupled to static Edge provider configuration. The milestone needs a Control Plane source of truth where multiple credential slots per principal are independent from protocol profiles and client-facing routes resolve unambiguously to one slot/profile/upstream-model tuple.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
|
||||
- Task ids:
|
||||
- `slot-store`: principal별 provider credential slot 원장
|
||||
- `model-binding`: route와 slot/profile/upstream model의 단일 결합
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## Analysis
|
||||
|
||||
### Files Read
|
||||
|
||||
- `go.mod`
|
||||
- `apps/control-plane/cmd/control-plane/server.go`
|
||||
- `apps/node/internal/store/store.go`
|
||||
- `apps/node/internal/store/store_test.go`
|
||||
- `packages/go/config/config.go`
|
||||
- `packages/go/config/protocol_profile.go`
|
||||
- `packages/go/config/protocol_profile_test.go`
|
||||
|
||||
### SDD Criteria
|
||||
|
||||
- SDD: `agent-roadmap/sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md` (`승인`, 잠금 해제).
|
||||
- Targets: S04 / `slot-store`, S05 / `model-binding`.
|
||||
- Evidence Map row: S04-S08의 multi-slot, alias collision, multi-model binding 근거 중 S04-S05.
|
||||
- Checklist/verification은 동일 principal/vendor 복수 slot, ciphertext envelope 필수, principal-scoped alias uniqueness, 한 slot의 compatible multi-profile/model route, cross-principal/incompatible/ambiguous route fail-closed를 직접 증명한다.
|
||||
|
||||
### Verification Context
|
||||
|
||||
- handoff 없음. 저장소 manifest, protocol profile resolver/tests, Control Plane store plan contract, project/domain/local test rules로 명령을 도출했다.
|
||||
- Local runner `/config/workspace/iop-s0`, Go 1.26.2 linux/arm64. 기본 Go temp/cache 권한 문제 때문에 ignored `.cache/go-build`와 `.cache/go-cache`를 명시한다.
|
||||
- Precondition: sibling `01_principal_store/complete.log`가 필요하다. 현재 active/archived completion은 없어 implementation을 시작할 수 없지만 계획 작성과 routing에는 ambiguity가 없다.
|
||||
- 외부 provider/DB/secret 없이 temp SQLite와 deterministic opaque envelopes로 검증 가능하다. Confidence: high.
|
||||
|
||||
### Test Coverage Gaps
|
||||
|
||||
- 기존 config protocol profile tests는 built-in profile resolution만 다루며 credential slot compatibility/lifecycle을 다루지 않는다.
|
||||
- multi-slot, alias collision, route ownership, route ID uniqueness, encrypted envelope validation을 다루는 Control Plane test가 전무하므로 신규 repository tests가 필요하다.
|
||||
|
||||
### Symbol References
|
||||
|
||||
- Rename/remove 없음. 기존 `config.ResolveProtocolProfile`은 validation dependency로 호출만 추가한다.
|
||||
|
||||
### Split Judgment
|
||||
|
||||
- Stable contract: slot row는 opaque encrypted revision을 보존하고 route row는 정확히 한 principal/slot/profile/model을 참조한다. 두 테이블의 FK와 활성화 transaction이 하나의 invariant이므로 S04/S05를 같은 packet으로 유지한다.
|
||||
- Predecessor 01 = `01_principal_store`; active `complete.log` missing, archived candidate 없음. `agent-task/m-principal-provider-credential-slot-routing/01_principal_store/complete.log` 생성 전 구현 금지.
|
||||
- Consumers: 03은 projection snapshot을, 05는 management mutation API를 사용한다.
|
||||
|
||||
### Scope Rationale
|
||||
|
||||
- raw provider credential 암호화 구현과 실행 시 secret delivery는 Epic 3 소유이다. 이 packet은 non-empty key id/nonce/ciphertext를 가진 opaque envelope만 수락하고 plaintext를 받지 않는다.
|
||||
- Edge cache, discovery/dispatch, management transport는 후속 packet으로 제외한다.
|
||||
- built-in protocol profile 정의는 변경하지 않고 resolver 결과를 compatibility oracle로 사용한다.
|
||||
|
||||
### Final Routing
|
||||
|
||||
- `status=routed`, `evaluation_mode=first-pass`, finalizer=`finalize-task-policy.sh`, pair mode.
|
||||
- Build closures 모두 true; scores `2+2+1+1+1=G07`; base/route=`local-fit`, lane=`local`, canonical=`PLAN-local-G07.md`.
|
||||
- Review closures 모두 true; scores `2+2+1+1+1=G07`; `official-review`, cloud, `gpt-5.6-sol` xhigh, canonical=`CODE_REVIEW-cloud-G07.md`.
|
||||
- `large_indivisible_context=false`; risks=`temporal_state`, `boundary_contract`, `variant_product` (3); rework=0; integrity failure=false; capability gap 없음.
|
||||
|
||||
## Dependencies and Execution Order
|
||||
|
||||
1. `01_principal_store` must produce `agent-task/m-principal-provider-credential-slot-routing/01_principal_store/complete.log`.
|
||||
2. Implement schema/envelope/slot lifecycle, then route bindings against that stable contract.
|
||||
3. Do not add dependencies absent from directory name `02+01_credential_catalog`.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Add opaque encrypted-secret revisions and principal-scoped credential-slot lifecycle with strict alias constraints.
|
||||
- [ ] Add route bindings that converge to exactly one principal, slot, compatible protocol profile, and upstream model.
|
||||
- [ ] Prove multi-slot, multi-profile/model, collision, cross-principal, lifecycle, and restart behavior with fresh repository tests.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
### [API-1] Persist opaque credential slots independently of profiles
|
||||
|
||||
**Problem:** the repository has no Control Plane credential tables; Edge provider auth config couples secret material and provider protocol selection.
|
||||
|
||||
**Solution:** Extend the credential-store migration with `credential_slots` and immutable `credential_secret_revisions`. Define stable UUID `slot_id`, principal FK, vendor, credential kind, optional normalized alias, lifecycle status/revision, and current secret revision. Accept only an `EncryptedSecretEnvelope` containing key id, nonce, ciphertext, and algorithm; never accept plaintext in this package.
|
||||
|
||||
Before (`apps/control-plane/cmd/control-plane/server.go:29-31`):
|
||||
|
||||
```go
|
||||
if cfg.Database.URL != "" {
|
||||
logger.Info("control-plane database configured", databaseLogFields(cfg.Database.URL)...)
|
||||
}
|
||||
```
|
||||
|
||||
After domain shape:
|
||||
|
||||
```go
|
||||
type EncryptedSecretEnvelope struct {
|
||||
KeyID, Algorithm string
|
||||
Nonce, Ciphertext []byte
|
||||
}
|
||||
type CredentialSlot struct {
|
||||
SlotID, PrincipalID, Vendor, CredentialKind string
|
||||
Alias, Status string
|
||||
Revision, SecretRevision uint64
|
||||
}
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/internal/credentialstore/schema.go`: add constrained slot/secret revision tables and indexes.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/secret_envelope.go`: define opaque validated envelope without decrypt/plaintext API.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/slot.go`: create/list/rotate/disable/revoke with revision CAS and principal-scoped alias normalization.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/slot_test.go`: test two same-vendor slots, alias collision, envelope rejection, rotate, disable/revoke, reopen.
|
||||
|
||||
**Test Strategy:** Write `TestCredentialSlotsAllowSameVendorMultipleSlots`, `TestCredentialSlotRejectsMissingEnvelopeFields`, `TestCredentialSlotAliasUniqueWithinPrincipal`, `TestCredentialSlotRotationCreatesRevision`, and `TestCredentialSlotLifecyclePersists`. Fixtures use distinct opaque ciphertext bytes and never raw API tokens.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/credentialstore -run 'CredentialSlot|EncryptedSecret'` passes.
|
||||
|
||||
### [API-2] Bind each client route to one slot/profile/model
|
||||
|
||||
**Problem:** no durable `route_id` exists; config model catalogs can map a model group to several providers and cannot express principal-scoped explicit slot selection.
|
||||
|
||||
**Solution:** Add route records with principal FK, slot FK, client-facing stable `route_id`, optional display alias, protocol profile id, upstream model, status/revision. Resolve the profile via `config.ResolveProtocolProfile`; validate vendor/credential-kind compatibility; enforce route and alias uniqueness within a principal and transactionally reject cross-principal slot references.
|
||||
|
||||
Before (`packages/go/config/protocol_profile.go`, existing resolver contract):
|
||||
|
||||
```go
|
||||
func ResolveProtocolProfile(profileID, providerType string, catalog map[string]ConcreteProtocolProfile) (ConcreteProtocolProfile, error)
|
||||
```
|
||||
|
||||
After usage:
|
||||
|
||||
```go
|
||||
profile, err := config.ResolveProtocolProfile(input.ProfileID, input.Vendor, config.BuiltInProtocolProfiles)
|
||||
if err != nil {
|
||||
return RouteBinding{}, ErrIncompatibleProfile
|
||||
}
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/internal/credentialstore/schema.go`: add route binding table, principal/slot composite FK, and uniqueness constraints.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/route.go`: create/list/update/disable/revoke and strict lookup by principal+route id.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/route_test.go`: cover same upstream model on two slots, one slot on multiple compatible routes, invalid profile, alias ambiguity, and cross-principal rejection.
|
||||
|
||||
**Test Strategy:** Write `TestRoutesDisambiguateSameUpstreamModelBySlot`, `TestSlotSupportsMultipleCompatibleProfileModelRoutes`, `TestRouteRejectsCrossPrincipalSlot`, `TestRouteRejectsIncompatibleProfile`, and `TestRouteAliasCollisionFailsClosed`. Assert each lookup returns one tuple and never guesses.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/credentialstore -run 'Route|Binding'` passes.
|
||||
|
||||
## Modified Files Summary
|
||||
|
||||
| File | Items |
|
||||
|------|-------|
|
||||
| `apps/control-plane/internal/credentialstore/schema.go` | API-1, API-2 |
|
||||
| `apps/control-plane/internal/credentialstore/secret_envelope.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/slot.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/slot_test.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/route.go` | API-2 |
|
||||
| `apps/control-plane/internal/credentialstore/route_test.go` | API-2 |
|
||||
| `agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/CODE_REVIEW-cloud-G07.md` | API-1, API-2 |
|
||||
|
||||
## Final Verification
|
||||
|
||||
Run from `/config/workspace/iop-s0`; cached test results are not acceptable.
|
||||
|
||||
1. `test -f agent-task/m-principal-provider-credential-slot-routing/01_principal_store/complete.log` — predecessor is complete.
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache` — executable Go temp/cache paths exist.
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/internal/credentialstore` — no vet findings.
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialstore` — all lifecycle, compatibility, isolation, reopen, and race tests pass.
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/...` — Control Plane suite passes.
|
||||
6. `git diff --check` — no whitespace errors.
|
||||
|
||||
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/03+01,02_projection_auth plan=1 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `Implementation Checklist`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## Overview
|
||||
|
||||
date=2026-08-01
|
||||
task=m-principal-provider-credential-slot-routing/03+01,02_projection_auth, plan=1, tag=API
|
||||
|
||||
## Archive Evidence Snapshot
|
||||
|
||||
- Prior pair: `agent-task/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/plan_cloud_G10_0.log`, `agent-task/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/code_review_cloud_G10_0.log`.
|
||||
- Verdict/evidence: implementation had not started; no verdict or verification output.
|
||||
- Replan reason: the previous plan pushed authorization state over the current unauthenticated plain TCP connector and incorrectly claimed S02/S03 completion before S10 `secure-transport`.
|
||||
- Roadmap carryover: this is foundation-only. PASS must not check `auth-projection` or `surface-auth`; authenticated CP–Edge activation remains required.
|
||||
|
||||
## For the Review Agent
|
||||
|
||||
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
|
||||
|
||||
Compare implementation of each item against source files and verify that output in `Verification Results` matches code.
|
||||
Review completion means the following steps are finished:
|
||||
|
||||
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
|
||||
2. Archive `CODE_REVIEW-cloud-G09.md` → `code_review_cloud_G09_1.log` and `PLAN-cloud-G09.md` → `plan_cloud_G09_1.log`.
|
||||
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
|
||||
4. If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata. Roadmap state check and `update-roadmap` calls are runtime responsibilities.
|
||||
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Item Completion
|
||||
|
||||
| Item | Status |
|
||||
|------|---------|
|
||||
| API-1 Define durable secret-free projection snapshots | [ ] |
|
||||
| API-2 Add a shared bounded Edge cache | [ ] |
|
||||
| API-3 Add dormant managed-mode surface authentication | [ ] |
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Build durable, secret-free projection snapshots with monotonic generation and additive generated message types, covering the S02 data/state fixtures without transport activation.
|
||||
- [ ] Build a bounded immutable Edge cache that applies only higher fresh generations and remains managed fail-closed after expiry/revocation under race tests.
|
||||
- [ ] Make OpenAI bearer and Anthropic bearer/x-api-key authentication consume the shared cache only when a verified snapshot is installed, with S03 mismatch and zero-handler-call fixtures.
|
||||
- [ ] Document and test that the current unauthenticated CP–Edge connector cannot install or receive the projection; leave S02/S03 Roadmap completion for secure-transport rollout.
|
||||
- [ ] Run fresh generation, race, Edge/client regression, dormant-activation search, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
## Review-Only Checklist
|
||||
|
||||
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
|
||||
> Implementing agents must not modify or check this section.
|
||||
|
||||
- [ ] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
|
||||
- [ ] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
|
||||
- [ ] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G09_1.log`.
|
||||
- [ ] Archive active `PLAN-*-G??.md` to `plan_cloud_G09_1.log`.
|
||||
- [ ] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
|
||||
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
|
||||
- [ ] If PASS, move active task directory `agent-task/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/` to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/` and update this checklist at the final archive path.
|
||||
- [ ] If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata for runtime, without modifying roadmap or directly calling `update-roadmap`.
|
||||
- [ ] If PASS for split work, remove empty active parent `agent-task/m-principal-provider-credential-slot-routing/` or verify it was kept due to remaining siblings/files.
|
||||
- [ ] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
_Record any deviations from the plan and the rationale here._
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
_Record key design decisions here._
|
||||
|
||||
## Reviewer Checkpoints
|
||||
|
||||
- Confirm no Roadmap Targets section exists and PASS cannot check S02/S03.
|
||||
- Verify generation commits with mutations and the cache bounds/deep-copy/atomic higher-generation fence under `-race`.
|
||||
- Confirm managed expiry/revocation rejects before handlers while unmanaged config behavior remains unchanged.
|
||||
- Prove Connector, EdgeServer, and Runtime have no projection listener/push/wiring on current plain transport.
|
||||
- Check contracts describe reserved/gated behavior rather than active production rollout.
|
||||
|
||||
## Verification Results
|
||||
|
||||
Paste actual stdout/stderr beneath every command; do not summarize or reconstruct it.
|
||||
|
||||
### API-1
|
||||
|
||||
`make proto && make proto-dart && GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/credentialstore`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-2
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/edge/internal/authprojection ./apps/edge/internal/input`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-3
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'Managed|Principal|Anthropic.*Auth'`
|
||||
|
||||
_Pending._
|
||||
|
||||
### Final Verification
|
||||
|
||||
1. `test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/01_principal_store/complete.log' -print | wc -l)" -eq 1 && test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log' -print | wc -l)" -eq 1`
|
||||
2. `command -v go && go version && command -v protoc && protoc --version && command -v protoc-gen-go && command -v protoc-gen-dart && command -v flutter && flutter --version`
|
||||
3. `mkdir -p .cache/go-build .cache/go-cache`
|
||||
4. `make proto && make proto-dart`
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/internal/credentialstore ./apps/edge/internal/authprojection ./apps/edge/internal/input ./apps/edge/internal/openai`
|
||||
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialstore ./apps/edge/internal/authprojection ./apps/edge/internal/input ./apps/edge/internal/openai`
|
||||
7. `make client-test`
|
||||
8. `if rg --sort path -n 'PrincipalProjection' apps/control-plane/internal/wire/edge_server.go apps/edge/internal/controlplane/connector.go apps/edge/internal/bootstrap/runtime.go; then exit 1; else exit 0; fi`
|
||||
9. `git diff --check`
|
||||
|
||||
_Pending actual output for each command._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Section Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
|
||||
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
|
||||
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
|
||||
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section |
|
||||
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholder text with actual content |
|
||||
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
|
||||
| Verification Results (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `Deviations from Plan` entry |
|
||||
| Code Review Result | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/03+01,02_projection_auth plan=1 tag=API -->
|
||||
|
||||
# Auth Projection and Surface-Auth Foundation
|
||||
|
||||
## For the Implementing Agent
|
||||
|
||||
Filling implementation-owned sections of `CODE_REVIEW-cloud-G09.md` is mandatory. Run every verification command, paste actual output, keep active files in place, and report ready for review; only code-review may finalize, rename logs, write `complete.log`, or archive. If blocked, record exact blocker evidence and resume conditions only in implementation-owned fields; do not ask the user, call user-input tools, create stop files, or classify next state.
|
||||
|
||||
## Background
|
||||
|
||||
The Edge needs a bounded immutable principal projection cache and common OpenAI/Anthropic authentication logic. The current CP–Edge TCP connection has no authenticated peer or transport integrity, so wiring an authorization projection over it would allow an untrusted source to replace token verifiers and routes; this packet deliberately builds but does not activate that transport path.
|
||||
|
||||
## Archive Evidence Snapshot
|
||||
|
||||
- Prior pair: `agent-task/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/plan_cloud_G10_0.log`, `agent-task/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/code_review_cloud_G10_0.log`.
|
||||
- Verdict/evidence: implementation had not started; no verdict or verification output.
|
||||
- Replan reason: the previous plan pushed authorization state over the current unauthenticated plain TCP connector and incorrectly claimed S02/S03 completion before S10 `secure-transport`.
|
||||
- Roadmap carryover: this is foundation-only. PASS must not check `auth-projection` or `surface-auth`; authenticated CP–Edge activation remains required.
|
||||
|
||||
## Analysis
|
||||
|
||||
### Files Read
|
||||
|
||||
- `proto/iop/control.proto`
|
||||
- `proto/gen/iop/control.pb.go`
|
||||
- `apps/client/lib/gen/proto/iop/control.pb.dart`
|
||||
- `apps/client/lib/gen/proto/iop/control.pbenum.dart`
|
||||
- `apps/client/lib/gen/proto/iop/control.pbjson.dart`
|
||||
- `apps/client/lib/gen/proto/iop/control.pbserver.dart`
|
||||
- `apps/control-plane/internal/wire/edge.go`
|
||||
- `apps/control-plane/internal/wire/edge_server.go`
|
||||
- `apps/edge/internal/controlplane/connector.go`
|
||||
- `apps/edge/internal/controlplane/connector_test.go`
|
||||
- `apps/edge/internal/input/manager.go`
|
||||
- `apps/edge/internal/input/manager_test.go`
|
||||
- `apps/edge/internal/openai/server.go`
|
||||
- `apps/edge/internal/openai/principal.go`
|
||||
- `apps/edge/internal/openai/anthropic_types.go`
|
||||
- `apps/edge/internal/openai/routes.go`
|
||||
- `apps/edge/internal/openai/identity_metering_test.go`
|
||||
- `apps/edge/internal/openai/anthropic_surface_test.go`
|
||||
- `apps/edge/internal/openai/openai_auth_routes_models_test.go`
|
||||
- `agent-contract/inner/control-plane-edge-wire.md`
|
||||
- `agent-contract/outer/openai-compatible-api.md`
|
||||
- `agent-contract/outer/anthropic-compatible-api.md`
|
||||
|
||||
### SDD Criteria
|
||||
|
||||
- SDD: `agent-roadmap/sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md` (`승인`, 잠금 해제).
|
||||
- Foundation maps to S02 / `auth-projection` and S03 / `surface-auth`, Evidence Map S01-S03, but intentionally does not satisfy their production synchronization/authorization evidence.
|
||||
- Checklist covers durable generation, bounded atomic cache, fresh/stale/revoked/out-of-order fixtures, and dual-header zero-handler-call behavior. Final verification also proves no current Connector/EdgeServer call site activates projection messages.
|
||||
|
||||
### Verification Context
|
||||
|
||||
- handoff 없음. SDD secure-delivery clause explicitly states current plain TCP/WS cannot carry credential-bearing operations; current CP–Edge contract has hello but no peer authentication.
|
||||
- Go 1.26.2, protoc 29.3, Go/Dart generators present, Flutter 3.41.5. Workspace ignored Go temp/cache paths are required because `/tmp` is noexec.
|
||||
- Preconditions 01 and 02 are currently incomplete; runtime resolves their archived completions before implementation.
|
||||
- No external provider or TLS runner is required because this packet is dormant foundation. Confidence: high.
|
||||
- The current dirty baseline already contains user-owned authentication fixes in `principal.go`, `anthropic_types.go`, `anthropic_surface_test.go`, and `openai_auth_routes_models_test.go`; preserve and integrate those semantics instead of replacing or reverting them.
|
||||
|
||||
### Test Coverage Gaps
|
||||
|
||||
- Static Edge auth tests cover header normalization but not generation/expiry/revocation or concurrent apply/read.
|
||||
- No durable projection snapshot builder or bounded cache exists.
|
||||
- No test currently proves projection activation is absent on the unauthenticated connector.
|
||||
|
||||
### Symbol References
|
||||
|
||||
- Rename/remove 없음. `openai.NewServer` signature remains unchanged; many tests rely on it.
|
||||
- `input.NewManager` is called by `bootstrap.NewRuntime`; preserve its signature and expose the shared cache on Manager.
|
||||
- No `NewConnector`/`NewEdgeServer` call site is changed in this packet.
|
||||
|
||||
### Split Judgment
|
||||
|
||||
- Durable snapshot format and Edge cache/header semantics form a stable dormant contract with deterministic tests.
|
||||
- Predecessors 01 and 02: active/archived completion missing, ambiguity 없음.
|
||||
- Authenticated transport activation is excluded and must be planned with S10; therefore no Roadmap Targets section is present.
|
||||
- Packet 04 may consume only the dormant cache contract and likewise cannot claim public rollout.
|
||||
|
||||
### Scope Rationale
|
||||
|
||||
- Do not register projection listeners, push from `EdgeServer`, or wire `bootstrap.Runtime.ControlPlane` to the cache on the current plain transport.
|
||||
- Do not carry ciphertext/plaintext, add TLS, or implement credential leases.
|
||||
- Preserve the existing user changes that scope Anthropic `x-api-key`/version handling away from ordinary OpenAI routes; managed-mode additions must extend that baseline without reverting it.
|
||||
- Contracts must mark messages/cache as reserved/gated foundation, not active production behavior.
|
||||
|
||||
### Final Routing
|
||||
|
||||
- `status=routed`, `evaluation_mode=isolated-reassessment`, finalizer=`finalize-task-policy.sh`, pair mode.
|
||||
- Build closures all true for dormant foundation; scores `2+2+1+2+2=G09`; base/route=`grade-boundary`, cloud, `PLAN-cloud-G09.md`.
|
||||
- Review closures all true; scores `2+2+1+2+2=G09`; `official-review`, cloud, `gpt-5.6-sol` xhigh, `CODE_REVIEW-cloud-G09.md`.
|
||||
- `large_indivisible_context=false`; risks=`temporal_state`, `concurrent_consistency`, `boundary_contract`, `variant_product` (4); rework=0; integrity failure=false; no capability gap.
|
||||
|
||||
## Dependencies and Execution Order
|
||||
|
||||
1. `01_principal_store` and `02+01_credential_catalog` must complete; runtime accepts their archived `complete.log` files.
|
||||
2. Add durable snapshot generation and additive message schema, then the shared Edge cache, then dormant HTTP managed-mode logic.
|
||||
3. Do not add dependencies absent from `03+01,02_projection_auth`.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Build durable, secret-free projection snapshots with monotonic generation and additive generated message types, covering the S02 data/state fixtures without transport activation.
|
||||
- [ ] Build a bounded immutable Edge cache that applies only higher fresh generations and remains managed fail-closed after expiry/revocation under race tests.
|
||||
- [ ] Make OpenAI bearer and Anthropic bearer/x-api-key authentication consume the shared cache only when a verified snapshot is installed, with S03 mismatch and zero-handler-call fixtures.
|
||||
- [ ] Document and test that the current unauthenticated CP–Edge connector cannot install or receive the projection; leave S02/S03 Roadmap completion for secure-transport rollout.
|
||||
- [ ] Run fresh generation, race, Edge/client regression, dormant-activation search, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
### [API-1] Define durable secret-free projection snapshots
|
||||
|
||||
**Problem:** `proto/iop/control.proto` and the Control Plane store have no generation/expiry token+route snapshot contract.
|
||||
|
||||
**Solution:** Add additive projection value messages, but no live request listener. Add a DB singleton generation bumped transactionally by token/slot/route mutations and a bounded snapshot builder containing active digests and route metadata only.
|
||||
|
||||
Before (`apps/control-plane/internal/wire/edge.go:12-18`):
|
||||
|
||||
```go
|
||||
return proto_socket.ParserMap{
|
||||
proto_socket.TypeNameOf(&iop.EdgeHelloRequest{}): func(b []byte) (proto.Message, error) {
|
||||
req := &iop.EdgeHelloRequest{}
|
||||
return req, proto.Unmarshal(b, req)
|
||||
},
|
||||
```
|
||||
|
||||
After schema boundary:
|
||||
|
||||
```protobuf
|
||||
message PrincipalProjection {
|
||||
uint64 generation = 1;
|
||||
int64 issued_at_unix_nano = 2;
|
||||
int64 expires_at_unix_nano = 3;
|
||||
repeated ProjectedPrincipalToken tokens = 4;
|
||||
repeated ProjectedPrincipalRoute routes = 5;
|
||||
}
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `proto/iop/control.proto`: add value/apply types additively; do not register an active wire operation.
|
||||
- [ ] `proto/gen/iop/control.pb.go`: regenerate Go.
|
||||
- [ ] `apps/client/lib/gen/proto/iop/control.pb.dart`: regenerate Dart.
|
||||
- [ ] `apps/client/lib/gen/proto/iop/control.pbenum.dart`: regenerate Dart enums.
|
||||
- [ ] `apps/client/lib/gen/proto/iop/control.pbjson.dart`: regenerate Dart metadata.
|
||||
- [ ] `apps/client/lib/gen/proto/iop/control.pbserver.dart`: regenerate Dart helpers.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/schema.go`: add durable projection generation.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/principal.go`: bump generation with token mutations.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/slot.go`: bump generation with slot mutations.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/route.go`: bump generation with route mutations.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/projection.go`: build bounded immutable snapshots.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/projection_test.go`: verify durability/filtering/bounds.
|
||||
|
||||
**Test Strategy:** Write `TestProjectionGenerationCommitsWithMutation`, `TestProjectionGenerationPersistsAcrossReopen`, and `TestProjectionSnapshotOmitsInactiveRecords` with temp SQLite and injected clocks.
|
||||
|
||||
**Verification:** `make proto && make proto-dart && GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/credentialstore` passes.
|
||||
|
||||
### [API-2] Add a shared bounded Edge cache
|
||||
|
||||
**Problem:** `apps/edge/internal/openai/server.go:50-68` owns mutable config/catalog state but has no fenced principal projection snapshot.
|
||||
|
||||
**Solution:** Add `apps/edge/internal/authprojection` with injected clock, hard entry/byte limits, deep-copy validation, atomic snapshot swap, digest lookup, and explicit unmanaged/fresh/expired states. `input.Manager` constructs one cache and gives it to OpenAI; it remains unmanaged because Runtime/Connector receives no apply handle.
|
||||
|
||||
Required imports for the new cache boundary:
|
||||
|
||||
```go
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
iop "iop/proto/gen/iop"
|
||||
)
|
||||
```
|
||||
|
||||
Before (`apps/edge/internal/input/manager.go:20-29`):
|
||||
|
||||
```go
|
||||
func NewManager(cfg config.EdgeConfig, svc *edgeservice.Service, logger *zap.Logger) *Manager {
|
||||
openaiServer := edgeopenai.NewServer(cfg.OpenAI, svc, logger.Named("openai"))
|
||||
openaiServer.SetEdgeID(cfg.Edge.ID)
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
projection := authprojection.NewCache(authprojection.DefaultLimits(), time.Now)
|
||||
openaiServer := edgeopenai.NewServer(cfg.OpenAI, svc, logger.Named("openai"))
|
||||
openaiServer.SetPrincipalProjection(projection)
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/edge/internal/authprojection/cache.go`: implement validated atomic cache and states.
|
||||
- [ ] `apps/edge/internal/authprojection/cache_test.go`: cover fresh/stale/expired/revoked/out-of-order/bounds/concurrency.
|
||||
- [ ] `apps/edge/internal/openai/server.go`: read the cache through a narrow interface without changing constructor.
|
||||
- [ ] `apps/edge/internal/input/manager.go`: create and expose the dormant shared cache.
|
||||
- [ ] `apps/edge/internal/input/manager_test.go`: prove shared instance and unmanaged default.
|
||||
|
||||
**Test Strategy:** Write `TestCacheAppliesOnlyHigherFreshGeneration`, `TestCacheExpiryKeepsManagedFailClosed`, `TestCacheRejectsOversizedSnapshot`, and `TestCacheConcurrentLookupAndApply`; run with `-race`.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/edge/internal/authprojection ./apps/edge/internal/input` passes.
|
||||
|
||||
### [API-3] Add dormant managed-mode surface authentication
|
||||
|
||||
**Problem:** `apps/edge/internal/openai/routes.go:30-43` reads only static config, so no shared cache behavior can be tested; directly wiring the cache to current connector would be unsafe.
|
||||
|
||||
**Solution:** When the cache is explicitly in managed state, authenticate constant-time token digests and surface-specific headers from it; unmanaged state preserves existing config behavior. Expired/revoked managed state returns 401 before handler. Do not add any production cache writer.
|
||||
|
||||
Before (`apps/edge/internal/openai/routes.go:30-33`):
|
||||
|
||||
```go
|
||||
func (s *Server) withAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := principalFromRequest(r, s.cfg)
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
principal, state := s.authenticatePrincipal(r)
|
||||
if state != principalAuthenticated {
|
||||
s.writeAuthenticationFailure(w, r)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/edge/internal/openai/principal.go`: share header normalization and projected digest lookup.
|
||||
- [ ] `apps/edge/internal/openai/routes.go`: enforce managed error precedence before handlers.
|
||||
- [ ] `apps/edge/internal/openai/identity_metering_test.go`: cover managed principal metadata and static compatibility.
|
||||
- [ ] `apps/edge/internal/openai/anthropic_surface_test.go`: cover same/mismatch/unregistered/revoked/expired dual headers and zero dispatch.
|
||||
- [ ] `agent-contract/inner/control-plane-edge-wire.md`: mark projection types reserved until authenticated transport, with no current listener.
|
||||
- [ ] `agent-contract/outer/openai-compatible-api.md`: document dormant managed-mode gate and unchanged legacy production mode.
|
||||
- [ ] `agent-contract/outer/anthropic-compatible-api.md`: document the same gated header semantics.
|
||||
|
||||
**Test Strategy:** Write `TestManagedOpenAIAuthRejectsBeforeHandler`, `TestManagedAnthropicDualHeadersSameToken`, `TestManagedAnthropicDualHeadersMismatch`, and `TestManagedProjectionExpiryRejectsBeforeDispatch` using direct cache fixture installation inside tests.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'Managed|Principal|Anthropic.*Auth'` passes.
|
||||
|
||||
## Modified Files Summary
|
||||
|
||||
| File | Items |
|
||||
|------|-------|
|
||||
| `proto/iop/control.proto` | API-1 |
|
||||
| `proto/gen/iop/control.pb.go` | API-1 |
|
||||
| `apps/client/lib/gen/proto/iop/control.pb.dart` | API-1 |
|
||||
| `apps/client/lib/gen/proto/iop/control.pbenum.dart` | API-1 |
|
||||
| `apps/client/lib/gen/proto/iop/control.pbjson.dart` | API-1 |
|
||||
| `apps/client/lib/gen/proto/iop/control.pbserver.dart` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/schema.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/principal.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/slot.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/route.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/projection.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/projection_test.go` | API-1 |
|
||||
| `apps/edge/internal/authprojection/cache.go` | API-2 |
|
||||
| `apps/edge/internal/authprojection/cache_test.go` | API-2 |
|
||||
| `apps/edge/internal/openai/server.go` | API-2 |
|
||||
| `apps/edge/internal/input/manager.go` | API-2 |
|
||||
| `apps/edge/internal/input/manager_test.go` | API-2 |
|
||||
| `apps/edge/internal/openai/principal.go` | API-3 |
|
||||
| `apps/edge/internal/openai/routes.go` | API-3 |
|
||||
| `apps/edge/internal/openai/identity_metering_test.go` | API-3 |
|
||||
| `apps/edge/internal/openai/anthropic_surface_test.go` | API-3 |
|
||||
| `agent-contract/inner/control-plane-edge-wire.md` | API-3 |
|
||||
| `agent-contract/outer/openai-compatible-api.md` | API-3 |
|
||||
| `agent-contract/outer/anthropic-compatible-api.md` | API-3 |
|
||||
| `agent-task/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/CODE_REVIEW-cloud-G09.md` | API-1, API-2, API-3 |
|
||||
|
||||
## Final Verification
|
||||
|
||||
Run from `/config/workspace/iop-s0`; fresh results are required.
|
||||
|
||||
1. `test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/01_principal_store/complete.log' -print | wc -l)" -eq 1 && test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log' -print | wc -l)" -eq 1` — both archived predecessors exist exactly once.
|
||||
2. `command -v go && go version && command -v protoc && protoc --version && command -v protoc-gen-go && command -v protoc-gen-dart && command -v flutter && flutter --version` — toolchains resolve.
|
||||
3. `mkdir -p .cache/go-build .cache/go-cache` — executable Go paths exist.
|
||||
4. `make proto && make proto-dart` — generated outputs are current.
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/internal/credentialstore ./apps/edge/internal/authprojection ./apps/edge/internal/input ./apps/edge/internal/openai` — no vet findings.
|
||||
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialstore ./apps/edge/internal/authprojection ./apps/edge/internal/input ./apps/edge/internal/openai` — foundation lifecycle and race tests pass.
|
||||
7. `make client-test` — generated Dart compatibility passes.
|
||||
8. `if rg --sort path -n 'PrincipalProjection' apps/control-plane/internal/wire/edge_server.go apps/edge/internal/controlplane/connector.go apps/edge/internal/bootstrap/runtime.go; then exit 1; else exit 0; fi` — current unauthenticated runtime has no projection activation.
|
||||
9. `git diff --check` — no whitespace errors.
|
||||
|
||||
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/03+01,02_projection_auth plan=0 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `Implementation Checklist`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## Overview
|
||||
|
||||
date=2026-08-01
|
||||
task=m-principal-provider-credential-slot-routing/03+01,02_projection_auth, plan=0, tag=API
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
|
||||
- Task ids:
|
||||
- `auth-projection`: generation/expiry principal projection과 bounded Edge cache
|
||||
- `surface-auth`: OpenAI/Anthropic 동일 principal 인증
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## For the Review Agent
|
||||
|
||||
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
|
||||
|
||||
Compare implementation of each item against source files and verify that output in `Verification Results` matches code.
|
||||
Review completion means the following steps are finished:
|
||||
|
||||
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
|
||||
2. Archive `CODE_REVIEW-cloud-G10.md` → `code_review_cloud_G10_0.log` and `PLAN-cloud-G10.md` → `plan_cloud_G10_0.log`.
|
||||
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
|
||||
4. If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata. Roadmap state check and `update-roadmap` calls are runtime responsibilities.
|
||||
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Item Completion
|
||||
|
||||
| Item | Status |
|
||||
|------|---------|
|
||||
| API-1 Produce and synchronize durable projections | [ ] |
|
||||
| API-2 Apply a fenced bounded Edge cache | [ ] |
|
||||
| API-3 Authenticate both HTTP surfaces from the same snapshot | [ ] |
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Add durable monotonic principal projection snapshots and the CP-to-Edge protobuf request/acknowledgment contract.
|
||||
- [ ] Apply only higher, unexpired generations to a bounded atomic Edge cache and retain managed fail-closed state after expiry or revocation.
|
||||
- [ ] Authenticate OpenAI bearer and Anthropic bearer/x-api-key through the shared cache, rejecting mismatch or invalid tokens before model/dispatch handlers.
|
||||
- [ ] Update generated Go/Dart code and inner/outer contracts without exposing raw secrets.
|
||||
- [ ] Run fresh race, wire, client-generation, full-cycle ingress, vet, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
## Review-Only Checklist
|
||||
|
||||
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
|
||||
> Implementing agents must not modify or check this section.
|
||||
|
||||
- [ ] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
|
||||
- [ ] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
|
||||
- [ ] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G10_0.log`.
|
||||
- [ ] Archive active `PLAN-*-G??.md` to `plan_cloud_G10_0.log`.
|
||||
- [ ] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
|
||||
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
|
||||
- [ ] If PASS, move active task directory `agent-task/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/` to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/` and update this checklist at the final archive path.
|
||||
- [ ] If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata for runtime, without modifying roadmap or directly calling `update-roadmap`.
|
||||
- [ ] If PASS for split work, remove empty active parent `agent-task/m-principal-provider-credential-slot-routing/` or verify it was kept due to remaining siblings/files.
|
||||
- [ ] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
_Record any deviations from the plan and the rationale here._
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
_Record key design decisions here._
|
||||
|
||||
## Reviewer Checkpoints
|
||||
|
||||
- Verify both predecessor complete logs and additive protobuf compatibility.
|
||||
- Inspect transaction boundaries so each auth-affecting mutation and durable generation commit together.
|
||||
- Confirm cache validation, size bounds, deep copy, atomic swap, higher-generation fence, fake-clock expiry, and `-race` evidence.
|
||||
- Confirm managed expiry/revocation never falls back to static config and 401 occurs before model/dispatch handler invocation.
|
||||
- Search generated/contracts/logging paths for raw token/provider secret exposure.
|
||||
|
||||
## Verification Results
|
||||
|
||||
Paste actual stdout/stderr beneath every command; do not summarize or reconstruct it.
|
||||
|
||||
### API-1
|
||||
|
||||
`make proto && make proto-dart && GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/credentialstore ./apps/control-plane/internal/wire`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-2
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/edge/internal/controlplane ./apps/edge/internal/openai ./apps/edge/internal/input ./apps/edge/internal/bootstrap`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-3
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'Principal|Anthropic.*Auth|Projection'`
|
||||
|
||||
_Pending._
|
||||
|
||||
### Final Verification
|
||||
|
||||
1. `test -f agent-task/m-principal-provider-credential-slot-routing/01_principal_store/complete.log && test -f agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log`
|
||||
2. `command -v go && go version && command -v protoc && protoc --version && command -v protoc-gen-go && command -v protoc-gen-dart && command -v flutter && flutter --version`
|
||||
3. `mkdir -p .cache/go-build .cache/go-cache`
|
||||
4. `make proto && make proto-dart`
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/... ./apps/edge/internal/controlplane ./apps/edge/internal/openai ./apps/edge/internal/input ./apps/edge/internal/bootstrap`
|
||||
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialstore ./apps/control-plane/internal/wire ./apps/edge/internal/controlplane ./apps/edge/internal/openai ./apps/edge/internal/input ./apps/edge/internal/bootstrap`
|
||||
7. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache make test-control-plane-edge-wire`
|
||||
8. `make client-test`
|
||||
9. `cd apps/client && flutter analyze`
|
||||
10. `git diff --check`
|
||||
|
||||
_Pending actual output for each command._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Section Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
|
||||
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
|
||||
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
|
||||
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section |
|
||||
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholder text with actual content |
|
||||
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
|
||||
| Verification Results (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `Deviations from Plan` entry |
|
||||
| Code Review Result | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,304 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/03+01,02_projection_auth plan=0 tag=API -->
|
||||
|
||||
# Principal Projection and Surface Authentication
|
||||
|
||||
## For the Implementing Agent
|
||||
|
||||
Filling the implementation-owned sections of `CODE_REVIEW-cloud-G10.md` is mandatory. Execute every verification command, paste actual notes/output, leave active files in place, and report ready for review; only code-review may finalize, rename logs, create `complete.log`, or archive. If blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence; do not ask the user, invoke user-input tools, create stop files, or classify next state.
|
||||
|
||||
## Background
|
||||
|
||||
Edge authenticates static config hashes and already normalizes Anthropic bearer/`x-api-key`, but it has no Control Plane generation, expiry, revocation, or bounded cache. This packet establishes the CP→Edge projection contract and makes both compatible HTTP surfaces use the same fresh principal snapshot before model lookup or provider dispatch.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
|
||||
- Task ids:
|
||||
- `auth-projection`: generation/expiry principal projection과 bounded Edge cache
|
||||
- `surface-auth`: OpenAI/Anthropic 동일 principal 인증
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## Analysis
|
||||
|
||||
### Files Read
|
||||
|
||||
- `proto/iop/control.proto`
|
||||
- `proto/gen/iop/control.pb.go`
|
||||
- `apps/client/lib/gen/proto/iop/control.pb.dart`
|
||||
- `apps/client/lib/gen/proto/iop/control.pbenum.dart`
|
||||
- `apps/client/lib/gen/proto/iop/control.pbjson.dart`
|
||||
- `apps/client/lib/gen/proto/iop/control.pbserver.dart`
|
||||
- `apps/control-plane/cmd/control-plane/server.go`
|
||||
- `apps/control-plane/internal/wire/edge.go`
|
||||
- `apps/control-plane/internal/wire/edge_server.go`
|
||||
- `apps/control-plane/internal/wire/edge_test.go`
|
||||
- `apps/control-plane/internal/wire/edge_server_test.go`
|
||||
- `apps/edge/internal/controlplane/connector.go`
|
||||
- `apps/edge/internal/controlplane/connector_test.go`
|
||||
- `apps/edge/internal/bootstrap/runtime.go`
|
||||
- `apps/edge/internal/bootstrap/runtime_connector_test.go`
|
||||
- `apps/edge/internal/input/manager.go`
|
||||
- `apps/edge/internal/input/manager_test.go`
|
||||
- `apps/edge/internal/openai/server.go`
|
||||
- `apps/edge/internal/openai/principal.go`
|
||||
- `apps/edge/internal/openai/routes.go`
|
||||
- `apps/edge/internal/openai/identity_metering_test.go`
|
||||
- `apps/edge/internal/openai/anthropic_surface_test.go`
|
||||
- `apps/client/test/iop_wire/generated_proto_import_test.dart`
|
||||
- `agent-contract/inner/control-plane-edge-wire.md`
|
||||
- `agent-contract/outer/openai-compatible-api.md`
|
||||
- `agent-contract/outer/anthropic-compatible-api.md`
|
||||
|
||||
### SDD Criteria
|
||||
|
||||
- SDD: `agent-roadmap/sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md` (`승인`, 잠금 해제).
|
||||
- Targets: S02 / `auth-projection`, S03 / `surface-auth`.
|
||||
- Evidence Map row: S01-S03의 projection generation/expiry 및 dual-header ingress tests 중 S02-S03.
|
||||
- Checklist는 durable monotonic generation, atomic higher-generation apply, fresh/stale/expired/revoked/out-of-order fixtures, bounded cache, same-token dual header success, mismatched/unregistered/revoked 401-before-handler를 직접 연결한다.
|
||||
|
||||
### Verification Context
|
||||
|
||||
- handoff 없음. wire/proto generators, CP/Edge tests, outer/inner contracts, domain rules와 local control-plane/edge/client smoke profiles에서 검증을 구성했다.
|
||||
- Tools: Go 1.26.2, `protoc` 29.3, `protoc-gen-go` v1.36.11, `protoc-gen-dart` present, Flutter 3.41.5/Dart 3.11.3. `make -n proto proto-dart`가 canonical outputs를 확인했다.
|
||||
- Local preflight compile and focused legacy principal/Anthropic tests pass only with `GOTMPDIR`/`GOCACHE` under ignored workspace `.cache`; `/tmp` is noexec and default cache is inaccessible.
|
||||
- Preconditions: `01_principal_store` and `02+01_credential_catalog` complete logs. 현재 둘 다 missing이며 ambiguity 없음.
|
||||
- Full cycle is local CP→Edge TCP plus Edge HTTP; no real provider credential or external host is required because rejection must occur before fake dispatch. Confidence: high.
|
||||
|
||||
### Test Coverage Gaps
|
||||
|
||||
- Existing Edge auth tests cover static hashes and dual-header mismatch, but not projection generation/expiry/revocation or fail-closed managed mode.
|
||||
- CP-Edge tests cover hello/status/command only; no projection parser/request/apply acknowledgment exists.
|
||||
- Runtime/input tests do not prove a shared cache is wired from Connector into OpenAI server.
|
||||
- Generated Dart import test does not reference projection messages yet.
|
||||
|
||||
### Symbol References
|
||||
|
||||
- No rename/remove.
|
||||
- `wire.NewEdgeServer` call sites: `apps/control-plane/cmd/control-plane/server.go:51`, `apps/control-plane/internal/wire/edge_server_test.go:22`; preserve constructor compatibility through an option.
|
||||
- `controlplane.NewConnector` call site: `apps/edge/internal/bootstrap/runtime.go:83`; existing tests call it directly and remain source-compatible through an option.
|
||||
- `openai.NewServer` has many tests; do not change its signature. Add setter/sink methods.
|
||||
|
||||
### Split Judgment
|
||||
|
||||
- Projection and ingress authentication remain one packet because “higher fresh generation atomically replaces token lookup, then middleware authenticates from that exact snapshot” is one correctness invariant. Splitting would allow an invalid state where wire reports success while HTTP still reads static config.
|
||||
- Predecessor 01 `01_principal_store`: missing active `complete.log`, no archived candidate.
|
||||
- Predecessor 02 `02+01_credential_catalog`: missing active `complete.log`, no archived candidate.
|
||||
- Consumer 04 depends only on this packet's complete projection snapshot contract.
|
||||
|
||||
### Scope Rationale
|
||||
|
||||
- Principal-specific model listing/dispatch is packet 04. Management mutations are packet 05.
|
||||
- Provider ciphertext delivery/decrypt/header injection is Epic 3 and excluded; projection contains IDs, hashes, route metadata, never raw provider or IOP secrets.
|
||||
- Legacy config auth remains available only when Edge has never entered CP-managed projection mode; once managed, expiry is fail-closed rather than fallback.
|
||||
|
||||
### Final Routing
|
||||
|
||||
- `status=routed`, `evaluation_mode=first-pass`, finalizer=`finalize-task-policy.sh`, pair mode.
|
||||
- Build closures all true; scores `2+2+2+2+2=G10`; base/route=`grade-boundary`, cloud, canonical=`PLAN-cloud-G10.md`.
|
||||
- Review closures all true; scores `2+2+2+2+2=G10`; `official-review`, cloud, `gpt-5.6-sol` xhigh, canonical=`CODE_REVIEW-cloud-G10.md`.
|
||||
- `large_indivisible_context=false`; risks=`temporal_state`, `concurrent_consistency`, `boundary_contract`, `variant_product` (4), risk boundary matched but grade basis retained; rework=0; integrity failure=false; no capability gap.
|
||||
|
||||
## Dependencies and Execution Order
|
||||
|
||||
1. `01_principal_store` must provide `agent-task/m-principal-provider-credential-slot-routing/01_principal_store/complete.log`.
|
||||
2. `02+01_credential_catalog` must provide `agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log`.
|
||||
3. Add durable projection generation/snapshot, then wire apply, then switch ingress authentication to the shared cache.
|
||||
4. No dependency beyond indices `01,02` may be introduced.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Add durable monotonic principal projection snapshots and the CP-to-Edge protobuf request/acknowledgment contract.
|
||||
- [ ] Apply only higher, unexpired generations to a bounded atomic Edge cache and retain managed fail-closed state after expiry or revocation.
|
||||
- [ ] Authenticate OpenAI bearer and Anthropic bearer/x-api-key through the shared cache, rejecting mismatch or invalid tokens before model/dispatch handlers.
|
||||
- [ ] Update generated Go/Dart code and inner/outer contracts without exposing raw secrets.
|
||||
- [ ] Run fresh race, wire, client-generation, full-cycle ingress, vet, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
### [API-1] Produce and synchronize durable projections
|
||||
|
||||
**Problem:** `apps/control-plane/internal/wire/edge.go:12-46` knows only hello/status/events/commands, and store mutations have no durable projection generation.
|
||||
|
||||
**Solution:** Add a singleton projection generation incremented in the same DB transaction as principal/token/slot/route mutations. Build immutable snapshots of active token digests and route metadata with `generation`, `issued_at`, and `expires_at`. Add CP→Edge `PrincipalProjectionApplyRequest/Response`; push after accepted hello and on newer store generation, with retry safe through generation fencing.
|
||||
|
||||
Before (`apps/control-plane/internal/wire/edge.go:12-18`):
|
||||
|
||||
```go
|
||||
return proto_socket.ParserMap{
|
||||
proto_socket.TypeNameOf(&iop.EdgeHelloRequest{}): func(b []byte) (proto.Message, error) {
|
||||
req := &iop.EdgeHelloRequest{}
|
||||
return req, proto.Unmarshal(b, req)
|
||||
},
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
proto_socket.TypeNameOf(&iop.PrincipalProjectionApplyRequest{}): func(b []byte) (proto.Message, error) {
|
||||
req := &iop.PrincipalProjectionApplyRequest{}
|
||||
return req, proto.Unmarshal(b, req)
|
||||
},
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `proto/iop/control.proto`: add projection token/route/snapshot/apply/ack messages with additive field numbers.
|
||||
- [ ] `proto/gen/iop/control.pb.go`: regenerate Go messages.
|
||||
- [ ] `apps/client/lib/gen/proto/iop/control.pb.dart`: regenerate Dart messages.
|
||||
- [ ] `apps/client/lib/gen/proto/iop/control.pbenum.dart`: regenerate Dart enum output.
|
||||
- [ ] `apps/client/lib/gen/proto/iop/control.pbjson.dart`: regenerate Dart JSON metadata.
|
||||
- [ ] `apps/client/lib/gen/proto/iop/control.pbserver.dart`: regenerate Dart server helpers.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/schema.go`: add durable projection generation state.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/principal.go`: bump generation transactionally on token lifecycle mutations.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/slot.go`: bump generation transactionally on slot lifecycle mutations.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/route.go`: bump generation transactionally on route lifecycle mutations.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/projection.go`: build bounded immutable active snapshots.
|
||||
- [ ] `apps/control-plane/internal/credentialstore/projection_test.go`: test generation durability and filtered snapshots.
|
||||
- [ ] `apps/control-plane/internal/wire/edge.go`: register projection parsers.
|
||||
- [ ] `apps/control-plane/internal/wire/edge_server.go`: accept projection source option and push fresh snapshot to live Edge connections.
|
||||
- [ ] `apps/control-plane/internal/wire/projection_sync.go`: implement generation-aware publish/retry without blocking hello.
|
||||
- [ ] `apps/control-plane/internal/wire/edge_test.go`: assert parser round trips.
|
||||
- [ ] `apps/control-plane/internal/wire/edge_server_test.go`: assert connect/push/ack and reconnect behavior.
|
||||
- [ ] `apps/control-plane/cmd/control-plane/server.go`: supply store projection source to EdgeServer.
|
||||
|
||||
**Test Strategy:** Write `TestProjectionGenerationCommitsWithMutation`, `TestProjectionSnapshotOmitsDisabledRevokedExpiredRecords`, `TestEdgeServerPushesProjectionAfterHello`, and `TestProjectionRetryKeepsGeneration`. Use temp SQLite and loopback TCP; timestamps use injected clocks.
|
||||
|
||||
**Verification:** `make proto && make proto-dart && GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/credentialstore ./apps/control-plane/internal/wire` passes.
|
||||
|
||||
### [API-2] Apply a fenced bounded Edge cache
|
||||
|
||||
**Problem:** `apps/edge/internal/controlplane/connector.go:229-238` registers only status/command listeners, and OpenAI server state has no CP projection cache.
|
||||
|
||||
**Solution:** Define a `PrincipalProjectionSink` connector option. Register the apply listener before hello, validate sizes/digests/times, deep-copy and atomically swap only when generation is higher and snapshot is fresh. Once the first managed generation is accepted, expiry/revocation remains managed fail-closed and never falls back to static config.
|
||||
|
||||
Before (`apps/edge/internal/controlplane/connector.go:229-238`):
|
||||
|
||||
```go
|
||||
toki.AddRequestListenerTyped(&cl.Communicator, func(req *iop.EdgeStatusRequest) (*iop.EdgeStatusResponse, error) {
|
||||
return c.buildStatusResponse(req), nil
|
||||
})
|
||||
|
||||
toki.AddRequestListenerTyped(&cl.Communicator, func(req *iop.EdgeCommandRequest) (*iop.EdgeCommandResponse, error) {
|
||||
return c.handleEdgeCommand(ctx, req)
|
||||
})
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
toki.AddRequestListenerTyped(&cl.Communicator, func(req *iop.PrincipalProjectionApplyRequest) (*iop.PrincipalProjectionApplyResponse, error) {
|
||||
return c.applyPrincipalProjection(req), nil
|
||||
})
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/edge/internal/controlplane/connector.go`: add sink option/listener and stable apply responses.
|
||||
- [ ] `apps/edge/internal/controlplane/connector_test.go`: test listener ordering, invalid/out-of-order application, reconnect.
|
||||
- [ ] `apps/edge/internal/openai/principal_projection.go`: implement limits, injected clock, atomic immutable snapshot, lookup, expiry state.
|
||||
- [ ] `apps/edge/internal/openai/principal_projection_test.go`: cover fresh/stale/expired/revoked/out-of-order/concurrent reads.
|
||||
- [ ] `apps/edge/internal/openai/server.go`: own the projection cache and expose sink/setter without changing `NewServer` signature.
|
||||
- [ ] `apps/edge/internal/input/manager.go`: expose the OpenAI projection sink.
|
||||
- [ ] `apps/edge/internal/input/manager_test.go`: assert the shared cache exists.
|
||||
- [ ] `apps/edge/internal/bootstrap/runtime.go`: wire connector sink to `Input.OpenAI`.
|
||||
- [ ] `apps/edge/internal/bootstrap/runtime_connector_test.go`: assert projection sink wiring.
|
||||
|
||||
**Test Strategy:** Write `TestProjectionAppliesOnlyHigherFreshGeneration`, `TestProjectionExpiryKeepsManagedFailClosed`, `TestProjectionBoundsRejectOversizedSnapshot`, and `TestProjectionConcurrentLookupAndApply`. Run `-race` with a deterministic fake clock.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/edge/internal/controlplane ./apps/edge/internal/openai ./apps/edge/internal/input ./apps/edge/internal/bootstrap` passes.
|
||||
|
||||
### [API-3] Authenticate both HTTP surfaces from the same snapshot
|
||||
|
||||
**Problem:** `apps/edge/internal/openai/routes.go:30-43` always calls static `principalFromRequest(r, s.cfg)`. Static dual-header logic cannot observe Control Plane revocation or expiry.
|
||||
|
||||
**Solution:** Make middleware resolve from the cache when managed; use constant-time digest lookup, require equal bearer and `x-api-key` values when both exist, and return surface-correct 401 before invoking the handler. Keep current static behavior only in never-managed compatibility mode.
|
||||
|
||||
Before (`apps/edge/internal/openai/routes.go:30-33`):
|
||||
|
||||
```go
|
||||
func (s *Server) withAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := principalFromRequest(r, s.cfg)
|
||||
if !ok {
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
principal, authState := s.authenticatePrincipal(r)
|
||||
if authState != principalAuthenticated {
|
||||
s.writeAuthenticationFailure(w, r)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/edge/internal/openai/principal.go`: share header normalization and digest matching between static and projected sources.
|
||||
- [ ] `apps/edge/internal/openai/routes.go`: fail before handler invocation with OpenAI/Anthropic 401 envelopes.
|
||||
- [ ] `apps/edge/internal/openai/identity_metering_test.go`: retain static compatibility and projected principal metadata assertions.
|
||||
- [ ] `apps/edge/internal/openai/anthropic_surface_test.go`: add bearer/x-api-key same/mismatch/unregistered/revoked/expired cases and zero-dispatch assertions.
|
||||
- [ ] `agent-contract/inner/control-plane-edge-wire.md`: document projection fields, generation, expiry, size and ack rules.
|
||||
- [ ] `agent-contract/outer/openai-compatible-api.md`: document CP-managed bearer behavior.
|
||||
- [ ] `agent-contract/outer/anthropic-compatible-api.md`: document bearer/x-api-key equivalence and 401 precedence.
|
||||
|
||||
**Test Strategy:** Add `TestProjectedOpenAIAuthRejectsBeforeHandler`, `TestAnthropicProjectedDualHeadersSameToken`, `TestAnthropicProjectedDualHeadersMismatch`, and `TestRevokedOrExpiredProjectionRejectsBeforeDispatch`. Use a counting handler/fake service to assert zero calls.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'Principal|Anthropic.*Auth|Projection'` passes.
|
||||
|
||||
## Modified Files Summary
|
||||
|
||||
| File | Items |
|
||||
|------|-------|
|
||||
| `proto/iop/control.proto` | API-1 |
|
||||
| `proto/gen/iop/control.pb.go` | API-1 |
|
||||
| `apps/client/lib/gen/proto/iop/control.pb.dart` | API-1 |
|
||||
| `apps/client/lib/gen/proto/iop/control.pbenum.dart` | API-1 |
|
||||
| `apps/client/lib/gen/proto/iop/control.pbjson.dart` | API-1 |
|
||||
| `apps/client/lib/gen/proto/iop/control.pbserver.dart` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/schema.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/principal.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/slot.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/route.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/projection.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialstore/projection_test.go` | API-1 |
|
||||
| `apps/control-plane/internal/wire/edge.go` | API-1 |
|
||||
| `apps/control-plane/internal/wire/edge_server.go` | API-1 |
|
||||
| `apps/control-plane/internal/wire/projection_sync.go` | API-1 |
|
||||
| `apps/control-plane/internal/wire/edge_test.go` | API-1 |
|
||||
| `apps/control-plane/internal/wire/edge_server_test.go` | API-1 |
|
||||
| `apps/control-plane/cmd/control-plane/server.go` | API-1 |
|
||||
| `apps/edge/internal/controlplane/connector.go` | API-2 |
|
||||
| `apps/edge/internal/controlplane/connector_test.go` | API-2 |
|
||||
| `apps/edge/internal/openai/principal_projection.go` | API-2 |
|
||||
| `apps/edge/internal/openai/principal_projection_test.go` | API-2 |
|
||||
| `apps/edge/internal/openai/server.go` | API-2 |
|
||||
| `apps/edge/internal/input/manager.go` | API-2 |
|
||||
| `apps/edge/internal/input/manager_test.go` | API-2 |
|
||||
| `apps/edge/internal/bootstrap/runtime.go` | API-2 |
|
||||
| `apps/edge/internal/bootstrap/runtime_connector_test.go` | API-2 |
|
||||
| `apps/edge/internal/openai/principal.go` | API-3 |
|
||||
| `apps/edge/internal/openai/routes.go` | API-3 |
|
||||
| `apps/edge/internal/openai/identity_metering_test.go` | API-3 |
|
||||
| `apps/edge/internal/openai/anthropic_surface_test.go` | API-3 |
|
||||
| `agent-contract/inner/control-plane-edge-wire.md` | API-3 |
|
||||
| `agent-contract/outer/openai-compatible-api.md` | API-3 |
|
||||
| `agent-contract/outer/anthropic-compatible-api.md` | API-3 |
|
||||
| `agent-task/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/CODE_REVIEW-cloud-G10.md` | API-1, API-2, API-3 |
|
||||
|
||||
## Final Verification
|
||||
|
||||
Run from `/config/workspace/iop-s0`; fresh Go tests are required.
|
||||
|
||||
1. `test -f agent-task/m-principal-provider-credential-slot-routing/01_principal_store/complete.log && test -f agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log` — both predecessors complete.
|
||||
2. `command -v go && go version && command -v protoc && protoc --version && command -v protoc-gen-go && command -v protoc-gen-dart && command -v flutter && flutter --version` — required generators/toolchains resolve.
|
||||
3. `mkdir -p .cache/go-build .cache/go-cache` — executable Go temp/cache paths exist.
|
||||
4. `make proto && make proto-dart` — generated Go/Dart artifacts are current.
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/... ./apps/edge/internal/controlplane ./apps/edge/internal/openai ./apps/edge/internal/input ./apps/edge/internal/bootstrap` — no vet findings.
|
||||
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialstore ./apps/control-plane/internal/wire ./apps/edge/internal/controlplane ./apps/edge/internal/openai ./apps/edge/internal/input ./apps/edge/internal/bootstrap` — projection/auth lifecycle and races pass.
|
||||
7. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache make test-control-plane-edge-wire` — CP↔Edge wire compatibility passes.
|
||||
8. `make client-test` — generated Dart messages/imports remain valid.
|
||||
9. `cd apps/client && flutter analyze` — client analysis has no findings.
|
||||
10. `git diff --check` — no whitespace errors.
|
||||
|
||||
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/04+03_principal_routes plan=1 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `Implementation Checklist`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## Overview
|
||||
|
||||
date=2026-08-01
|
||||
task=m-principal-provider-credential-slot-routing/04+03_principal_routes, plan=1, tag=API
|
||||
|
||||
## Archive Evidence Snapshot
|
||||
|
||||
- Prior pair: `agent-task/m-principal-provider-credential-slot-routing/04+03_principal_routes/plan_local_G08_0.log`, `agent-task/m-principal-provider-credential-slot-routing/04+03_principal_routes/code_review_cloud_G09_0.log`.
|
||||
- Verdict/evidence: implementation had not started; no verdict or verification output.
|
||||
- Replan reason: prior plan claimed principal discovery/selection rollout while its authorization projection would traverse an unauthenticated transport.
|
||||
- Roadmap carryover: PASS is foundation-only and must not check `model-discovery` or `explicit-selection`.
|
||||
|
||||
## For the Review Agent
|
||||
|
||||
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
|
||||
|
||||
Compare implementation of each item against source files and verify that output in `Verification Results` matches code.
|
||||
Review completion means the following steps are finished:
|
||||
|
||||
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
|
||||
2. Archive `CODE_REVIEW-cloud-G08.md` → `code_review_cloud_G08_1.log` and `PLAN-local-G07.md` → `plan_local_G07_1.log`.
|
||||
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/04+03_principal_routes/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
|
||||
4. If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata. Roadmap state check and `update-roadmap` calls are runtime responsibilities.
|
||||
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Item Completion
|
||||
|
||||
| Item | Status |
|
||||
|------|---------|
|
||||
| API-1 Add principal-scoped dormant discovery | [ ] |
|
||||
| API-2 Fix managed requests to one projected route | [ ] |
|
||||
| API-3 Keep gated contracts honest | [ ] |
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Return only a verified-cache principal's active projected route IDs from OpenAI and Anthropic model-list variants, covering the S06 isolation fixtures without production activation.
|
||||
- [ ] Resolve request model to one projected route and compose fixed provider/profile/upstream-model predicates plus stable slot metadata across initial and recovery dispatch, covering S07 no-fallback fixtures.
|
||||
- [ ] Preserve unmanaged global/legacy behavior and prove managed unknown/inactive/cross-principal routes never fall back or dispatch.
|
||||
- [ ] Keep contracts explicitly gated and run fresh focused/full Edge, dormant-activation, vet, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
## Review-Only Checklist
|
||||
|
||||
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
|
||||
> Implementing agents must not modify or check this section.
|
||||
|
||||
- [ ] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
|
||||
- [ ] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
|
||||
- [ ] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G08_1.log`.
|
||||
- [ ] Archive active `PLAN-*-G??.md` to `plan_local_G07_1.log`.
|
||||
- [ ] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
|
||||
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
|
||||
- [ ] If PASS, move active task directory `agent-task/m-principal-provider-credential-slot-routing/04+03_principal_routes/` to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/04+03_principal_routes/` and update this checklist at the final archive path.
|
||||
- [ ] If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata for runtime, without modifying roadmap or directly calling `update-roadmap`.
|
||||
- [ ] If PASS for split work, remove empty active parent `agent-task/m-principal-provider-credential-slot-routing/` or verify it was kept due to remaining siblings/files.
|
||||
- [ ] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
_Record any deviations from the plan and the rationale here._
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
_Record key design decisions here._
|
||||
|
||||
## Reviewer Checkpoints
|
||||
|
||||
- Confirm no Roadmap Targets section exists and PASS cannot check S06/S07.
|
||||
- Verify managed discovery and dispatch read one immutable principal route view.
|
||||
- Confirm route predicates compose with stream/profile predicates and survive recovery re-resolution.
|
||||
- Prove managed failures never enter global/legacy fallback while unmanaged behavior remains unchanged.
|
||||
- Confirm no Connector/Runtime activation or secret-delivery logic entered this packet.
|
||||
|
||||
## Verification Results
|
||||
|
||||
Paste actual stdout/stderr beneath every command; do not summarize or reconstruct it.
|
||||
|
||||
### API-1
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'Managed.*Models|PrincipalRoute|UnmanagedModels'`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-2
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'ManagedRoute|ExplicitSlot|NoFallback'`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-3
|
||||
|
||||
`rg --sort path -n 'verified|managed|route_id|slot_id|fallback' agent-contract/outer/openai-compatible-api.md agent-contract/outer/anthropic-compatible-api.md`
|
||||
|
||||
_Pending._
|
||||
|
||||
### Final Verification
|
||||
|
||||
1. `test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/complete.log' -print | wc -l)" -eq 1`
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache`
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/edge/internal/authprojection ./apps/edge/internal/openai ./apps/edge/internal/service`
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'Managed|PrincipalRoute|ExplicitSlot|NoFallback|Anthropic'`
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/authprojection ./apps/edge/internal/openai ./apps/edge/internal/service`
|
||||
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/...`
|
||||
7. `if rg --sort path -n 'PrincipalProjection' apps/control-plane/internal/wire/edge_server.go apps/edge/internal/controlplane/connector.go apps/edge/internal/bootstrap/runtime.go; then exit 1; else exit 0; fi`
|
||||
8. `git diff --check`
|
||||
|
||||
_Pending actual output for each command._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Section Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
|
||||
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
|
||||
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
|
||||
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section |
|
||||
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholder text with actual content |
|
||||
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
|
||||
| Verification Results (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `Deviations from Plan` entry |
|
||||
| Code Review Result | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/04+03_principal_routes plan=1 tag=API -->
|
||||
|
||||
# Principal Route Resolution Foundation
|
||||
|
||||
## For the Implementing Agent
|
||||
|
||||
Filling implementation-owned sections of `CODE_REVIEW-cloud-G08.md` is mandatory. Run every verification command, paste actual output, leave active files in place, and report ready for review; only code-review may finalize, rename logs, write `complete.log`, or archive. If blocked, record exact blocker evidence and resume conditions only in implementation-owned fields; do not ask the user, call user-input tools, create stop files, or classify next state.
|
||||
|
||||
## Background
|
||||
|
||||
Managed model discovery and explicit route selection should consume one immutable principal projection view. Because packet 03 intentionally leaves projection transport dormant until authenticated CP–Edge transport exists, this packet builds and tests the managed route behavior without enabling it in production or claiming S06/S07 completion.
|
||||
|
||||
## Archive Evidence Snapshot
|
||||
|
||||
- Prior pair: `agent-task/m-principal-provider-credential-slot-routing/04+03_principal_routes/plan_local_G08_0.log`, `agent-task/m-principal-provider-credential-slot-routing/04+03_principal_routes/code_review_cloud_G09_0.log`.
|
||||
- Verdict/evidence: implementation had not started; no verdict or verification output.
|
||||
- Replan reason: prior plan claimed principal discovery/selection rollout while its authorization projection would traverse an unauthenticated transport.
|
||||
- Roadmap carryover: PASS is foundation-only and must not check `model-discovery` or `explicit-selection`.
|
||||
|
||||
## Analysis
|
||||
|
||||
### Files Read
|
||||
|
||||
- `apps/edge/internal/openai/routes.go`
|
||||
- `apps/edge/internal/openai/route_resolution.go`
|
||||
- `apps/edge/internal/openai/chat_handler.go`
|
||||
- `apps/edge/internal/openai/responses_handler.go`
|
||||
- `apps/edge/internal/openai/anthropic_handler.go`
|
||||
- `apps/edge/internal/openai/server.go`
|
||||
- `apps/edge/internal/openai/openai_auth_routes_models_test.go`
|
||||
- `apps/edge/internal/openai/provider_selection_test.go`
|
||||
- `apps/edge/internal/openai/anthropic_surface_test.go`
|
||||
- `apps/edge/internal/service/provider_pool.go`
|
||||
- `apps/edge/internal/service/protocol_profile_test.go`
|
||||
- `agent-contract/outer/openai-compatible-api.md`
|
||||
- `agent-contract/outer/anthropic-compatible-api.md`
|
||||
|
||||
### SDD Criteria
|
||||
|
||||
- SDD: `agent-roadmap/sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md` (`승인`, 잠금 해제).
|
||||
- Foundation maps to S06 / `model-discovery` and S07 / `explicit-selection`, Evidence Map S04-S08, but lacks authenticated projection rollout evidence and therefore has no Roadmap Targets.
|
||||
- Checklist covers principal list isolation, inactive omission, exact provider/profile/model candidate predicate, stable slot metadata, recovery predicate retention, and no legacy/alternate-slot fallback in a directly installed verified-cache fixture.
|
||||
|
||||
### Verification Context
|
||||
|
||||
- handoff 없음. Existing provider-pool `AcceptCandidate` is reapplied during queued/recovery resolution and is the deterministic no-fallback oracle.
|
||||
- Local Go-only tests use a counting fake service; no provider credential or network runner is required. Workspace-local Go temp/cache paths are mandatory.
|
||||
- Predecessor 03 is incomplete and will be archived on PASS; verification checks the narrow archived candidate.
|
||||
- Confidence: high for dormant foundation, not for production rollout.
|
||||
|
||||
### Test Coverage Gaps
|
||||
|
||||
- Existing model tests use global catalogs and do not isolate principals.
|
||||
- Existing selection tests allow provider-pool fallback and do not retain slot/route binding metadata.
|
||||
- No test proves managed lookup refuses legacy fallback while unmanaged behavior remains unchanged.
|
||||
|
||||
### Symbol References
|
||||
|
||||
- Rename/remove 없음.
|
||||
- Production `resolveRouteDispatch` calls: `chat_handler.go:40`, `responses_handler.go:40`, `anthropic_handler.go:49,98`; preserve legacy function and add a context-aware wrapper.
|
||||
- `advertisedModels` is called at `routes.go:60`; only internal managed branch changes.
|
||||
- Existing handler `AcceptCandidate` assignments must compose, not overwrite, the route predicate.
|
||||
|
||||
### Split Judgment
|
||||
|
||||
- Discovery and dispatch resolution share the same route-view invariant and stay atomic.
|
||||
- Predecessor 03 `03+01,02_projection_auth`: active/archived completion missing, ambiguity 없음.
|
||||
- Authenticated transport activation remains a separate secure-transport rollout; no Roadmap Targets section is present.
|
||||
|
||||
### Scope Rationale
|
||||
|
||||
- Do not modify CP store/wire, Connector/Runtime, provider queue accounting, secret delivery, or provider auth header injection.
|
||||
- Managed behavior is reachable only through packet-03 verified-cache state; current production stays unmanaged/legacy.
|
||||
- Contracts describe gated semantics, not an active CP-managed deployment mode.
|
||||
|
||||
### Final Routing
|
||||
|
||||
- `status=routed`, `evaluation_mode=isolated-reassessment`, finalizer=`finalize-task-policy.sh`, pair mode.
|
||||
- Build closures all true for dormant route foundation; scores `2+1+1+1+2=G07`; base/route=`local-fit`, local, `PLAN-local-G07.md`.
|
||||
- Review closures all true; scores `2+1+1+2+2=G08`; `official-review`, cloud, `gpt-5.6-sol` xhigh, `CODE_REVIEW-cloud-G08.md`.
|
||||
- `large_indivisible_context=false`; risks=`boundary_contract`, `structured_interpretation`, `variant_product` (3); rework=0; integrity failure=false; no capability gap.
|
||||
|
||||
## Dependencies and Execution Order
|
||||
|
||||
1. `03+01,02_projection_auth` must complete; runtime accepts its archived `complete.log`.
|
||||
2. Implement principal route list/lookup, then compose its fixed predicate in all three handlers.
|
||||
3. No dependency absent from `04+03_principal_routes` may be introduced.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Return only a verified-cache principal's active projected route IDs from OpenAI and Anthropic model-list variants, covering the S06 isolation fixtures without production activation.
|
||||
- [ ] Resolve request model to one projected route and compose fixed provider/profile/upstream-model predicates plus stable slot metadata across initial and recovery dispatch, covering S07 no-fallback fixtures.
|
||||
- [ ] Preserve unmanaged global/legacy behavior and prove managed unknown/inactive/cross-principal routes never fall back or dispatch.
|
||||
- [ ] Keep contracts explicitly gated and run fresh focused/full Edge, dormant-activation, vet, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
### [API-1] Add principal-scoped dormant discovery
|
||||
|
||||
**Problem:** `apps/edge/internal/openai/routes.go:60` always lists the global catalog and ignores authenticated principal context.
|
||||
|
||||
**Solution:** When the packet-03 cache is managed, list only active routes for the request principal from one snapshot, sort by canonical route id, and use the same list for OpenAI and Anthropic shapes. Unmanaged mode retains current behavior.
|
||||
|
||||
Before (`apps/edge/internal/openai/routes.go:60-66`):
|
||||
|
||||
```go
|
||||
models := s.advertisedModels()
|
||||
if anthropic {
|
||||
if err := validateAnthropicHeaders(r, false); err != nil {
|
||||
writeAnthropicError(w, http.StatusBadRequest, "invalid_request_error", err.Error())
|
||||
return
|
||||
}
|
||||
writeAnthropicModels(w, models)
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
models, err := s.advertisedModelsForPrincipal(r.Context())
|
||||
if err != nil { s.writeManagedRouteError(w, r, err); return }
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/edge/internal/authprojection/cache.go`: expose active route snapshots for exactly one principal.
|
||||
- [ ] `apps/edge/internal/openai/principal_routes.go`: implement managed list/normalization/sorting/errors.
|
||||
- [ ] `apps/edge/internal/openai/routes.go`: use managed list for both variants.
|
||||
- [ ] `apps/edge/internal/openai/principal_routes_test.go`: cover two-principal isolation, inactive/ambiguous omission, ordering, aliases, and unmanaged compatibility.
|
||||
|
||||
**Test Strategy:** Write `TestManagedModelsListIsPrincipalScoped`, `TestManagedAnthropicModelsListUsesRouteIDs`, `TestManagedModelsOmitInactiveRoutes`, and `TestUnmanagedModelsRetainGlobalCatalog`.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'Managed.*Models|PrincipalRoute|UnmanagedModels'` passes.
|
||||
|
||||
### [API-2] Fix managed requests to one projected route
|
||||
|
||||
**Problem:** `apps/edge/internal/openai/route_resolution.go:103-115` resolves global model groups, and handlers may overwrite request-local candidate predicates.
|
||||
|
||||
**Solution:** Add a context-aware wrapper. Managed mode resolves one principal route and returns route id, slot id, provider id, profile id, and upstream model; unmanaged mode calls the legacy resolver. Compose its predicate with stream/profile predicates so initial and recovery candidate resolution cannot change the binding.
|
||||
|
||||
Required standard-library import:
|
||||
|
||||
```go
|
||||
import "context"
|
||||
```
|
||||
|
||||
Before (`apps/edge/internal/openai/route_resolution.go:103-108`):
|
||||
|
||||
```go
|
||||
// resolveRouteDispatch returns fully-resolved dispatch params for model.
|
||||
// Priority: provider-pool catalog → legacy model_routes → single-target fallback.
|
||||
// Returns (dispatch, true) on success; (zero, false) when no target can be resolved.
|
||||
func (s *Server) resolveRouteDispatch(model string) (routeDispatch, bool) {
|
||||
// Provider-pool catalog takes highest priority.
|
||||
if catalogEntry := s.findProviderPoolEntry(model); catalogEntry != nil {
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
func (s *Server) resolveRouteDispatchForPrincipal(ctx context.Context, model string) (routeDispatch, error) {
|
||||
if s.principalProjection.Managed() { return s.resolveProjectedRoute(ctx, model) }
|
||||
dispatch, ok := s.resolveRouteDispatch(model)
|
||||
if !ok { return routeDispatch{}, ErrRouteNotFound }
|
||||
return dispatch, nil
|
||||
}
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/edge/internal/openai/route_resolution.go`: add managed wrapper and route/slot facts while preserving legacy resolver.
|
||||
- [ ] `apps/edge/internal/openai/principal_routes.go`: resolve one route, compose predicates, and attach trusted metadata.
|
||||
- [ ] `apps/edge/internal/openai/chat_handler.go`: use wrapper and composed route/stream predicate.
|
||||
- [ ] `apps/edge/internal/openai/responses_handler.go`: use wrapper and composed route/stream predicate.
|
||||
- [ ] `apps/edge/internal/openai/anthropic_handler.go`: use wrapper and composed route/capability predicate.
|
||||
- [ ] `apps/edge/internal/openai/principal_routes_test.go`: test exact candidate filtering, recovery retention, zero fallback, and all surfaces.
|
||||
|
||||
**Test Strategy:** Write `TestManagedRouteSelectsOnlyBoundSlot`, `TestManagedRoutePredicateSurvivesRecovery`, `TestManagedRouteFailureDoesNotFallback`, and table cases for Chat/Responses/Messages/count_tokens.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'ManagedRoute|ExplicitSlot|NoFallback'` passes.
|
||||
|
||||
### [API-3] Keep gated contracts honest
|
||||
|
||||
**Problem:** contracts cannot describe principal-managed routing as available while no authenticated projection writer is wired.
|
||||
|
||||
**Solution:** Document route-id selection and no-fallback semantics under an explicit “verified managed projection only” gate, and state that current deployments remain legacy until secure transport activation.
|
||||
|
||||
Before (`apps/edge/internal/openai/anthropic_handler.go:49-54`):
|
||||
|
||||
```go
|
||||
dispatch, ok := s.resolveRouteDispatch(envelope.Model)
|
||||
if !ok || !dispatch.ProviderPool {
|
||||
writeAnthropicError(w, http.StatusBadRequest, "not_supported_error", "model does not resolve to a protocol profile")
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
dispatch, err := s.resolveRouteDispatchForPrincipal(r.Context(), envelope.Model)
|
||||
if err != nil || !dispatch.ProviderPool { s.writeAnthropicRouteError(w, err); return }
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `agent-contract/outer/openai-compatible-api.md`: document verified-cache gate, route ids, and no fallback.
|
||||
- [ ] `agent-contract/outer/anthropic-compatible-api.md`: document the same gated model-list/selection semantics.
|
||||
|
||||
**Test Strategy:** No docs-only test; API-1/API-2 tests are executable contract evidence.
|
||||
|
||||
**Verification:** `rg --sort path -n 'verified|managed|route_id|slot_id|fallback' agent-contract/outer/openai-compatible-api.md agent-contract/outer/anthropic-compatible-api.md` shows gated clauses.
|
||||
|
||||
## Modified Files Summary
|
||||
|
||||
| File | Items |
|
||||
|------|-------|
|
||||
| `apps/edge/internal/authprojection/cache.go` | API-1 |
|
||||
| `apps/edge/internal/openai/principal_routes.go` | API-1, API-2 |
|
||||
| `apps/edge/internal/openai/routes.go` | API-1 |
|
||||
| `apps/edge/internal/openai/route_resolution.go` | API-2 |
|
||||
| `apps/edge/internal/openai/chat_handler.go` | API-2 |
|
||||
| `apps/edge/internal/openai/responses_handler.go` | API-2 |
|
||||
| `apps/edge/internal/openai/anthropic_handler.go` | API-2 |
|
||||
| `apps/edge/internal/openai/principal_routes_test.go` | API-1, API-2 |
|
||||
| `agent-contract/outer/openai-compatible-api.md` | API-3 |
|
||||
| `agent-contract/outer/anthropic-compatible-api.md` | API-3 |
|
||||
| `agent-task/m-principal-provider-credential-slot-routing/04+03_principal_routes/CODE_REVIEW-cloud-G08.md` | API-1, API-2, API-3 |
|
||||
|
||||
## Final Verification
|
||||
|
||||
Run from `/config/workspace/iop-s0`; fresh Go results are required.
|
||||
|
||||
1. `test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/complete.log' -print | wc -l)" -eq 1` — exactly one archived predecessor completion exists.
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache` — executable Go paths exist.
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/edge/internal/authprojection ./apps/edge/internal/openai ./apps/edge/internal/service` — no vet findings.
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'Managed|PrincipalRoute|ExplicitSlot|NoFallback|Anthropic'` — focused fixtures pass.
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/authprojection ./apps/edge/internal/openai ./apps/edge/internal/service` — affected suites pass.
|
||||
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/...` — Edge regression suite passes.
|
||||
7. `if rg --sort path -n 'PrincipalProjection' apps/control-plane/internal/wire/edge_server.go apps/edge/internal/controlplane/connector.go apps/edge/internal/bootstrap/runtime.go; then exit 1; else exit 0; fi` — unauthenticated runtime still has no activation.
|
||||
8. `git diff --check` — no whitespace errors.
|
||||
|
||||
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/04+03_principal_routes plan=0 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `Implementation Checklist`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## Overview
|
||||
|
||||
date=2026-08-01
|
||||
task=m-principal-provider-credential-slot-routing/04+03_principal_routes, plan=0, tag=API
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
|
||||
- Task ids:
|
||||
- `model-discovery`: principal별 active route model list
|
||||
- `explicit-selection`: request model의 단일 slot route 선택 및 no fallback
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## For the Review Agent
|
||||
|
||||
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
|
||||
|
||||
Compare implementation of each item against source files and verify that output in `Verification Results` matches code.
|
||||
Review completion means the following steps are finished:
|
||||
|
||||
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
|
||||
2. Archive `CODE_REVIEW-cloud-G09.md` → `code_review_cloud_G09_0.log` and `PLAN-local-G08.md` → `plan_local_G08_0.log`.
|
||||
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/04+03_principal_routes/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
|
||||
4. If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata. Roadmap state check and `update-roadmap` calls are runtime responsibilities.
|
||||
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Item Completion
|
||||
|
||||
| Item | Status |
|
||||
|------|---------|
|
||||
| API-1 Make model discovery principal-scoped | [ ] |
|
||||
| API-2 Fix each request to one route and credential slot | [ ] |
|
||||
| API-3 Keep public contracts aligned | [ ] |
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Return only the authenticated principal's active projected route IDs from OpenAI and Anthropic model-list variants.
|
||||
- [ ] Resolve request model to one projected route and compose a fixed provider/profile/upstream-model candidate predicate plus stable slot metadata across initial and recovery dispatch.
|
||||
- [ ] Reject unknown, ambiguous, disabled, revoked, expired, cross-principal, and failed-slot routes without legacy or alternate-slot fallback in managed mode.
|
||||
- [ ] Update OpenAI/Anthropic contracts and run fresh isolated/full Edge tests, vet, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
## Review-Only Checklist
|
||||
|
||||
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
|
||||
> Implementing agents must not modify or check this section.
|
||||
|
||||
- [ ] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
|
||||
- [ ] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
|
||||
- [ ] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G09_0.log`.
|
||||
- [ ] Archive active `PLAN-*-G??.md` to `plan_local_G08_0.log`.
|
||||
- [ ] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
|
||||
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
|
||||
- [ ] If PASS, move active task directory `agent-task/m-principal-provider-credential-slot-routing/04+03_principal_routes/` to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/04+03_principal_routes/` and update this checklist at the final archive path.
|
||||
- [ ] If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata for runtime, without modifying roadmap or directly calling `update-roadmap`.
|
||||
- [ ] If PASS for split work, remove empty active parent `agent-task/m-principal-provider-credential-slot-routing/` or verify it was kept due to remaining siblings/files.
|
||||
- [ ] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
_Record any deviations from the plan and the rationale here._
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
_Record key design decisions here._
|
||||
|
||||
## Reviewer Checkpoints
|
||||
|
||||
- Verify predecessor 03 completion and that managed discovery/selection read the same immutable projection view.
|
||||
- Confirm managed-mode unknown/inactive/ambiguous routes never enter global catalog, legacy routes, or fallback target resolution.
|
||||
- Inspect predicate composition so stream/profile predicates cannot overwrite provider/profile/upstream route fencing.
|
||||
- Prove recovery re-resolution carries the same predicate and trusted `route_id`/`slot_id` metadata.
|
||||
- Confirm no secret delivery/decrypt logic was pulled forward from Epic 3.
|
||||
|
||||
## Verification Results
|
||||
|
||||
Paste actual stdout/stderr beneath every command; do not summarize or reconstruct it.
|
||||
|
||||
### API-1
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'Managed.*Models|PrincipalRoute'`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-2
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'ManagedRoute|ExplicitSlot|NoFallback'`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-3
|
||||
|
||||
`rg --sort path -n "principal|route_id|slot_id|fallback" agent-contract/outer/openai-compatible-api.md agent-contract/outer/anthropic-compatible-api.md`
|
||||
|
||||
_Pending._
|
||||
|
||||
### Final Verification
|
||||
|
||||
1. `test -f agent-task/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/complete.log`
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache`
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/edge/internal/openai ./apps/edge/internal/service`
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'Managed|PrincipalRoute|ExplicitSlot|NoFallback|Anthropic'`
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/service`
|
||||
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/...`
|
||||
7. `rg --sort path -n "principal|route_id|slot_id|fallback" agent-contract/outer/openai-compatible-api.md agent-contract/outer/anthropic-compatible-api.md`
|
||||
8. `git diff --check`
|
||||
|
||||
_Pending actual output for each command._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Section Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
|
||||
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
|
||||
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
|
||||
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section |
|
||||
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholder text with actual content |
|
||||
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
|
||||
| Verification Results (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `Deviations from Plan` entry |
|
||||
| Code Review Result | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/04+03_principal_routes plan=0 tag=API -->
|
||||
|
||||
# Principal Model Discovery and Explicit Route Selection
|
||||
|
||||
## For the Implementing Agent
|
||||
|
||||
Filling the implementation-owned sections of `CODE_REVIEW-cloud-G09.md` is mandatory. Run every verification command, paste actual output/notes, keep the active pair, and report ready for review; only code-review finalizes, renames, writes `complete.log`, or archives. If blocked, record exact blocker evidence and resume condition only in implementation-owned fields; do not ask the user, invoke user-input tools, create stop files, or classify next state.
|
||||
|
||||
## Background
|
||||
|
||||
`/v1/models` currently lists the global provider catalog, and route resolution prioritizes global catalog then legacy routes/fallback. A projected principal can therefore neither discover only its authorized client route IDs nor force a request to remain on the one credential slot/profile/model binding selected by `model`.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
|
||||
- Task ids:
|
||||
- `model-discovery`: principal별 active route model list
|
||||
- `explicit-selection`: request model의 단일 slot route 선택 및 no fallback
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## Analysis
|
||||
|
||||
### Files Read
|
||||
|
||||
- `apps/edge/internal/openai/routes.go`
|
||||
- `apps/edge/internal/openai/route_resolution.go`
|
||||
- `apps/edge/internal/openai/chat_handler.go`
|
||||
- `apps/edge/internal/openai/responses_handler.go`
|
||||
- `apps/edge/internal/openai/anthropic_handler.go`
|
||||
- `apps/edge/internal/openai/server.go`
|
||||
- `apps/edge/internal/openai/openai_auth_routes_models_test.go`
|
||||
- `apps/edge/internal/openai/provider_selection_test.go`
|
||||
- `apps/edge/internal/openai/anthropic_surface_test.go`
|
||||
- `apps/edge/internal/service/provider_pool.go`
|
||||
- `apps/edge/internal/service/protocol_profile_test.go`
|
||||
- `agent-contract/outer/openai-compatible-api.md`
|
||||
- `agent-contract/outer/anthropic-compatible-api.md`
|
||||
|
||||
### SDD Criteria
|
||||
|
||||
- SDD: `agent-roadmap/sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md` (`승인`, 잠금 해제).
|
||||
- Targets: S06 / `model-discovery`, S07 / `explicit-selection`.
|
||||
- Evidence Map row: S04-S08의 principal discovery/no-fallback 근거 중 S06-S07.
|
||||
- Checklist는 two-principal list isolation, disabled/revoked/expired omission, Claude-compatible route IDs, same-upstream/two-slot disambiguation, provider/profile/model predicate, recovery re-resolution에서도 fixed slot metadata 유지, failure 시 다른 slot/vendor 미선택을 직접 검증한다.
|
||||
|
||||
### Verification Context
|
||||
|
||||
- handoff 없음. OpenAI/Anthropic handlers, provider-pool predicate/re-resolution contract, outer contracts, Edge local smoke rules에서 명령을 도출했다.
|
||||
- Local Go runner only; no real provider. Counting fake service evaluates `AcceptCandidate` and records metadata, including recovery re-entry.
|
||||
- Go temp/cache is pinned to ignored workspace `.cache` because default cache is inaccessible and `/tmp` noexec.
|
||||
- Precondition: `03+01,02_projection_auth/complete.log`; current active/archived completion missing, no ambiguity. Confidence: high.
|
||||
|
||||
### Test Coverage Gaps
|
||||
|
||||
- Existing model tests assert global catalog ordering/refresh, not principal filtering.
|
||||
- Existing provider selection tests allow provider-pool fallback and do not carry credential slot/route IDs.
|
||||
- Existing Anthropic tests route global models; no isolated model list or fixed-slot predicate tests.
|
||||
|
||||
### Symbol References
|
||||
|
||||
- No rename/remove.
|
||||
- `resolveRouteDispatch` direct production call sites: `chat_handler.go:40`, `responses_handler.go:40`, `anthropic_handler.go:49,98`; preserve the existing function for legacy/direct tests and add a context-aware wrapper.
|
||||
- `advertisedModels` call site: `routes.go:60`; change only this internal call to pass authenticated principal context.
|
||||
- Existing provider-pool `AcceptCandidate` writers in chat, responses, Anthropic must compose rather than overwrite principal route predicates.
|
||||
|
||||
### Split Judgment
|
||||
|
||||
- Discovery and selection share one immutable per-principal route view: listing an ID that dispatch cannot resolve, or resolving an ID hidden from listing, is invalid. Keep S06/S07 atomic.
|
||||
- Predecessor 03 = `03+01,02_projection_auth`; active `complete.log` missing, archived candidate 없음. Implementation waits for its exact complete log.
|
||||
- Secret decryption/injection remains downstream; fixed `slot_id` is carried as dispatch metadata so Epic 3 can consume it without reselecting.
|
||||
|
||||
### Scope Rationale
|
||||
|
||||
- Do not modify provider queue scheduling/resource accounting. Existing request-local candidate predicates already reapply on initial and recovery resolution.
|
||||
- Do not inject/decrypt credentials or remove legacy caller provider-auth forwarding; Epic 3 owns that cutover.
|
||||
- CP store/projection wire and management mutations are predecessor/parallel packet responsibilities.
|
||||
|
||||
### Final Routing
|
||||
|
||||
- `status=routed`, `evaluation_mode=first-pass`, finalizer=`finalize-task-policy.sh`, pair mode.
|
||||
- Build closures all true; scores `2+1+2+1+2=G08`; base/route=`local-fit`, local, canonical=`PLAN-local-G08.md`.
|
||||
- Review closures all true; scores `2+1+2+2+2=G09`; `official-review`, cloud, `gpt-5.6-sol` xhigh, canonical=`CODE_REVIEW-cloud-G09.md`.
|
||||
- `large_indivisible_context=false`; risks=`boundary_contract`, `structured_interpretation`, `variant_product` (3); rework=0; integrity failure=false; no capability gap.
|
||||
|
||||
## Dependencies and Execution Order
|
||||
|
||||
1. `03+01,02_projection_auth` must create `agent-task/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/complete.log`.
|
||||
2. Add route-view lookup/listing, then use the same resolved record to construct candidate predicates and metadata on all three handlers.
|
||||
3. Directory name `04+03_principal_routes` is the complete dependency declaration.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Return only the authenticated principal's active projected route IDs from OpenAI and Anthropic model-list variants.
|
||||
- [ ] Resolve request model to one projected route and compose a fixed provider/profile/upstream-model candidate predicate plus stable slot metadata across initial and recovery dispatch.
|
||||
- [ ] Reject unknown, ambiguous, disabled, revoked, expired, cross-principal, and failed-slot routes without legacy or alternate-slot fallback in managed mode.
|
||||
- [ ] Update OpenAI/Anthropic contracts and run fresh isolated/full Edge tests, vet, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
### [API-1] Make model discovery principal-scoped
|
||||
|
||||
**Problem:** `apps/edge/internal/openai/routes.go:60` calls `s.advertisedModels()`, which reads the global catalog and ignores authenticated principal context.
|
||||
|
||||
**Solution:** In managed mode, read the request principal and the exact current projection snapshot, filter active route records by principal and lifecycle/expiry, sort by stable route ID, and emit those IDs for both OpenAI and Anthropic shapes. Never merge legacy/global entries into a managed response.
|
||||
|
||||
Before (`apps/edge/internal/openai/routes.go:60-66`):
|
||||
|
||||
```go
|
||||
models := s.advertisedModels()
|
||||
if anthropic {
|
||||
if err := validateAnthropicHeaders(r, false); err != nil {
|
||||
writeAnthropicError(w, http.StatusBadRequest, "invalid_request_error", err.Error())
|
||||
return
|
||||
}
|
||||
writeAnthropicModels(w, models)
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
models, err := s.advertisedModelsForPrincipal(r.Context())
|
||||
if err != nil {
|
||||
s.writeManagedRouteError(w, r, err)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/edge/internal/openai/principal_projection.go`: expose immutable active routes for one principal without leaking other principals.
|
||||
- [ ] `apps/edge/internal/openai/principal_routes.go`: implement principal route list, normalization, deterministic sorting, and managed errors.
|
||||
- [ ] `apps/edge/internal/openai/routes.go`: use principal-scoped discovery for both response variants.
|
||||
- [ ] `apps/edge/internal/openai/principal_routes_test.go`: test two-principal isolation, lifecycle omission, aliases, ordering, and Claude-compatible IDs.
|
||||
|
||||
**Test Strategy:** Write `TestManagedModelsListIsPrincipalScoped`, `TestManagedAnthropicModelsListUsesRouteIDs`, and `TestManagedModelsOmitInactiveOrAmbiguousRoutes`. Fixtures install one fresh snapshot with two principals and conflicting upstream model names.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'Managed.*Models|PrincipalRoute'` passes.
|
||||
|
||||
### [API-2] Fix each request to one route and credential slot
|
||||
|
||||
**Problem:** `apps/edge/internal/openai/route_resolution.go:103-115` resolves a global model group and lets provider-pool admission choose any matching candidate; handlers can overwrite `AcceptCandidate` with surface/runtime predicates.
|
||||
|
||||
**Solution:** Add `resolveRouteDispatchForPrincipal(ctx, model)` that, in managed mode, resolves exactly one projected route and returns `route_id`, `slot_id`, provider id, profile id, upstream model, and pool dispatch facts. Compose its candidate predicate with stream/profile predicates; attach route/slot IDs to trusted metadata. Managed lookup failures return an error and never call legacy resolution. Because `AcceptCandidate` is copied into recovery requests, the fixed predicate applies on every re-resolution.
|
||||
|
||||
Before (`apps/edge/internal/openai/route_resolution.go:103-108`):
|
||||
|
||||
```go
|
||||
// resolveRouteDispatch returns fully-resolved dispatch params for model.
|
||||
// Priority: provider-pool catalog → legacy model_routes → single-target fallback.
|
||||
func (s *Server) resolveRouteDispatch(model string) (routeDispatch, bool) {
|
||||
// Provider-pool catalog takes highest priority.
|
||||
if catalogEntry := s.findProviderPoolEntry(model); catalogEntry != nil {
|
||||
```
|
||||
|
||||
After managed wrapper:
|
||||
|
||||
```go
|
||||
func (s *Server) resolveRouteDispatchForPrincipal(ctx context.Context, model string) (routeDispatch, error) {
|
||||
if s.principalProjection.Managed() {
|
||||
return s.resolveProjectedRoute(ctx, model)
|
||||
}
|
||||
dispatch, ok := s.resolveRouteDispatch(model)
|
||||
if !ok { return routeDispatch{}, ErrRouteNotFound }
|
||||
return dispatch, nil
|
||||
}
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/edge/internal/openai/route_resolution.go`: extend dispatch facts and add context-aware managed wrapper while preserving legacy resolver.
|
||||
- [ ] `apps/edge/internal/openai/principal_routes.go`: resolve one principal route, construct fixed candidate predicate, compose predicates, and attach trusted metadata.
|
||||
- [ ] `apps/edge/internal/openai/chat_handler.go`: use managed wrapper and compose route predicate with stream-gate predicate.
|
||||
- [ ] `apps/edge/internal/openai/responses_handler.go`: use managed wrapper and compose route predicate with stream-gate predicate.
|
||||
- [ ] `apps/edge/internal/openai/anthropic_handler.go`: use managed wrapper and compose route predicate with Messages capability predicate.
|
||||
- [ ] `apps/edge/internal/openai/principal_routes_test.go`: assert exact provider/profile/model predicate, slot metadata, and no alternate selection on errors/recovery.
|
||||
|
||||
**Test Strategy:** Write `TestManagedRouteSelectsOnlyBoundSlot`, `TestManagedRouteCandidatePredicateSurvivesRecovery`, `TestManagedRouteFailureDoesNotFallback`, and table tests for Chat, Responses, Anthropic Messages/count_tokens. Use two routes sharing upstream model but different slot IDs and a fake provider pool with candidates in adverse order.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'ManagedRoute|ExplicitSlot|NoFallback'` passes.
|
||||
|
||||
### [API-3] Keep public contracts aligned
|
||||
|
||||
**Problem:** outer contracts describe global models/legacy route behavior and do not state managed principal isolation or exact slot no-fallback semantics.
|
||||
|
||||
**Solution:** Document route IDs as the `model` selector, principal-scoped listing, managed-mode error precedence, and the invariant that retry/recovery may re-resolve execution capacity but may not change credential slot/profile/model binding.
|
||||
|
||||
Before (`apps/edge/internal/openai/anthropic_handler.go:49-54`):
|
||||
|
||||
```go
|
||||
dispatch, ok := s.resolveRouteDispatch(envelope.Model)
|
||||
if !ok || !dispatch.ProviderPool {
|
||||
writeAnthropicError(w, http.StatusBadRequest, "not_supported_error", "model does not resolve to a protocol profile")
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
dispatch, err := s.resolveRouteDispatchForPrincipal(r.Context(), envelope.Model)
|
||||
if err != nil || !dispatch.ProviderPool {
|
||||
s.writeAnthropicRouteError(w, err)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `agent-contract/outer/openai-compatible-api.md`: document managed `/v1/models` and explicit route selection.
|
||||
- [ ] `agent-contract/outer/anthropic-compatible-api.md`: document Anthropic model-list variant and fixed route semantics.
|
||||
|
||||
**Test Strategy:** No docs-only test. API-1/API-2 tests are executable evidence for every documented behavior.
|
||||
|
||||
**Verification:** `rg --sort path -n "principal|route_id|slot_id|fallback" agent-contract/outer/openai-compatible-api.md agent-contract/outer/anthropic-compatible-api.md` shows the new contract clauses in stable path order.
|
||||
|
||||
## Modified Files Summary
|
||||
|
||||
| File | Items |
|
||||
|------|-------|
|
||||
| `apps/edge/internal/openai/principal_projection.go` | API-1 |
|
||||
| `apps/edge/internal/openai/principal_routes.go` | API-1, API-2 |
|
||||
| `apps/edge/internal/openai/routes.go` | API-1 |
|
||||
| `apps/edge/internal/openai/route_resolution.go` | API-2 |
|
||||
| `apps/edge/internal/openai/chat_handler.go` | API-2 |
|
||||
| `apps/edge/internal/openai/responses_handler.go` | API-2 |
|
||||
| `apps/edge/internal/openai/anthropic_handler.go` | API-2 |
|
||||
| `apps/edge/internal/openai/principal_routes_test.go` | API-1, API-2 |
|
||||
| `agent-contract/outer/openai-compatible-api.md` | API-3 |
|
||||
| `agent-contract/outer/anthropic-compatible-api.md` | API-3 |
|
||||
| `agent-task/m-principal-provider-credential-slot-routing/04+03_principal_routes/CODE_REVIEW-cloud-G09.md` | API-1, API-2, API-3 |
|
||||
|
||||
## Final Verification
|
||||
|
||||
Run from `/config/workspace/iop-s0`; Go results must be fresh.
|
||||
|
||||
1. `test -f agent-task/m-principal-provider-credential-slot-routing/03+01,02_projection_auth/complete.log` — predecessor complete.
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache` — executable temp/cache paths exist.
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/edge/internal/openai ./apps/edge/internal/service` — no vet findings.
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai -run 'Managed|PrincipalRoute|ExplicitSlot|NoFallback|Anthropic'` — focused isolation/selection suite passes.
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/service` — full affected suites pass.
|
||||
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/...` — Edge regression suite passes.
|
||||
7. `rg --sort path -n "principal|route_id|slot_id|fallback" agent-contract/outer/openai-compatible-api.md agent-contract/outer/anthropic-compatible-api.md` — contract clauses are present.
|
||||
8. `git diff --check` — no whitespace errors.
|
||||
|
||||
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/05+01,02_management_core plan=2 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `Implementation Checklist`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## Overview
|
||||
|
||||
date=2026-08-01
|
||||
task=m-principal-provider-credential-slot-routing/05+01,02_management_core, plan=2, tag=API
|
||||
|
||||
## Archive Evidence Snapshot
|
||||
|
||||
- Parent pair: `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_core/plan_local_G07_1.log`, `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_core/code_review_cloud_G08_1.log`.
|
||||
- Verdict/evidence: implementation had not started; no verdict or verification output.
|
||||
- Refinement reason: the in-process service and host-local first-principal bootstrap are independently implementable and verifiable; bootstrap moved to sibling `06+01,02_host_local_bootstrap`.
|
||||
- Roadmap carryover: this remains a preparatory packet. PASS must not check `credential-management`; S08 requires S09 at-rest sealer and S10 confidential Client transport rollout.
|
||||
|
||||
## For the Review Agent
|
||||
|
||||
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
|
||||
|
||||
Compare implementation of each item against source files and verify that output in `Verification Results` matches code.
|
||||
Review completion means the following steps are finished:
|
||||
|
||||
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
|
||||
2. Archive `CODE_REVIEW-cloud-G06.md` → `code_review_cloud_G06_2.log` and `PLAN-local-G06.md` → `plan_local_G06_2.log`.
|
||||
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/05+01,02_management_core/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
|
||||
4. If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata. Roadmap state check and `update-roadmap` calls are runtime responsibilities.
|
||||
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Item Completion
|
||||
|
||||
| Item | Status |
|
||||
|------|---------|
|
||||
| API-1 Add the in-process management core | [ ] |
|
||||
| API-3 Keep the service dormant on the plaintext boundary | [ ] |
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Add an in-process principal-authenticated management service with revision authorization, secret-blind records, and an injected-but-unimplemented `SecretSealer` boundary.
|
||||
- [ ] Prove own-principal lifecycle, cross-principal denial, stale revision conflict, missing-sealer failure, and raw-secret redaction with deterministic tests.
|
||||
- [ ] Prove service construction has no network registration side effect and leaves the plaintext ClientServer contract unchanged.
|
||||
- [ ] Run fresh race, Control Plane regression, archive-predecessor, structural absence, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
## Review-Only Checklist
|
||||
|
||||
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
|
||||
> Implementing agents must not modify or check this section.
|
||||
|
||||
- [ ] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
|
||||
- [ ] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
|
||||
- [ ] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G06_2.log`.
|
||||
- [ ] Archive active `PLAN-*-G??.md` to `plan_local_G06_2.log`.
|
||||
- [ ] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
|
||||
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
|
||||
- [ ] If PASS, move active task directory `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_core/` to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/05+01,02_management_core/` and update this checklist at the final archive path.
|
||||
- [ ] If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata for runtime, without modifying roadmap or directly calling `update-roadmap`.
|
||||
- [ ] If PASS for split work, remove empty active parent `agent-task/m-principal-provider-credential-slot-routing/` or verify it was kept due to remaining siblings/files.
|
||||
- [ ] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
_Record any deviations from the plan and the rationale here._
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
_Record key design decisions here._
|
||||
|
||||
## Reviewer Checkpoints
|
||||
|
||||
- Confirm no Roadmap Targets section exists and PASS cannot check S08.
|
||||
- Verify service authorization, revision CAS, missing-sealer fail-closed behavior, and secret-blind DTOs.
|
||||
- Confirm service construction has no ClientServer or protobuf registration side effect.
|
||||
- Prove raw IOP/provider secrets are absent from response DTOs, logs, errors, and persisted service inputs.
|
||||
- Confirm no concrete key loading/encryption or network credential transport entered this packet.
|
||||
|
||||
## Verification Results
|
||||
|
||||
Paste actual stdout/stderr beneath every command; do not summarize or reconstruct it.
|
||||
|
||||
### API-1
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialops`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-3
|
||||
|
||||
`if rg --sort path -n 'message[[:space:]]+.*(Bootstrap|CredentialOperation)|AddRequestListenerTyped.*Credential' proto/iop/control.proto apps/control-plane/internal/wire/client.go; then exit 1; else exit 0; fi`
|
||||
|
||||
_Pending._
|
||||
|
||||
### Final Verification
|
||||
|
||||
1. `test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/01_principal_store/complete.log' -print | wc -l)" -eq 1 && test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log' -print | wc -l)" -eq 1`
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache`
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/internal/credentialops`
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialops`
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/...`
|
||||
6. `if rg --sort path -n 'message[[:space:]]+.*(Bootstrap|CredentialOperation)|AddRequestListenerTyped.*Credential' proto/iop/control.proto apps/control-plane/internal/wire/client.go; then exit 1; else exit 0; fi`
|
||||
7. `git diff --check`
|
||||
|
||||
_Pending actual output for each command._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Section Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
|
||||
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
|
||||
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
|
||||
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section |
|
||||
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholder text with actual content |
|
||||
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
|
||||
| Verification Results (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `Deviations from Plan` entry |
|
||||
| Code Review Result | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/05+01,02_management_core plan=2 tag=API -->
|
||||
|
||||
# Credential Management Core
|
||||
|
||||
## For the Implementing Agent
|
||||
|
||||
Filling implementation-owned sections of `CODE_REVIEW-cloud-G06.md` is mandatory. Run every verification command, paste actual output, keep active files in place, and report ready for review; only code-review may finalize, rename logs, write `complete.log`, or archive. If blocked, record exact blocker evidence and resume conditions only in implementation-owned fields; do not ask the user, call user-input tools, create stop files, or classify next state.
|
||||
|
||||
## Background
|
||||
|
||||
Control Plane needs a principal-scoped in-process management service. The current `/client` WebSocket is plaintext and unauthenticated, so this packet keeps the service dormant, defines an injected `SecretSealer` boundary, and does not expose a remote management operation or claim S08 completion.
|
||||
|
||||
## Archive Evidence Snapshot
|
||||
|
||||
- Parent pair: `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_core/plan_local_G07_1.log`, `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_core/code_review_cloud_G08_1.log`.
|
||||
- Verdict/evidence: implementation had not started; no verdict or verification output.
|
||||
- Refinement reason: the in-process service and host-local first-principal bootstrap are independently implementable and verifiable; bootstrap moved to sibling `06+01,02_host_local_bootstrap`.
|
||||
- Roadmap carryover: this remains a preparatory packet. PASS must not check `credential-management`; S08 requires S09 at-rest sealer and S10 confidential Client transport rollout.
|
||||
|
||||
## Analysis
|
||||
|
||||
### Files Read
|
||||
|
||||
- `apps/control-plane/internal/wire/client.go`
|
||||
- `apps/control-plane/internal/wire/client_test.go`
|
||||
- `proto/iop/control.proto`
|
||||
- `apps/client/lib/iop_wire/client_wire_client.dart`
|
||||
- `apps/client/lib/iop_wire/parser_map.dart`
|
||||
- `agent-contract/inner/client-control-plane-wire.md`
|
||||
|
||||
### SDD Criteria
|
||||
|
||||
- SDD: `agent-roadmap/sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md` (`승인`, 잠금 해제).
|
||||
- This foundation maps to S08 / `credential-management`, but intentionally lacks the confidential remote operation required for full S08.
|
||||
- Checklist covers principal/revision authorization, secret-blind records, fail-closed missing sealer behavior, lifecycle isolation, and proof that creating the service does not register a plaintext network operation.
|
||||
|
||||
### Verification Context
|
||||
|
||||
- No handoff exists. The SDD requires principal clients to authenticate over TLS and forbids credential-bearing operations on the current plain WebSocket.
|
||||
- Go 1.26.2 linux/arm64. Tests use a temp SQLite store, fake sealer, injected logger/random reader, and workspace-local Go temp/cache paths.
|
||||
- Preconditions 01 and 02 are incomplete; runtime resolves their archived completion artifacts before implementation.
|
||||
- No external key manager, TLS runner, Flutter change, or provider is required. Confidence: high.
|
||||
|
||||
### Test Coverage Gaps
|
||||
|
||||
- No principal/revision authorization service exists.
|
||||
- Current tests do not cover own-principal lifecycle, cross-principal denial, stale revision conflicts, missing sealer failure, or secret-blind responses.
|
||||
- Existing ClientServer tests cover hello only; the new service must have no network registration side effect.
|
||||
|
||||
### Symbol References
|
||||
|
||||
- Rename/remove 없음.
|
||||
- `wire.NewClientServer`, Dart client/parser call sites, and Control Plane server wiring remain unchanged.
|
||||
|
||||
### Split Judgment
|
||||
|
||||
- This child owns the reusable authorization/CAS service and its dormant secure-boundary guard.
|
||||
- Sibling `06+01,02_host_local_bootstrap` independently owns the host-local first-principal CLI and structural absence check.
|
||||
- Further separation would split service behavior from the tests and fail-closed sealer rule required for its PASS.
|
||||
|
||||
### Scope Rationale
|
||||
|
||||
- Do not modify `proto/iop/control.proto`, ClientServer, Dart client/parser, configs, CLI registration, or Control Plane server wiring.
|
||||
- Define a `SecretSealer` interface only; tests use a fake. Do not add AES/key-file/deployment-key implementation.
|
||||
- Do not add remote bootstrap, credential reveal, or plaintext network payloads.
|
||||
|
||||
### Final Routing
|
||||
|
||||
- `status=routed`, `evaluation_mode=isolated-reassessment`, finalizer=`finalize-task-policy.sh`, pair mode.
|
||||
- Build closures all true; scores `1+2+1+1+1=G06`; base/route=`local-fit`, lane=`local`, canonical=`PLAN-local-G06.md`.
|
||||
- Review closures all true; scores `1+2+1+1+1=G06`; route=`official-review`, lane=`cloud`, model=`gpt-5.6-sol` xhigh, canonical=`CODE_REVIEW-cloud-G06.md`.
|
||||
- `large_indivisible_context=false`; risks=`temporal_state`, `concurrent_consistency`, `boundary_contract` (3); rework=0; integrity failure=false; capability gap 없음.
|
||||
|
||||
## Dependencies and Execution Order
|
||||
|
||||
1. `01_principal_store` and `02+01_credential_catalog` must complete; runtime accepts their archived `complete.log` files.
|
||||
2. Implement authentication/ownership/CAS mapping, then the injected sealer boundary and secret-blind responses.
|
||||
3. No dependency absent from `05+01,02_management_core` may be introduced.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Add an in-process principal-authenticated management service with revision authorization, secret-blind records, and an injected-but-unimplemented `SecretSealer` boundary.
|
||||
- [ ] Prove own-principal lifecycle, cross-principal denial, stale revision conflict, missing-sealer failure, and raw-secret redaction with deterministic tests.
|
||||
- [ ] Prove service construction has no network registration side effect and leaves the plaintext ClientServer contract unchanged.
|
||||
- [ ] Run fresh race, Control Plane regression, archive-predecessor, structural absence, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
### [API-1] Add the in-process management core
|
||||
|
||||
**Problem:** `apps/control-plane/internal/wire/client.go:62-73` has only hello, and there is no reusable service enforcing principal identity, target ownership, expected revision, or secret-blind response types.
|
||||
|
||||
**Solution:** Add `credentialops.Service` over the store. It hashes the presented IOP token in process, resolves the principal, authorizes target equality, maps CAS/lifecycle errors, and exposes token/slot/route operations. Provider create/rotate receives plaintext only at the method boundary, requires an injected `SecretSealer`, clears the local byte slice on return, and passes only an opaque envelope to the store. The production packet supplies no sealer or network adapter.
|
||||
|
||||
Before (`apps/control-plane/internal/wire/client.go:62-73`):
|
||||
|
||||
```go
|
||||
proto_socket.AddRequestListenerTyped(&client.Communicator, func(req *iop.ClientHelloRequest) (*iop.ClientHelloResponse, error) {
|
||||
return &iop.ClientHelloResponse{Ready: true, Protocol: Protocol}, nil
|
||||
})
|
||||
```
|
||||
|
||||
After service-only boundary:
|
||||
|
||||
```go
|
||||
type SecretSealer interface {
|
||||
Seal(context.Context, []byte, SecretContext) (credentialstore.EncryptedSecretEnvelope, error)
|
||||
}
|
||||
func (s *Service) RotateSlot(ctx context.Context, rawIOPToken []byte, input RotateSlotInput) (SlotRecord, error)
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/internal/credentialops/service.go`: implement authentication, ownership, CAS, lifecycle mapping, secret-blind DTOs, and sealer interface.
|
||||
- [ ] `apps/control-plane/internal/credentialops/service_test.go`: cover own/cross-principal operations, stale revision, lifecycle, missing sealer, fake-sealer ciphertext handoff, captured-log redaction, and absence of network side effects.
|
||||
|
||||
**Test Strategy:** Write `TestServiceScopesOperationsToAuthenticatedPrincipal`, `TestServiceRejectsCrossPrincipalTargets`, `TestServiceRejectsStaleRevision`, `TestServiceRequiresSealerForSecretMutation`, and `TestServiceResponsesAreSecretBlind` using the temp store and deterministic fake sealer.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialops` passes.
|
||||
|
||||
### [API-3] Keep the service dormant on the plaintext boundary
|
||||
|
||||
**Problem:** a future service constructor could register credential operations on the current unauthenticated ClientServer as a side effect.
|
||||
|
||||
**Solution:** Keep `credentialops.Service` transport-neutral and add a deterministic test that constructing it does not mutate or register against the ClientServer. Missing sealer behavior remains fail-closed.
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/internal/credentialops/service_test.go`: prove no network registration side effect and missing sealer failure.
|
||||
|
||||
**Test Strategy:** Exercise the service directly without a wire server and verify no listener or generated credential message is required.
|
||||
|
||||
**Verification:** `if rg --sort path -n 'message[[:space:]]+.*(Bootstrap|CredentialOperation)|AddRequestListenerTyped.*Credential' proto/iop/control.proto apps/control-plane/internal/wire/client.go; then exit 1; else exit 0; fi` passes with no matches.
|
||||
|
||||
## Modified Files Summary
|
||||
|
||||
| File | Items |
|
||||
|------|-------|
|
||||
| `apps/control-plane/internal/credentialops/service.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialops/service_test.go` | API-1, API-3 |
|
||||
| `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_core/CODE_REVIEW-cloud-G06.md` | API-1, API-3 |
|
||||
|
||||
## Final Verification
|
||||
|
||||
Run from `/config/workspace/iop-s0`; fresh Go results are required.
|
||||
|
||||
1. `test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/01_principal_store/complete.log' -print | wc -l)" -eq 1 && test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log' -print | wc -l)" -eq 1` — both archived predecessors exist exactly once.
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache` — executable Go paths exist.
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/internal/credentialops` — no vet findings.
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialops` — service lifecycle and race tests pass.
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/...` — Control Plane regression suite passes.
|
||||
6. `if rg --sort path -n 'message[[:space:]]+.*(Bootstrap|CredentialOperation)|AddRequestListenerTyped.*Credential' proto/iop/control.proto apps/control-plane/internal/wire/client.go; then exit 1; else exit 0; fi` — no plaintext management wire was added.
|
||||
7. `git diff --check` — no whitespace errors.
|
||||
|
||||
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/05+01,02_management_ops plan=1 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `Implementation Checklist`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## Overview
|
||||
|
||||
date=2026-08-01
|
||||
task=m-principal-provider-credential-slot-routing/05+01,02_management_ops, plan=1, tag=API
|
||||
|
||||
## Archive Evidence Snapshot
|
||||
|
||||
- Prior pair: `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_ops/plan_cloud_G10_0.log`, `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_ops/code_review_cloud_G10_0.log`.
|
||||
- Verdict/evidence: implementation had not started; no verdict or verification output.
|
||||
- Replan reason: the previous plan sent raw IOP/provider credentials on the current plaintext Client WebSocket and implemented part of Epic-3 at-rest protection while claiming only S08.
|
||||
- Roadmap carryover: this is a preparatory service/CLI packet. PASS must not check `credential-management`; S08 requires S09 at-rest sealer and S10 confidential Client transport rollout.
|
||||
|
||||
## For the Review Agent
|
||||
|
||||
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
|
||||
|
||||
Compare implementation of each item against source files and verify that output in `Verification Results` matches code.
|
||||
Review completion means the following steps are finished:
|
||||
|
||||
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
|
||||
2. Archive `CODE_REVIEW-cloud-G08.md` → `code_review_cloud_G08_1.log` and `PLAN-local-G07.md` → `plan_local_G07_1.log`.
|
||||
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/05+01,02_management_ops/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
|
||||
4. If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata. Roadmap state check and `update-roadmap` calls are runtime responsibilities.
|
||||
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Item Completion
|
||||
|
||||
| Item | Status |
|
||||
|------|---------|
|
||||
| API-1 Add the in-process management core | [ ] |
|
||||
| API-2 Add host-local first-principal bootstrap | [ ] |
|
||||
| API-3 Guard the secure-transport boundary | [ ] |
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Add an in-process principal-authenticated management service with revision authorization, secret-blind records, and an injected-but-unimplemented `SecretSealer` boundary.
|
||||
- [ ] Add a host-local-only first-principal/bootstrap CLI that emits the initial raw IOP token exactly once and never logs or persists it.
|
||||
- [ ] Prove own-principal lifecycle, cross-principal denial, stale revision conflict, one-time output, restart persistence, and raw-secret redaction with deterministic tests.
|
||||
- [ ] Prove no management/bootstrap protobuf, ClientServer listener, Dart method, or plaintext network activation was added; leave S08 completion for S09/S10 rollout.
|
||||
- [ ] Run fresh race, Control Plane regression, archive-predecessor, structural absence, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
## Review-Only Checklist
|
||||
|
||||
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
|
||||
> Implementing agents must not modify or check this section.
|
||||
|
||||
- [ ] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
|
||||
- [ ] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
|
||||
- [ ] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G08_1.log`.
|
||||
- [ ] Archive active `PLAN-*-G??.md` to `plan_local_G07_1.log`.
|
||||
- [ ] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
|
||||
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
|
||||
- [ ] If PASS, move active task directory `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_ops/` to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/05+01,02_management_ops/` and update this checklist at the final archive path.
|
||||
- [ ] If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata for runtime, without modifying roadmap or directly calling `update-roadmap`.
|
||||
- [ ] If PASS for split work, remove empty active parent `agent-task/m-principal-provider-credential-slot-routing/` or verify it was kept due to remaining siblings/files.
|
||||
- [ ] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
_Record any deviations from the plan and the rationale here._
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
_Record key design decisions here._
|
||||
|
||||
## Reviewer Checkpoints
|
||||
|
||||
- Confirm no Roadmap Targets section exists and PASS cannot check S08.
|
||||
- Verify service authorization, revision CAS, missing-sealer fail-closed, and secret-blind DTOs.
|
||||
- Confirm bootstrap is direct host CLI, refuses an existing principal, and emits/logs raw token only as specified.
|
||||
- Prove proto, ClientServer, Dart client/parser, and server wiring remain unchanged.
|
||||
- Confirm no concrete key loading/encryption or network credential transport was pulled into this packet.
|
||||
|
||||
## Verification Results
|
||||
|
||||
Paste actual stdout/stderr beneath every command; do not summarize or reconstruct it.
|
||||
|
||||
### API-1
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialops`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-2
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/cmd/control-plane -run 'PrincipalBootstrap|Credential'`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-3
|
||||
|
||||
`if rg --sort path -n 'message[[:space:]]+.*(Bootstrap|CredentialOperation)|AddRequestListenerTyped.*Credential' proto/iop/control.proto apps/control-plane/internal/wire/client.go; then exit 1; else exit 0; fi`
|
||||
|
||||
_Pending._
|
||||
|
||||
### Final Verification
|
||||
|
||||
1. `test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/01_principal_store/complete.log' -print | wc -l)" -eq 1 && test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log' -print | wc -l)" -eq 1`
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache`
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/internal/credentialops ./apps/control-plane/cmd/control-plane`
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialops ./apps/control-plane/cmd/control-plane`
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/...`
|
||||
6. `if rg --sort path -n 'message[[:space:]]+.*(Bootstrap|CredentialOperation)|AddRequestListenerTyped.*Credential' proto/iop/control.proto apps/control-plane/internal/wire/client.go; then exit 1; else exit 0; fi`
|
||||
7. `git diff --check`
|
||||
|
||||
_Pending actual output for each command._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Section Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
|
||||
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
|
||||
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
|
||||
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section |
|
||||
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholder text with actual content |
|
||||
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
|
||||
| Verification Results (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `Deviations from Plan` entry |
|
||||
| Code Review Result | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/05+01,02_management_ops plan=0 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `Implementation Checklist`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## Overview
|
||||
|
||||
date=2026-08-01
|
||||
task=m-principal-provider-credential-slot-routing/05+01,02_management_ops, plan=0, tag=API
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
|
||||
- Task ids:
|
||||
- `credential-management`: host-local bootstrap 및 principal-scoped management operations
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## For the Review Agent
|
||||
|
||||
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
|
||||
|
||||
Compare implementation of each item against source files and verify that output in `Verification Results` matches code.
|
||||
Review completion means the following steps are finished:
|
||||
|
||||
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
|
||||
2. Archive `CODE_REVIEW-cloud-G10.md` → `code_review_cloud_G10_0.log` and `PLAN-cloud-G10.md` → `plan_cloud_G10_0.log`.
|
||||
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/05+01,02_management_ops/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
|
||||
4. If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata. Roadmap state check and `update-roadmap` calls are runtime responsibilities.
|
||||
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Item Completion
|
||||
|
||||
| Item | Status |
|
||||
|------|---------|
|
||||
| API-1 Centralize authorization and sealing | [ ] |
|
||||
| API-2 Expose typed authenticated wire operations | [ ] |
|
||||
| API-3 Add host-local bootstrap only | [ ] |
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Add a principal-authenticated credential-operation service and host-local key-file sealer that never gives plaintext to the repository.
|
||||
- [ ] Add typed Client-Control Plane management requests/responses for create/list/update/rotate/disable/revoke with principal and revision authorization.
|
||||
- [ ] Add a host-local-only bootstrap CLI that emits the first raw IOP token once and has no remote wire equivalent.
|
||||
- [ ] Update generated Go/Dart code, Dart client/parser methods, config, and the inner wire contract.
|
||||
- [ ] Prove bootstrap and every operation, cross-principal denial, CAS conflict, ciphertext persistence, and secret non-disclosure with fresh Go/Dart tests.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
## Review-Only Checklist
|
||||
|
||||
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
|
||||
> Implementing agents must not modify or check this section.
|
||||
|
||||
- [ ] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
|
||||
- [ ] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
|
||||
- [ ] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G10_0.log`.
|
||||
- [ ] Archive active `PLAN-*-G??.md` to `plan_cloud_G10_0.log`.
|
||||
- [ ] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
|
||||
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
|
||||
- [ ] If PASS, move active task directory `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_ops/` to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/05+01,02_management_ops/` and update this checklist at the final archive path.
|
||||
- [ ] If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata for runtime, without modifying roadmap or directly calling `update-roadmap`.
|
||||
- [ ] If PASS for split work, remove empty active parent `agent-task/m-principal-provider-credential-slot-routing/` or verify it was kept due to remaining siblings/files.
|
||||
- [ ] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
_Record any deviations from the plan and the rationale here._
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
_Record key design decisions here._
|
||||
|
||||
## Reviewer Checkpoints
|
||||
|
||||
- Verify both predecessor complete logs and confirm management auth reads the durable token store directly.
|
||||
- Inspect every operation for principal equality, expected revision, stable error mapping, and no cross-principal existence leak.
|
||||
- Confirm plaintext provider credentials cross only the service→sealer boundary and repository input is ciphertext envelope only.
|
||||
- Confirm first-principal bootstrap is direct host CLI with no protobuf request equivalent and one-time stdout token behavior.
|
||||
- Re-run Go race and Dart parser/client evidence; inspect logs/errors/list records for raw secrets.
|
||||
|
||||
## Verification Results
|
||||
|
||||
Paste actual stdout/stderr beneath every command; do not summarize or reconstruct it.
|
||||
|
||||
### API-1
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialops`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-2
|
||||
|
||||
`make proto && make proto-dart && GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/wire ./apps/control-plane/cmd/control-plane && make client-test`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-3
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/cmd/control-plane -run 'Bootstrap|Credential|Config'`
|
||||
|
||||
_Pending._
|
||||
|
||||
### Final Verification
|
||||
|
||||
1. `test -f agent-task/m-principal-provider-credential-slot-routing/01_principal_store/complete.log && test -f agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log`
|
||||
2. `command -v go && go version && command -v protoc && protoc --version && command -v protoc-gen-go && command -v protoc-gen-dart && command -v flutter && flutter --version`
|
||||
3. `mkdir -p .cache/go-build .cache/go-cache`
|
||||
4. `make proto && make proto-dart`
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/...`
|
||||
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialops ./apps/control-plane/internal/credentialstore ./apps/control-plane/internal/wire ./apps/control-plane/cmd/control-plane`
|
||||
7. `make client-test`
|
||||
8. `cd apps/client && flutter analyze`
|
||||
9. `if rg --sort path -n 'Bootstrap.*Request|bootstrap.*request' proto/iop/control.proto agent-contract/inner/client-control-plane-wire.md; then exit 1; else exit 0; fi`
|
||||
10. `git diff --check`
|
||||
|
||||
_Pending actual output for each command._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Section Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
|
||||
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
|
||||
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
|
||||
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section |
|
||||
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholder text with actual content |
|
||||
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
|
||||
| Verification Results (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `Deviations from Plan` entry |
|
||||
| Code Review Result | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/05+01,02_management_ops plan=0 tag=API -->
|
||||
|
||||
# Credential Management Operations
|
||||
|
||||
## For the Implementing Agent
|
||||
|
||||
Filling the implementation-owned sections of `CODE_REVIEW-cloud-G10.md` is mandatory. Run every verification command, paste actual output/notes, keep the active pair in place, and report ready for review; only code-review may finalize, rename logs, write `complete.log`, or archive. If blocked, record exact blocker evidence, attempted commands/output, and resume condition only in implementation-owned fields; do not ask the user, invoke user-input tools, create stop files, or classify next state.
|
||||
|
||||
## Background
|
||||
|
||||
Control Plane currently exposes only hello/status wire behavior and a `serve` CLI command. The approved design requires a host-local bootstrap path plus authenticated principal-scoped create/list/update/rotate/disable/revoke operations, with raw IOP/provider secrets accepted or emitted only at the relevant one-time boundary.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md`
|
||||
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/principal-provider-credential-slot-routing.md)
|
||||
- Task ids:
|
||||
- `credential-management`: host-local bootstrap 및 principal-scoped management operations
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## Analysis
|
||||
|
||||
### Files Read
|
||||
|
||||
- `proto/iop/control.proto`
|
||||
- `proto/gen/iop/control.pb.go`
|
||||
- `apps/client/lib/gen/proto/iop/control.pb.dart`
|
||||
- `apps/client/lib/gen/proto/iop/control.pbenum.dart`
|
||||
- `apps/client/lib/gen/proto/iop/control.pbjson.dart`
|
||||
- `apps/client/lib/gen/proto/iop/control.pbserver.dart`
|
||||
- `apps/control-plane/cmd/control-plane/main.go`
|
||||
- `apps/control-plane/cmd/control-plane/server.go`
|
||||
- `apps/control-plane/cmd/control-plane/config_test.go`
|
||||
- `apps/control-plane/internal/wire/client.go`
|
||||
- `apps/control-plane/internal/wire/client_test.go`
|
||||
- `apps/client/lib/iop_wire/client_wire_client.dart`
|
||||
- `apps/client/lib/iop_wire/parser_map.dart`
|
||||
- `apps/client/test/iop_wire/client_wire_client_test.dart`
|
||||
- `apps/client/test/iop_wire/parser_map_test.dart`
|
||||
- `apps/client/test/iop_wire/generated_proto_import_test.dart`
|
||||
- `configs/control-plane.yaml`
|
||||
- `agent-contract/inner/client-control-plane-wire.md`
|
||||
|
||||
### SDD Criteria
|
||||
|
||||
- SDD: `agent-roadmap/sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md` (`승인`, 잠금 해제).
|
||||
- Target: S08 / `credential-management`.
|
||||
- Evidence Map row: S04-S08의 management authorization 근거 중 S08.
|
||||
- Checklist는 remote bootstrap 부재, host-local first principal/token issuance, authenticated principal-only token/slot/route mutations, optimistic revision, bootstrap/create/list/update/rotate/disable/revoke, cross-principal rejection, raw secret input/one-time output 및 list/log 비노출을 검증한다.
|
||||
|
||||
### Verification Context
|
||||
|
||||
- handoff 없음. existing Cobra/config, client WS parser, protobuf generation, client-control-plane contract, local CP/client test profiles에서 검증을 도출했다.
|
||||
- Tools: Go 1.26.2, protoc 29.3, Go/Dart generators present, Flutter 3.41.5/Dart 3.11.3. `make -n proto proto-dart` confirms canonical generation commands.
|
||||
- Preconditions: 01 and 02 complete logs. No external provider or running shared service is required; loopback WS and temp SQLite/key files suffice.
|
||||
- Default Go temp/cache is unusable and `/tmp` noexec, so ignored workspace `.cache` paths are mandatory. Confidence: high.
|
||||
|
||||
### Test Coverage Gaps
|
||||
|
||||
- ClientServer parser map recognizes only hello request/response; no authenticated management message or authorization test exists.
|
||||
- CLI has only `serve`; no host-local bootstrap or stdout/log secret hygiene test exists.
|
||||
- Client Dart parser/method tests know only hello.
|
||||
- No encryption ingress boundary exists between plaintext provider credential input and opaque store envelope.
|
||||
|
||||
### Symbol References
|
||||
|
||||
- No rename/remove.
|
||||
- `rootCmd` adds commands at `main.go:61` and existing `serveCmd` remains.
|
||||
- `wire.NewClientServer` call sites: `server.go:42` and `client_test.go`; preserve constructor compatibility through options.
|
||||
- Dart `ClientWireClient` public hello method remains source-compatible; management methods are additive.
|
||||
|
||||
### Split Judgment
|
||||
|
||||
- Host CLI and authenticated wire operations share the same authorization/service/sealing boundary; splitting would duplicate or temporarily bypass the raw-secret invariant. Keep them atomic.
|
||||
- Predecessor 01 `01_principal_store`: missing active `complete.log`, no archived candidate.
|
||||
- Predecessor 02 `02+01_credential_catalog`: missing active `complete.log`, no archived candidate.
|
||||
- This packet can PASS without projection/auth packet 03 because it authenticates management requests directly against the durable token store; later projection observes the transactionally bumped generation.
|
||||
|
||||
### Scope Rationale
|
||||
|
||||
- Implement only the sealing ingress needed to hand ciphertext envelopes to the packet-02 store: AES-256-GCM with a host-local external key file and non-secret key id. Do not claim or check Epic-3 `secret-at-rest`; key rotation policy, restart delivery, Edge decrypt, execution-time header injection, and plaintext zeroization evidence remain Epic 3.
|
||||
- No remote bootstrap operation is added. Bootstrap opens DB/key locally from the Control Plane process command.
|
||||
- No UI screens are added; Dart wire support is the typed client contract only.
|
||||
- Roadmap/spec/SDD and central Agent-Ops files remain untouched.
|
||||
|
||||
### Final Routing
|
||||
|
||||
- `status=routed`, `evaluation_mode=first-pass`, finalizer=`finalize-task-policy.sh`, pair mode.
|
||||
- Build closures all true; scores `2+2+2+2+2=G10`; base/route=`grade-boundary`, cloud, canonical=`PLAN-cloud-G10.md`.
|
||||
- Review closures all true; scores `2+2+2+2+2=G10`; `official-review`, cloud, `gpt-5.6-sol` xhigh, canonical=`CODE_REVIEW-cloud-G10.md`.
|
||||
- `large_indivisible_context=false`; risks=`temporal_state`, `concurrent_consistency`, `boundary_contract`, `variant_product` (4), risk boundary matched but grade basis retained; rework=0; integrity failure=false; no capability gap.
|
||||
|
||||
## Dependencies and Execution Order
|
||||
|
||||
1. `01_principal_store` must create `agent-task/m-principal-provider-credential-slot-routing/01_principal_store/complete.log`.
|
||||
2. `02+01_credential_catalog` must create `agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log`.
|
||||
3. Implement service/sealer, then WS operations/client, then host-local CLI on the same service.
|
||||
4. No dependency absent from `05+01,02_management_ops` may be added.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Add a principal-authenticated credential-operation service and host-local key-file sealer that never gives plaintext to the repository.
|
||||
- [ ] Add typed Client-Control Plane management requests/responses for create/list/update/rotate/disable/revoke with principal and revision authorization.
|
||||
- [ ] Add a host-local-only bootstrap CLI that emits the first raw IOP token once and has no remote wire equivalent.
|
||||
- [ ] Update generated Go/Dart code, Dart client/parser methods, config, and the inner wire contract.
|
||||
- [ ] Prove bootstrap and every operation, cross-principal denial, CAS conflict, ciphertext persistence, and secret non-disclosure with fresh Go/Dart tests.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
### [API-1] Centralize authorization and sealing
|
||||
|
||||
**Problem:** `apps/control-plane/internal/wire/client.go:37-46` parses only hello messages, and there is no service that authenticates a durable principal token or seals provider input before storage.
|
||||
|
||||
**Solution:** Add `credentialops.Service` over the credential store. Authenticate raw IOP tokens by SHA-256 lookup, authorize every target principal against that identity, require expected revision for mutations, and return typed stable errors. Add an AES-256-GCM `Sealer` loaded from a base64 32-byte host-local key file; key id/nonce/ciphertext only cross into `credentialstore`.
|
||||
|
||||
Before (`apps/control-plane/internal/wire/client.go:37-46`):
|
||||
|
||||
```go
|
||||
parserMap := proto_socket.ParserMap{
|
||||
proto_socket.TypeNameOf(&iop.ClientHelloRequest{}): func(b []byte) (proto.Message, error) {
|
||||
req := &iop.ClientHelloRequest{}
|
||||
return req, proto.Unmarshal(b, req)
|
||||
},
|
||||
```
|
||||
|
||||
After service boundary:
|
||||
|
||||
```go
|
||||
principal, err := svc.Authenticate(ctx, rawIOPToken)
|
||||
if err != nil { return ErrUnauthenticated }
|
||||
if principal.ID != input.PrincipalID { return ErrForbidden }
|
||||
envelope, err := svc.Sealer.Seal(input.ProviderSecret)
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/internal/credentialops/sealer.go`: load/validate key file and seal with random nonce/AAD; never log key/plaintext.
|
||||
- [ ] `apps/control-plane/internal/credentialops/sealer_test.go`: assert round-trip only in test helper, nonce uniqueness, missing/invalid key failures, ciphertext-only store input.
|
||||
- [ ] `apps/control-plane/internal/credentialops/service.go`: authenticate, authorize, map lifecycle/CAS errors, and expose typed token/slot/route operations.
|
||||
- [ ] `apps/control-plane/internal/credentialops/service_test.go`: cover own-principal success, cross-principal denial, lifecycle, CAS, one-time outputs, and captured log redaction.
|
||||
|
||||
**Test Strategy:** Write `TestServiceAuthenticatesDigestAndScopesPrincipal`, `TestServiceRejectsCrossPrincipalOperations`, `TestServiceCASConflicts`, `TestSealerRejectsMissingKey`, and `TestProviderSecretIsStoredOnlyAsCiphertext`. Use temp key/SQLite files and injected logger/random reader.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialops` passes.
|
||||
|
||||
### [API-2] Expose typed authenticated wire operations
|
||||
|
||||
**Problem:** `apps/control-plane/internal/wire/client.go:62-73` only handles hello and reports ready without an authenticated management surface.
|
||||
|
||||
**Solution:** Add additive protobuf messages for typed token/slot/route operations and stable status/error fields. Each management request includes the caller IOP token as input-only auth material; the server passes it directly to `credentialops.Service`, never logs it, and response/list types omit secrets. Only create-token returns a one-time token field. Extend Go/Dart parser maps and Dart client methods.
|
||||
|
||||
Before (`apps/control-plane/internal/wire/client.go:62-73`):
|
||||
|
||||
```go
|
||||
proto_socket.AddRequestListenerTyped(&client.Communicator, func(req *iop.ClientHelloRequest) (*iop.ClientHelloResponse, error) {
|
||||
logger.Info("client hello request received",
|
||||
zap.String("client_id", req.ClientId),
|
||||
zap.String("client_version", req.ClientVersion),
|
||||
)
|
||||
return &iop.ClientHelloResponse{Ready: true, Protocol: Protocol}, nil
|
||||
})
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
proto_socket.AddRequestListenerTyped(&client.Communicator, func(req *iop.CredentialOperationRequest) (*iop.CredentialOperationResponse, error) {
|
||||
return handleCredentialOperation(context.Background(), operations, req), nil
|
||||
})
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `proto/iop/control.proto`: add additive management operation/request/response/record messages and oneofs.
|
||||
- [ ] `proto/gen/iop/control.pb.go`: regenerate Go output.
|
||||
- [ ] `apps/client/lib/gen/proto/iop/control.pb.dart`: regenerate Dart messages.
|
||||
- [ ] `apps/client/lib/gen/proto/iop/control.pbenum.dart`: regenerate Dart enums.
|
||||
- [ ] `apps/client/lib/gen/proto/iop/control.pbjson.dart`: regenerate Dart JSON metadata.
|
||||
- [ ] `apps/client/lib/gen/proto/iop/control.pbserver.dart`: regenerate Dart server helpers.
|
||||
- [ ] `apps/control-plane/internal/wire/client.go`: add parser/listener/option and error mapping without auth-token logging.
|
||||
- [ ] `apps/control-plane/internal/wire/client_test.go`: test every operation class, missing/invalid/cross-principal auth, one-time token, and response secrecy.
|
||||
- [ ] `apps/control-plane/cmd/control-plane/server.go`: construct credentialops service and inject into ClientServer when store is configured.
|
||||
- [ ] `apps/client/lib/iop_wire/parser_map.dart`: register all management responses.
|
||||
- [ ] `apps/client/lib/iop_wire/client_wire_client.dart`: add typed management methods while keeping hello behavior.
|
||||
- [ ] `apps/client/test/iop_wire/parser_map_test.dart`: prove message parsing.
|
||||
- [ ] `apps/client/test/iop_wire/client_wire_client_test.dart`: prove request/response typing, status errors, and no secret fields on list records.
|
||||
- [ ] `apps/client/test/iop_wire/generated_proto_import_test.dart`: import/construct new generated messages.
|
||||
|
||||
**Test Strategy:** Add Go table test `TestCredentialOperationAuthorizationAndLifecycle` for bootstrap absence plus create/list/update/rotate/disable/revoke. Add Dart tests for each oneof and parser type. Assert malformed/unknown operations fail closed and list responses have no raw secret/token field.
|
||||
|
||||
**Verification:** `make proto && make proto-dart && GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/internal/wire ./apps/control-plane/cmd/control-plane && make client-test` passes.
|
||||
|
||||
### [API-3] Add host-local bootstrap only
|
||||
|
||||
**Problem:** `apps/control-plane/cmd/control-plane/main.go:49-62` registers only `serve`, so there is no safe first-principal bootstrap path.
|
||||
|
||||
**Solution:** Add `principal bootstrap --alias ...` as a local command that loads config, opens the DB directly, refuses when any principal already exists unless an explicit idempotent lookup proves the same request, issues the first raw IOP token once to command stdout, and writes no token through zap/errors. Add key-file path config for slot operations; the tracked YAML stores only an empty path/key id, never key bytes.
|
||||
|
||||
Before (`apps/control-plane/cmd/control-plane/main.go:59-62`):
|
||||
|
||||
```go
|
||||
}
|
||||
root.PersistentFlags().StringVarP(&cfgFile, "config", "c", "configs/control-plane.yaml", "config file path")
|
||||
root.AddCommand(serveCmd())
|
||||
return root
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
root.PersistentFlags().StringVarP(&cfgFile, "config", "c", "configs/control-plane.yaml", "config file path")
|
||||
root.AddCommand(serveCmd(), principalCmd())
|
||||
return root
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/cmd/control-plane/main.go`: add credential key-file config/env override and register principal CLI.
|
||||
- [ ] `apps/control-plane/cmd/control-plane/credential_commands.go`: implement direct host-local bootstrap with explicit stdout writer.
|
||||
- [ ] `apps/control-plane/cmd/control-plane/credential_commands_test.go`: test first bootstrap, second refusal, reopen, stdout one-time token, stderr/log redaction, and absence of remote bootstrap.
|
||||
- [ ] `apps/control-plane/cmd/control-plane/config_test.go`: test key-file path override without reading/logging contents.
|
||||
- [ ] `configs/control-plane.yaml`: document empty non-secret credential key file/key id settings.
|
||||
- [ ] `agent-contract/inner/client-control-plane-wire.md`: document management authentication, operations, revision/status/errors, secret fields, and explicit no-remote-bootstrap rule.
|
||||
|
||||
**Test Strategy:** Write `TestPrincipalBootstrapEmitsTokenOnce`, `TestPrincipalBootstrapRefusesSecondRoot`, `TestBootstrapLogsNeverContainToken`, and verify protobuf has no bootstrap request via deterministic `rg` command.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/cmd/control-plane -run 'Bootstrap|Credential|Config'` passes.
|
||||
|
||||
## Modified Files Summary
|
||||
|
||||
| File | Items |
|
||||
|------|-------|
|
||||
| `apps/control-plane/internal/credentialops/sealer.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialops/sealer_test.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialops/service.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialops/service_test.go` | API-1 |
|
||||
| `proto/iop/control.proto` | API-2 |
|
||||
| `proto/gen/iop/control.pb.go` | API-2 |
|
||||
| `apps/client/lib/gen/proto/iop/control.pb.dart` | API-2 |
|
||||
| `apps/client/lib/gen/proto/iop/control.pbenum.dart` | API-2 |
|
||||
| `apps/client/lib/gen/proto/iop/control.pbjson.dart` | API-2 |
|
||||
| `apps/client/lib/gen/proto/iop/control.pbserver.dart` | API-2 |
|
||||
| `apps/control-plane/internal/wire/client.go` | API-2 |
|
||||
| `apps/control-plane/internal/wire/client_test.go` | API-2 |
|
||||
| `apps/control-plane/cmd/control-plane/server.go` | API-2 |
|
||||
| `apps/client/lib/iop_wire/parser_map.dart` | API-2 |
|
||||
| `apps/client/lib/iop_wire/client_wire_client.dart` | API-2 |
|
||||
| `apps/client/test/iop_wire/parser_map_test.dart` | API-2 |
|
||||
| `apps/client/test/iop_wire/client_wire_client_test.dart` | API-2 |
|
||||
| `apps/client/test/iop_wire/generated_proto_import_test.dart` | API-2 |
|
||||
| `apps/control-plane/cmd/control-plane/main.go` | API-3 |
|
||||
| `apps/control-plane/cmd/control-plane/credential_commands.go` | API-3 |
|
||||
| `apps/control-plane/cmd/control-plane/credential_commands_test.go` | API-3 |
|
||||
| `apps/control-plane/cmd/control-plane/config_test.go` | API-3 |
|
||||
| `configs/control-plane.yaml` | API-3 |
|
||||
| `agent-contract/inner/client-control-plane-wire.md` | API-3 |
|
||||
| `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_ops/CODE_REVIEW-cloud-G10.md` | API-1, API-2, API-3 |
|
||||
|
||||
## Final Verification
|
||||
|
||||
Run from `/config/workspace/iop-s0`; fresh Go/Dart results are required.
|
||||
|
||||
1. `test -f agent-task/m-principal-provider-credential-slot-routing/01_principal_store/complete.log && test -f agent-task/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log` — both predecessors complete.
|
||||
2. `command -v go && go version && command -v protoc && protoc --version && command -v protoc-gen-go && command -v protoc-gen-dart && command -v flutter && flutter --version` — toolchains resolve.
|
||||
3. `mkdir -p .cache/go-build .cache/go-cache` — executable Go temp/cache paths exist.
|
||||
4. `make proto && make proto-dart` — generated sources are current.
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/...` — no vet findings.
|
||||
6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialops ./apps/control-plane/internal/credentialstore ./apps/control-plane/internal/wire ./apps/control-plane/cmd/control-plane` — service/store/wire/CLI lifecycle and race tests pass.
|
||||
7. `make client-test` — Dart wire/parser tests pass.
|
||||
8. `cd apps/client && flutter analyze` — client analysis has no findings.
|
||||
9. `if rg --sort path -n 'Bootstrap.*Request|bootstrap.*request' proto/iop/control.proto agent-contract/inner/client-control-plane-wire.md; then exit 1; else exit 0; fi` — no remote bootstrap request contract exists.
|
||||
10. `git diff --check` — no whitespace errors.
|
||||
|
||||
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/05+01,02_management_ops plan=1 tag=API -->
|
||||
|
||||
# Credential Management Core Foundation
|
||||
|
||||
## For the Implementing Agent
|
||||
|
||||
Filling implementation-owned sections of `CODE_REVIEW-cloud-G08.md` is mandatory. Run every verification command, paste actual output, keep active files in place, and report ready for review; only code-review may finalize, rename logs, write `complete.log`, or archive. If blocked, record exact blocker evidence and resume conditions only in implementation-owned fields; do not ask the user, call user-input tools, create stop files, or classify next state.
|
||||
|
||||
## Background
|
||||
|
||||
Control Plane needs a principal-scoped management service and a host-local first-principal bootstrap command. The current `/client` WebSocket is plaintext and unauthenticated, while the SDD explicitly forbids credential-bearing operations on it; this packet builds the service/CLI core but does not expose a remote management operation or claim S08 completion.
|
||||
|
||||
## Archive Evidence Snapshot
|
||||
|
||||
- Prior pair: `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_ops/plan_cloud_G10_0.log`, `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_ops/code_review_cloud_G10_0.log`.
|
||||
- Verdict/evidence: implementation had not started; no verdict or verification output.
|
||||
- Replan reason: the previous plan sent raw IOP/provider credentials on the current plaintext Client WebSocket and implemented part of Epic-3 at-rest protection while claiming only S08.
|
||||
- Roadmap carryover: this is a preparatory service/CLI packet. PASS must not check `credential-management`; S08 requires S09 at-rest sealer and S10 confidential Client transport rollout.
|
||||
|
||||
## Analysis
|
||||
|
||||
### Files Read
|
||||
|
||||
- `apps/control-plane/cmd/control-plane/main.go`
|
||||
- `apps/control-plane/cmd/control-plane/server.go`
|
||||
- `apps/control-plane/cmd/control-plane/config_test.go`
|
||||
- `apps/control-plane/internal/wire/client.go`
|
||||
- `apps/control-plane/internal/wire/client_test.go`
|
||||
- `proto/iop/control.proto`
|
||||
- `apps/client/lib/iop_wire/client_wire_client.dart`
|
||||
- `apps/client/lib/iop_wire/parser_map.dart`
|
||||
- `apps/client/test/iop_wire/client_wire_client_test.dart`
|
||||
- `apps/client/test/iop_wire/parser_map_test.dart`
|
||||
- `agent-contract/inner/client-control-plane-wire.md`
|
||||
|
||||
### SDD Criteria
|
||||
|
||||
- SDD: `agent-roadmap/sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md` (`승인`, 잠금 해제).
|
||||
- Foundation maps to S08 / `credential-management`, Evidence Map S04-S08, but intentionally lacks the confidential remote operation required for full S08.
|
||||
- Checklist covers host-local-only bootstrap, one-time raw token output, principal/revision authorization service fixtures, secret-blind list responses, and structural proof that no bootstrap/management protobuf or ClientServer listener is introduced.
|
||||
|
||||
### Verification Context
|
||||
|
||||
- handoff 없음. SDD says principal clients authenticate with IOP token over TLS and current plain WS cannot carry credential-bearing operations.
|
||||
- Go 1.26.2 linux/arm64. Tests use temp SQLite, fake sealer, injected logger/random reader, and workspace-local Go temp/cache paths.
|
||||
- Preconditions 01 and 02 are currently incomplete; runtime resolves their archived completion artifacts.
|
||||
- No external key manager, TLS runner, Flutter change, or provider is required. Confidence: high for core foundation.
|
||||
|
||||
### Test Coverage Gaps
|
||||
|
||||
- CLI exposes only `serve`; no bootstrap/idempotency/one-time-output test exists.
|
||||
- No principal/revision authorization service exists.
|
||||
- Existing ClientServer tests cover hello only; no test currently protects the intentional absence of credential-bearing listeners.
|
||||
|
||||
### Symbol References
|
||||
|
||||
- Rename/remove 없음.
|
||||
- `rootCmd` adds commands at `main.go:61`; `serveCmd` remains unchanged.
|
||||
- `wire.NewClientServer` and Dart client/parser call sites are intentionally unchanged.
|
||||
|
||||
### Split Judgment
|
||||
|
||||
- The service authorization/CAS contract and host-local bootstrap are a stable useful foundation that preserves current network behavior and independently PASSes.
|
||||
- Predecessors 01 and 02: active/archived completion missing, ambiguity 없음.
|
||||
- Remote Client operation and concrete `SecretSealer` are excluded and must be planned with S09/S10; no Roadmap Targets section is present.
|
||||
|
||||
### Scope Rationale
|
||||
|
||||
- Do not modify `proto/iop/control.proto`, ClientServer, Dart client/parser, configs, or Control Plane server wiring.
|
||||
- Define a `SecretSealer` interface only; tests use a fake. Do not add AES/key-file/deployment-key implementation in this packet.
|
||||
- Do not add remote bootstrap, credential reveal, or plaintext network payloads.
|
||||
|
||||
### Final Routing
|
||||
|
||||
- `status=routed`, `evaluation_mode=isolated-reassessment`, finalizer=`finalize-task-policy.sh`, pair mode.
|
||||
- Build closures all true for core foundation; scores `2+2+1+1+1=G07`; base/route=`local-fit`, local, `PLAN-local-G07.md`.
|
||||
- Review closures all true; scores `2+2+1+1+2=G08`; `official-review`, cloud, `gpt-5.6-sol` xhigh, `CODE_REVIEW-cloud-G08.md`.
|
||||
- `large_indivisible_context=false`; risks=`temporal_state`, `concurrent_consistency`, `boundary_contract` (3); rework=0; integrity failure=false; no capability gap.
|
||||
|
||||
## Dependencies and Execution Order
|
||||
|
||||
1. `01_principal_store` and `02+01_credential_catalog` must complete; runtime accepts their archived `complete.log` files.
|
||||
2. Implement authorization/CAS service, then host-local bootstrap and absence guards.
|
||||
3. No dependency absent from `05+01,02_management_ops` may be introduced.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Add an in-process principal-authenticated management service with revision authorization, secret-blind records, and an injected-but-unimplemented `SecretSealer` boundary.
|
||||
- [ ] Add a host-local-only first-principal/bootstrap CLI that emits the initial raw IOP token exactly once and never logs or persists it.
|
||||
- [ ] Prove own-principal lifecycle, cross-principal denial, stale revision conflict, one-time output, restart persistence, and raw-secret redaction with deterministic tests.
|
||||
- [ ] Prove no management/bootstrap protobuf, ClientServer listener, Dart method, or plaintext network activation was added; leave S08 completion for S09/S10 rollout.
|
||||
- [ ] Run fresh race, Control Plane regression, archive-predecessor, structural absence, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
### [API-1] Add the in-process management core
|
||||
|
||||
**Problem:** `apps/control-plane/internal/wire/client.go:62-73` has only hello, and there is no reusable service enforcing principal identity, target ownership, expected revision, or secret-blind response types.
|
||||
|
||||
**Solution:** Add `credentialops.Service` over the store. It hashes the presented IOP token in process, resolves the principal, authorizes target equality, maps CAS/lifecycle errors, and exposes token/slot/route operations. Provider create/rotate receives plaintext only at the method boundary, requires an injected `SecretSealer`, clears the local byte slice on return, and passes only an opaque envelope to the store. The production packet supplies no sealer or network adapter.
|
||||
|
||||
Before (`apps/control-plane/internal/wire/client.go:62-73`):
|
||||
|
||||
```go
|
||||
proto_socket.AddRequestListenerTyped(&client.Communicator, func(req *iop.ClientHelloRequest) (*iop.ClientHelloResponse, error) {
|
||||
logger.Info("client hello request received",
|
||||
zap.String("client_id", req.ClientId),
|
||||
zap.String("client_version", req.ClientVersion),
|
||||
)
|
||||
return &iop.ClientHelloResponse{
|
||||
Ready: true,
|
||||
Protocol: Protocol,
|
||||
ServerTimeUnixNano: time.Now().UnixNano(),
|
||||
Message: "Hello from IOP Control Plane",
|
||||
}, nil
|
||||
})
|
||||
```
|
||||
|
||||
After service-only boundary:
|
||||
|
||||
```go
|
||||
import (
|
||||
"context"
|
||||
|
||||
"iop/apps/control-plane/internal/credentialstore"
|
||||
)
|
||||
|
||||
type SecretSealer interface {
|
||||
Seal(context.Context, []byte, SecretContext) (credentialstore.EncryptedSecretEnvelope, error)
|
||||
}
|
||||
func (s *Service) RotateSlot(ctx context.Context, rawIOPToken []byte, input RotateSlotInput) (SlotRecord, error)
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/internal/credentialops/service.go`: implement authentication, ownership, CAS, lifecycle mapping, secret-blind DTOs, and sealer interface.
|
||||
- [ ] `apps/control-plane/internal/credentialops/service_test.go`: cover own/cross-principal operations, stale revision, lifecycle, missing sealer, fake-sealer ciphertext handoff, and captured-log redaction.
|
||||
|
||||
**Test Strategy:** Write `TestServiceScopesOperationsToAuthenticatedPrincipal`, `TestServiceRejectsCrossPrincipalTargets`, `TestServiceRejectsStaleRevision`, `TestServiceRequiresSealerForSecretMutation`, and `TestServiceResponsesAreSecretBlind` using temp store and deterministic fake sealer.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialops` passes.
|
||||
|
||||
### [API-2] Add host-local first-principal bootstrap
|
||||
|
||||
**Problem:** `apps/control-plane/cmd/control-plane/main.go:59-62` registers only `serve`, leaving no deployment-admin bootstrap path.
|
||||
|
||||
**Solution:** Add `principal bootstrap --alias` that loads config, opens the DB directly, refuses when any principal already exists, issues the first token through packet-01 store, and writes the raw token once to command stdout without zap/error inclusion. No remote operation is added.
|
||||
|
||||
Before (`apps/control-plane/cmd/control-plane/main.go:59-62`):
|
||||
|
||||
```go
|
||||
}
|
||||
root.PersistentFlags().StringVarP(&cfgFile, "config", "c", "configs/control-plane.yaml", "config file path")
|
||||
root.AddCommand(serveCmd())
|
||||
return root
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
root.PersistentFlags().StringVarP(&cfgFile, "config", "c", "configs/control-plane.yaml", "config file path")
|
||||
root.AddCommand(serveCmd(), principalCmd())
|
||||
return root
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/cmd/control-plane/main.go`: register the host-local principal command.
|
||||
- [ ] `apps/control-plane/cmd/control-plane/credential_commands.go`: implement direct DB bootstrap with explicit stdout/stderr writers.
|
||||
- [ ] `apps/control-plane/cmd/control-plane/credential_commands_test.go`: test first bootstrap, second refusal, restart, one-time stdout, and log/error redaction.
|
||||
|
||||
**Test Strategy:** Write `TestPrincipalBootstrapEmitsTokenOnce`, `TestPrincipalBootstrapRefusesExistingPrincipal`, `TestPrincipalBootstrapPersistsAcrossReopen`, and `TestBootstrapLogsNeverContainToken`.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/cmd/control-plane -run 'PrincipalBootstrap|Credential'` passes.
|
||||
|
||||
### [API-3] Guard the secure-transport boundary
|
||||
|
||||
**Problem:** a future implementer could expose the service on current plaintext ClientServer and accidentally create the SDD-forbidden credential path.
|
||||
|
||||
**Solution:** Add source-level absence tests that fail if bootstrap/credential protobuf messages or ClientServer credential listeners appear in this packet. Document the deferred adapter in service comments and tests, not the active wire contract.
|
||||
|
||||
Before (`proto/iop/control.proto`, current client baseline):
|
||||
|
||||
```protobuf
|
||||
message ClientHelloRequest {
|
||||
string client_id = 1;
|
||||
string client_version = 2;
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```text
|
||||
unchanged: no BootstrapRequest, CredentialOperationRequest, or raw token field
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/internal/credentialops/service_test.go`: assert the service has no network registration side effect and missing sealer fails closed.
|
||||
- [ ] `apps/control-plane/cmd/control-plane/credential_commands_test.go`: inspect root commands and prove bootstrap is CLI-only.
|
||||
|
||||
**Test Strategy:** Use deterministic `rg` final verification plus Go tests; no wire/Dart tests are changed because their contract remains untouched.
|
||||
|
||||
**Verification:** `if rg --sort path -n 'message[[:space:]]+.*(Bootstrap|CredentialOperation)|AddRequestListenerTyped.*Credential' proto/iop/control.proto apps/control-plane/internal/wire/client.go; then exit 1; else exit 0; fi` passes with no matches.
|
||||
|
||||
## Modified Files Summary
|
||||
|
||||
| File | Items |
|
||||
|------|-------|
|
||||
| `apps/control-plane/internal/credentialops/service.go` | API-1 |
|
||||
| `apps/control-plane/internal/credentialops/service_test.go` | API-1, API-3 |
|
||||
| `apps/control-plane/cmd/control-plane/main.go` | API-2 |
|
||||
| `apps/control-plane/cmd/control-plane/credential_commands.go` | API-2 |
|
||||
| `apps/control-plane/cmd/control-plane/credential_commands_test.go` | API-2, API-3 |
|
||||
| `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_ops/CODE_REVIEW-cloud-G08.md` | API-1, API-2, API-3 |
|
||||
|
||||
## Final Verification
|
||||
|
||||
Run from `/config/workspace/iop-s0`; fresh Go results are required.
|
||||
|
||||
1. `test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/01_principal_store/complete.log' -print | wc -l)" -eq 1 && test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log' -print | wc -l)" -eq 1` — both archived predecessors exist exactly once.
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache` — executable Go paths exist.
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/internal/credentialops ./apps/control-plane/cmd/control-plane` — no vet findings.
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 -race ./apps/control-plane/internal/credentialops ./apps/control-plane/cmd/control-plane` — service/CLI tests and races pass.
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/...` — Control Plane regression suite passes.
|
||||
6. `if rg --sort path -n 'message[[:space:]]+.*(Bootstrap|CredentialOperation)|AddRequestListenerTyped.*Credential' proto/iop/control.proto apps/control-plane/internal/wire/client.go; then exit 1; else exit 0; fi` — no plaintext management wire was added.
|
||||
7. `git diff --check` — no whitespace errors.
|
||||
|
||||
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/06+01,02_host_local_bootstrap plan=0 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `Implementation Checklist`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
|
||||
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
|
||||
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## Overview
|
||||
|
||||
date=2026-08-01
|
||||
task=m-principal-provider-credential-slot-routing/06+01,02_host_local_bootstrap, plan=0, tag=API
|
||||
|
||||
## Archive Evidence Snapshot
|
||||
|
||||
- Parent pair: `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_core/plan_local_G07_1.log`, `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_core/code_review_cloud_G08_1.log`.
|
||||
- Verdict/evidence: implementation had not started; no verdict or verification output.
|
||||
- Refinement reason: host-local bootstrap is independently implementable and verifiable from the in-process management service retained in sibling `05+01,02_management_core`.
|
||||
- Roadmap carryover: this remains preparatory. PASS must not check `credential-management`; S08 still requires S09 at-rest sealer and S10 confidential Client transport rollout.
|
||||
|
||||
## For the Review Agent
|
||||
|
||||
> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.
|
||||
|
||||
Compare implementation of each item against source files and verify that output in `Verification Results` matches code.
|
||||
Review completion means the following steps are finished:
|
||||
|
||||
1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
|
||||
2. Archive `CODE_REVIEW-cloud-G06.md` → `code_review_cloud_G06_0.log` and `PLAN-local-G06.md` → `plan_local_G06_0.log`.
|
||||
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/06+01,02_host_local_bootstrap/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
|
||||
4. If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata. Roadmap state check and `update-roadmap` calls are runtime responsibilities.
|
||||
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Item Completion
|
||||
|
||||
| Item | Status |
|
||||
|------|---------|
|
||||
| API-2 Add host-local first-principal bootstrap | [ ] |
|
||||
| API-3 Guard the secure-transport boundary | [ ] |
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Add a host-local-only first-principal/bootstrap CLI that emits the initial raw IOP token exactly once and never logs or persists it.
|
||||
- [ ] Prove first bootstrap, existing-principal refusal, one-time output, restart persistence, and raw-token redaction with deterministic tests.
|
||||
- [ ] Prove no management/bootstrap protobuf, ClientServer listener, Dart method, or plaintext network activation was added; leave S08 completion for S09/S10 rollout.
|
||||
- [ ] Run fresh CLI, Control Plane regression, archive-predecessor, structural absence, vet, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
## Review-Only Checklist
|
||||
|
||||
> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
|
||||
> Implementing agents must not modify or check this section.
|
||||
|
||||
- [ ] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
|
||||
- [ ] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
|
||||
- [ ] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G06_0.log`.
|
||||
- [ ] Archive active `PLAN-*-G??.md` to `plan_local_G06_0.log`.
|
||||
- [ ] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
|
||||
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
|
||||
- [ ] If PASS, move active task directory `agent-task/m-principal-provider-credential-slot-routing/06+01,02_host_local_bootstrap/` to `agent-task/archive/YYYY/MM/m-principal-provider-credential-slot-routing/06+01,02_host_local_bootstrap/` and update this checklist at the final archive path.
|
||||
- [ ] If PASS and task group is `m-principal-provider-credential-slot-routing`, report completion event metadata for runtime, without modifying roadmap or directly calling `update-roadmap`.
|
||||
- [ ] If PASS for split work, remove empty active parent `agent-task/m-principal-provider-credential-slot-routing/` or verify it was kept due to remaining siblings/files.
|
||||
- [ ] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
_Record any deviations from the plan and the rationale here._
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
_Record key design decisions here._
|
||||
|
||||
## Reviewer Checkpoints
|
||||
|
||||
- Confirm no Roadmap Targets section exists and PASS cannot check S08.
|
||||
- Verify bootstrap is a direct host-local CLI, refuses an existing principal, and emits the raw token exactly once.
|
||||
- Confirm raw token bytes are absent from logs, errors, and persisted reversible fields.
|
||||
- Prove proto, ClientServer, Dart client/parser, and server wiring remain unchanged.
|
||||
- Confirm no concrete key loading/encryption or network credential transport entered this packet.
|
||||
|
||||
## Verification Results
|
||||
|
||||
Paste actual stdout/stderr beneath every command; do not summarize or reconstruct it.
|
||||
|
||||
### API-2
|
||||
|
||||
`GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/cmd/control-plane -run 'PrincipalBootstrap|Credential'`
|
||||
|
||||
_Pending._
|
||||
|
||||
### API-3
|
||||
|
||||
`if rg --sort path -n 'message[[:space:]]+.*(Bootstrap|CredentialOperation)|AddRequestListenerTyped.*Credential' proto/iop/control.proto apps/control-plane/internal/wire/client.go; then exit 1; else exit 0; fi`
|
||||
|
||||
_Pending._
|
||||
|
||||
### Final Verification
|
||||
|
||||
1. `test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/01_principal_store/complete.log' -print | wc -l)" -eq 1 && test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log' -print | wc -l)" -eq 1`
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache`
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/cmd/control-plane`
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/cmd/control-plane -run 'PrincipalBootstrap|Credential'`
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/...`
|
||||
6. `if rg --sort path -n 'message[[:space:]]+.*(Bootstrap|CredentialOperation)|AddRequestListenerTyped.*Credential' proto/iop/control.proto apps/control-plane/internal/wire/client.go; then exit 1; else exit 0; fi`
|
||||
7. `git diff --check`
|
||||
|
||||
_Pending actual output for each command._
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Section Ownership
|
||||
|
||||
| Section | Owner | Note |
|
||||
|---------|-------|------|
|
||||
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
|
||||
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
|
||||
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
|
||||
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
|
||||
| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section |
|
||||
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholder text with actual content |
|
||||
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
|
||||
| Verification Results (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `Deviations from Plan` entry |
|
||||
| Code Review Result | Review agent appends | Not included in stub |
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
<!-- task=m-principal-provider-credential-slot-routing/06+01,02_host_local_bootstrap plan=0 tag=API -->
|
||||
|
||||
# Host-Local Principal Bootstrap
|
||||
|
||||
## For the Implementing Agent
|
||||
|
||||
Filling implementation-owned sections of `CODE_REVIEW-cloud-G06.md` is mandatory. Run every verification command, paste actual output, keep active files in place, and report ready for review; only code-review may finalize, rename logs, write `complete.log`, or archive. If blocked, record exact blocker evidence and resume conditions only in implementation-owned fields; do not ask the user, call user-input tools, create stop files, or classify next state.
|
||||
|
||||
## Background
|
||||
|
||||
Control Plane needs a host-local way to create the first principal and return its raw IOP token once. The command must open the configured store directly and must not add any credential-bearing operation to the current plaintext Client WebSocket.
|
||||
|
||||
## Archive Evidence Snapshot
|
||||
|
||||
- Parent pair: `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_core/plan_local_G07_1.log`, `agent-task/m-principal-provider-credential-slot-routing/05+01,02_management_core/code_review_cloud_G08_1.log`.
|
||||
- Verdict/evidence: implementation had not started; no verdict or verification output.
|
||||
- Refinement reason: host-local bootstrap is independently implementable and verifiable from the in-process management service retained in sibling `05+01,02_management_core`.
|
||||
- Roadmap carryover: this remains preparatory. PASS must not check `credential-management`; S08 still requires S09 at-rest sealer and S10 confidential Client transport rollout.
|
||||
|
||||
## Analysis
|
||||
|
||||
### Files Read
|
||||
|
||||
- `apps/control-plane/cmd/control-plane/main.go`
|
||||
- `apps/control-plane/cmd/control-plane/server.go`
|
||||
- `apps/control-plane/cmd/control-plane/config_test.go`
|
||||
- `apps/control-plane/internal/wire/client.go`
|
||||
- `apps/control-plane/internal/wire/client_test.go`
|
||||
- `proto/iop/control.proto`
|
||||
- `apps/client/lib/iop_wire/client_wire_client.dart`
|
||||
- `apps/client/lib/iop_wire/parser_map.dart`
|
||||
- `agent-contract/inner/client-control-plane-wire.md`
|
||||
|
||||
### SDD Criteria
|
||||
|
||||
- SDD: `agent-roadmap/sdd/operational-observability-provider-management/principal-provider-credential-slot-routing/SDD.md` (`승인`, 잠금 해제).
|
||||
- This foundation maps to S08 / `credential-management`, but the confidential remote operation required for full S08 remains excluded.
|
||||
- Checklist covers host-local-only bootstrap, one-time raw token output, restart persistence, redaction, and structural proof that no bootstrap/management protobuf or ClientServer listener is introduced.
|
||||
|
||||
### Verification Context
|
||||
|
||||
- No handoff exists. The SDD forbids credential-bearing operations on the current plain WebSocket.
|
||||
- Go 1.26.2 linux/arm64. Tests use temp SQLite, injected logger/random reader, explicit stdout/stderr writers, and workspace-local Go temp/cache paths.
|
||||
- Preconditions 01 and 02 are incomplete; runtime resolves their archived completion artifacts before implementation.
|
||||
- No external key manager, TLS runner, Flutter change, or provider is required. Confidence: high.
|
||||
|
||||
### Test Coverage Gaps
|
||||
|
||||
- CLI exposes only `serve`; no first-principal bootstrap, idempotency, restart, or one-time-output test exists.
|
||||
- No structural test protects the intentional absence of credential-bearing ClientServer listeners and protobuf messages.
|
||||
|
||||
### Symbol References
|
||||
|
||||
- Rename/remove 없음.
|
||||
- `rootCmd` adds commands at `main.go:61`; `serveCmd` remains unchanged.
|
||||
- `wire.NewClientServer` and Dart client/parser call sites remain unchanged.
|
||||
|
||||
### Split Judgment
|
||||
|
||||
- This child owns the direct host-local command, one-time stdout contract, and plaintext-network absence guard.
|
||||
- Sibling `05+01,02_management_core` independently owns the reusable authorization/CAS service.
|
||||
- The bootstrap command and its CLI-only/absence tests stay together because those tests define its safe exposure boundary.
|
||||
|
||||
### Scope Rationale
|
||||
|
||||
- Do not modify `proto/iop/control.proto`, ClientServer, Dart client/parser, configs, or Control Plane server wiring.
|
||||
- Do not depend on sibling 05; open the packet-01 store directly.
|
||||
- Do not add remote bootstrap, credential reveal, concrete sealer/key loading, or plaintext network payloads.
|
||||
|
||||
### Final Routing
|
||||
|
||||
- `status=routed`, `evaluation_mode=isolated-reassessment`, finalizer=`finalize-task-policy.sh`, pair mode.
|
||||
- Build closures all true; scores `1+1+2+1+1=G06`; base/route=`local-fit`, lane=`local`, canonical=`PLAN-local-G06.md`.
|
||||
- Review closures all true; scores `1+1+2+1+1=G06`; route=`official-review`, lane=`cloud`, model=`gpt-5.6-sol` xhigh, canonical=`CODE_REVIEW-cloud-G06.md`.
|
||||
- `large_indivisible_context=false`; risks=`temporal_state`, `boundary_contract` (2); rework=0; integrity failure=false; capability gap 없음.
|
||||
|
||||
## Dependencies and Execution Order
|
||||
|
||||
1. `01_principal_store` and `02+01_credential_catalog` must complete; runtime accepts their archived `complete.log` files.
|
||||
2. Implement direct DB bootstrap, then one-time output/restart tests and structural absence guards.
|
||||
3. No dependency absent from `06+01,02_host_local_bootstrap` may be introduced.
|
||||
|
||||
## Implementation Checklist
|
||||
|
||||
- [ ] Add a host-local-only first-principal/bootstrap CLI that emits the initial raw IOP token exactly once and never logs or persists it.
|
||||
- [ ] Prove first bootstrap, existing-principal refusal, one-time output, restart persistence, and raw-token redaction with deterministic tests.
|
||||
- [ ] Prove no management/bootstrap protobuf, ClientServer listener, Dart method, or plaintext network activation was added; leave S08 completion for S09/S10 rollout.
|
||||
- [ ] Run fresh CLI, Control Plane regression, archive-predecessor, structural absence, vet, and diff verification.
|
||||
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.
|
||||
|
||||
### [API-2] Add host-local first-principal bootstrap
|
||||
|
||||
**Problem:** `apps/control-plane/cmd/control-plane/main.go:59-62` registers only `serve`, leaving no deployment-admin bootstrap path.
|
||||
|
||||
**Solution:** Add `principal bootstrap --alias` that loads config, opens the DB directly, refuses when any principal already exists, issues the first token through packet-01 store, and writes the raw token once to command stdout without zap/error inclusion. No remote operation is added.
|
||||
|
||||
Before (`apps/control-plane/cmd/control-plane/main.go:59-62`):
|
||||
|
||||
```go
|
||||
root.PersistentFlags().StringVarP(&cfgFile, "config", "c", "configs/control-plane.yaml", "config file path")
|
||||
root.AddCommand(serveCmd())
|
||||
return root
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```go
|
||||
root.PersistentFlags().StringVarP(&cfgFile, "config", "c", "configs/control-plane.yaml", "config file path")
|
||||
root.AddCommand(serveCmd(), principalCmd())
|
||||
return root
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/cmd/control-plane/main.go`: register the host-local principal command.
|
||||
- [ ] `apps/control-plane/cmd/control-plane/credential_commands.go`: implement direct DB bootstrap with explicit stdout/stderr writers.
|
||||
- [ ] `apps/control-plane/cmd/control-plane/credential_commands_test.go`: test first bootstrap, second refusal, restart, one-time stdout, log/error redaction, and CLI-only exposure.
|
||||
|
||||
**Test Strategy:** Write `TestPrincipalBootstrapEmitsTokenOnce`, `TestPrincipalBootstrapRefusesExistingPrincipal`, `TestPrincipalBootstrapPersistsAcrossReopen`, and `TestBootstrapLogsNeverContainToken`.
|
||||
|
||||
**Verification:** `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/cmd/control-plane -run 'PrincipalBootstrap|Credential'` passes.
|
||||
|
||||
### [API-3] Guard the secure-transport boundary
|
||||
|
||||
**Problem:** a future implementer could expose bootstrap on the current plaintext ClientServer and create the SDD-forbidden credential path.
|
||||
|
||||
**Solution:** Keep bootstrap registered only as a host-local Cobra command. Add source-level absence tests that fail if bootstrap/credential protobuf messages or ClientServer credential listeners appear.
|
||||
|
||||
Before (`proto/iop/control.proto`, current client baseline):
|
||||
|
||||
```protobuf
|
||||
message ClientHelloRequest {
|
||||
string client_id = 1;
|
||||
string client_version = 2;
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```text
|
||||
unchanged: no BootstrapRequest, CredentialOperationRequest, or raw token field
|
||||
```
|
||||
|
||||
**Modified Files and Checklist:**
|
||||
|
||||
- [ ] `apps/control-plane/cmd/control-plane/credential_commands_test.go`: inspect root commands and prove bootstrap is CLI-only.
|
||||
|
||||
**Test Strategy:** Use deterministic `rg` final verification plus command tests; no wire/Dart test changes are required because their contract remains untouched.
|
||||
|
||||
**Verification:** `if rg --sort path -n 'message[[:space:]]+.*(Bootstrap|CredentialOperation)|AddRequestListenerTyped.*Credential' proto/iop/control.proto apps/control-plane/internal/wire/client.go; then exit 1; else exit 0; fi` passes with no matches.
|
||||
|
||||
## Modified Files Summary
|
||||
|
||||
| File | Items |
|
||||
|------|-------|
|
||||
| `apps/control-plane/cmd/control-plane/main.go` | API-2 |
|
||||
| `apps/control-plane/cmd/control-plane/credential_commands.go` | API-2 |
|
||||
| `apps/control-plane/cmd/control-plane/credential_commands_test.go` | API-2, API-3 |
|
||||
| `agent-task/m-principal-provider-credential-slot-routing/06+01,02_host_local_bootstrap/CODE_REVIEW-cloud-G06.md` | API-2, API-3 |
|
||||
|
||||
## Final Verification
|
||||
|
||||
Run from `/config/workspace/iop-s0`; fresh Go results are required.
|
||||
|
||||
1. `test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/01_principal_store/complete.log' -print | wc -l)" -eq 1 && test "$(find agent-task/archive -type f -path '*/m-principal-provider-credential-slot-routing/02+01_credential_catalog/complete.log' -print | wc -l)" -eq 1` — both archived predecessors exist exactly once.
|
||||
2. `mkdir -p .cache/go-build .cache/go-cache` — executable Go paths exist.
|
||||
3. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go vet ./apps/control-plane/cmd/control-plane` — no vet findings.
|
||||
4. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/cmd/control-plane -run 'PrincipalBootstrap|Credential'` — focused bootstrap tests pass.
|
||||
5. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/control-plane/...` — Control Plane regression suite passes.
|
||||
6. `if rg --sort path -n 'message[[:space:]]+.*(Bootstrap|CredentialOperation)|AddRequestListenerTyped.*Credential' proto/iop/control.proto apps/control-plane/internal/wire/client.go; then exit 1; else exit 0; fi` — no plaintext management wire was added.
|
||||
7. `git diff --check` — no whitespace errors.
|
||||
|
||||
After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
|
||||
|
|
@ -61,6 +61,7 @@ func TestAnthropicPrincipalBearerAndAPIKey(t *testing.T) {
|
|||
{"Authorization": "Bearer " + raw, "X-Api-Key": raw},
|
||||
} {
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
|
||||
req.Header.Set(anthropicVersionHeader, anthropicSupportedVersion)
|
||||
for key, value := range headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,9 +110,11 @@ func isAnthropicRequest(r *http.Request) bool {
|
|||
return false
|
||||
}
|
||||
path := r.URL.Path
|
||||
return path == "/v1/messages" || path == "/v1/messages/count_tokens" ||
|
||||
strings.HasPrefix(path, "/anthropic/v1/") ||
|
||||
strings.TrimSpace(r.Header.Get(anthropicVersionHeader)) != ""
|
||||
if path == "/v1/messages" || path == "/v1/messages/count_tokens" ||
|
||||
strings.HasPrefix(path, "/anthropic/v1/") {
|
||||
return true
|
||||
}
|
||||
return path == "/v1/models" && strings.TrimSpace(r.Header.Get(anthropicVersionHeader)) != ""
|
||||
}
|
||||
|
||||
func writeAnthropicError(w http.ResponseWriter, status int, errorType, message string) {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,47 @@ func TestRoutesRequireBearerTokenWhenConfigured(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOpenAIRoutesDoNotAcceptAnthropicAPIKey(t *testing.T) {
|
||||
srv := NewServer(config.EdgeOpenAIConf{BearerToken: "secret-token"}, &fakeRunService{}, nil)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
method string
|
||||
path string
|
||||
headers map[string]string
|
||||
}{
|
||||
{
|
||||
name: "models without Anthropic version",
|
||||
method: http.MethodGet,
|
||||
path: "/v1/models",
|
||||
headers: map[string]string{"X-Api-Key": "secret-token"},
|
||||
},
|
||||
{
|
||||
name: "chat with unrelated Anthropic version",
|
||||
method: http.MethodPost,
|
||||
path: "/v1/chat/completions",
|
||||
headers: map[string]string{
|
||||
"X-Api-Key": "secret-token",
|
||||
anthropicVersionHeader: anthropicSupportedVersion,
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(tc.method, tc.path, nil)
|
||||
for key, value := range tc.headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
srv.routes().ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealthzDoesNotRequireBearerToken(t *testing.T) {
|
||||
srv := NewServer(config.EdgeOpenAIConf{BearerToken: "secret-token"}, &fakeRunService{}, nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
|
||||
|
|
|
|||
|
|
@ -141,7 +141,10 @@ func matchPrincipalToken(tokens []config.OpenAIPrincipalTokenConf, token string)
|
|||
func principalFromRequest(r *http.Request, cfg config.EdgeOpenAIConf) (openAIPrincipal, bool) {
|
||||
authorization := strings.TrimSpace(r.Header.Get("Authorization"))
|
||||
bearer := bearerTokenFromHeader(authorization)
|
||||
apiKey := strings.TrimSpace(r.Header.Get("X-Api-Key"))
|
||||
apiKey := ""
|
||||
if isAnthropicRequest(r) {
|
||||
apiKey = strings.TrimSpace(r.Header.Get("X-Api-Key"))
|
||||
}
|
||||
if authorization != "" && bearer == "" {
|
||||
return openAIPrincipal{}, false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -336,9 +336,13 @@ func validateProfileURL(baseURL, opPath string) error {
|
|||
return fmt.Errorf("profile operation path must not be empty")
|
||||
}
|
||||
if strings.HasPrefix(trimmed, "http://") || strings.HasPrefix(trimmed, "https://") {
|
||||
if _, err := url.Parse(trimmed); err != nil {
|
||||
parsed, err := url.Parse(trimmed)
|
||||
if err != nil {
|
||||
return fmt.Errorf("profile operation path %q is not a valid absolute URL: %w", trimmed, err)
|
||||
}
|
||||
if (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Host == "" {
|
||||
return fmt.Errorf("profile operation path %q must be an absolute http(s) URL", trimmed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !strings.HasPrefix(trimmed, "/") {
|
||||
|
|
|
|||
|
|
@ -159,6 +159,22 @@ func TestProtocolProfileOverlayRejected(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("absolute_url_without_host", func(t *testing.T) {
|
||||
catalog := map[string]config.ProtocolProfileConf{
|
||||
"bad": {
|
||||
Driver: config.ProtocolDriverOpenAIChat,
|
||||
BaseURL: "https://a/v1",
|
||||
Operations: map[string]string{"chat_completions": "https:///v1/chat/completions"},
|
||||
Auth: config.ProtocolAuthConf{Header: "Authorization", Scheme: "Bearer"},
|
||||
Capabilities: []string{"chat"},
|
||||
},
|
||||
}
|
||||
_, err := config.ResolveProtocolProfile("bad", "", catalog)
|
||||
if err == nil || !strings.Contains(err.Error(), "absolute http(s) URL") {
|
||||
t.Fatalf("expected absolute URL host validation error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("relative_path_no_leading_slash", func(t *testing.T) {
|
||||
catalog := map[string]config.ProtocolProfileConf{
|
||||
"bad": {Driver: config.ProtocolDriverOpenAIChat, BaseURL: "https://a/v1", Operations: map[string]string{"models": "models"}, Capabilities: []string{"chat"}},
|
||||
|
|
|
|||
Loading…
Reference in a new issue