<!-- 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 | [x] |
| API-2 Fix managed requests to one projected route | [x] |
| API-3 Keep gated contracts honest | [x] |

## Implementation Checklist

- [x] 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.
- [x] 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.
- [x] Preserve unmanaged global/legacy behavior and prove managed unknown/inactive/cross-principal routes never fall back or dispatch.
- [x] Keep contracts explicitly gated and run fresh focused/full Edge, dormant-activation, vet, and diff verification.
- [x] 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.

- [x] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
- [x] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
- [x] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G08_1.log`.
- [x] Archive active `PLAN-*-G??.md` to `plan_local_G07_1.log`.
- [x] 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.
- [x] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.

## Deviations from Plan

None.

## Key Design Decisions

- Extended `authprojection.Reader` interface with `RoutesForPrincipal(principalRef string) ([]Route, State)` to allow lock-free route snapshot lookups by caller principal reference.
- Implemented `advertisedModelsForPrincipal` and `resolveRouteDispatchForPrincipal` in `apps/edge/internal/openai/principal_routes.go` to isolate model discovery and candidate selection per principal when in managed mode (`State() != StateUnmanaged`).
- Added candidate predicate composition helper `composeCandidatePredicates` to ensure managed route predicates (matching `CredentialSlotRef`, `ProfileID`, and `UpstreamModel`) compose with handler stream/profile predicates and survive recovery re-resolution.
- Enforced strict fail-closed behavior: in managed mode, unknown, inactive, or cross-principal route requests fail immediately without falling back to global catalog, `model_routes`, or single-target defaults.
- Preserved legacy/unmanaged mode behavior entirely unchanged.

## 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'`

```
ok  	iop/apps/edge/internal/openai	0.044s
```

### 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'`

```
ok  	iop/apps/edge/internal/openai	0.062s
```

### 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`

```
agent-contract/outer/openai-compatible-api.md
48:### Reserved managed projection auth
51:The cache starts in `unmanaged` state, so current production behavior continues
55:Only an explicitly installed, verified, fresh snapshot changes the cache to
56:managed state (`verified managed projection only`). In managed state, OpenAI-compatible
59:fallbacks. Unknown or removed digests, malformed headers, and expired snapshots
60:return `401 unauthorized` before model lookup or dispatch. Once managed, expiry
61:does not revert the cache to legacy/unmanaged behavior.
63:When managed mode is active, model discovery (`GET /v1/models`) lists only active
64:projected `route_id`s for the authenticated principal. Request model resolution binds
65:the request strictly to one projected route's `slot_id`, `profile_id`, and `upstream_model`.
81:### Legacy fallback
83:`openai.principal_tokens[]`가 설정되어 있더라도, raw token이 어떤 `principal_tokens` entry에도 매칭되지 않으면 `openai.bearer_token`이 설정된 경우 legacy 단일 bearer auth가 unmapped fallback으로 동작한다. `openai.bearer_token`과 `openai.principal_tokens[]`가 모두 설정된 경우, principal token 매칭이 실패하면 legacy fallback을 시도하고, 그래도 실패하면 `401 unauthorized`를 반환한다.
210:- provider-pool model group route(`models[]`)의 `/v1/responses` 호출은 selected provider가 the Responses operation and capability를 선언한 tunnel candidate이면 raw passthrough로 provider `POST /v1/responses`에 전달한다. This admission is not exclusive to the `openai_responses` driver. caller body는 `model` field만 served target으로 rewrite하고, selected provider가 지원하는 OpenAI-compatible 표준 field와 provider extension field(`max_output_tokens`, `tools`, `store`, provider-specific knobs 등)는 보존한다. `stream:true`는 provider raw SSE로 relay한다. provider auth forwarding이 적용되고, response model echo rewrite는 적용하지 않는다. 이 경로는 normalized `SubmitRun`으로 fallback하지 않는다.
211:- provider-pool model group route는 provider candidate를 먼저 선택한다. 선택된 provider가 OpenAI-compatible 호출 방식을 지원하면 `ProviderTunnelRequest` passthrough를 사용하고, Ollama/CLI/native provider이면 normalized `RunRequest`를 사용한다. provider type만으로 Ollama를 candidate set에서 제거하지 않으며, OpenAI-compatible provider의 tunnel 구현이 없으면 normalized fallback이 아니라 unsupported/implementation error다.
240:존재하지 않는 경로, 권한 오류, agent process exit failure는 기본 cwd fallback으로 숨기지 않고 호출자가 실패로 구분할 수 있어야 한다.
326:- `include_reasoning` 생략 또는 `true`인 요청은 기존 reasoning 본문을 `reasoning_content`에 유지하고, `content`가 비어 있으면 reasoning 본문을 fallback content로도 반환한다. `finish_reason`이 `stop`이 아니면 fallback content 뒤에 IOP notice를 붙인다.
328:- streaming 응답도 같은 정책을 따른다. reasoning-only 완료 시 최종 finish chunk와 `[DONE]` 전에 fallback 또는 hidden-reasoning notice를 `content` delta로 한 번 전송한다.
329:- Chat Completions provider-pool pure `passthrough` 응답 body에는 이 normalized fallback/filtering 정책을 적용하지 않는다.
372:`tools`가 있는 Chat Completions 요청에서 provider route(`openai_compat`, `vllm`, `ollama`, provider pool)는 forced tool 선택 객체와 `"none"` 같은 명시적 `tool_choice`를 backend에 전달한다. 단, `"auto"`는 OpenAI-compatible 기본값과 같으므로 provider request에서는 생략한다. 일부 vLLM 계열 backend는 explicit/default `"auto"`를 `--enable-auto-tool-choice`/`--tool-call-parser` 없이 400으로 거부한다. 이 400이 발생하고 요청 tool이 정확히 1개이면 Node adapter는 해당 tool에 대한 forced `tool_choice`로 1회 재시도한다. forced tool도 `--tool-call-parser` 요구로 거부되거나 여러 tool이라 forced를 고를 수 없으면, Node adapter는 `tools`/`tool_choice`를 제거하고 text tool-call system instruction을 leading system message에 병합해 1회 재시도하며 완료 metadata에 `openai_text_tool_fallback: "true"`를 싣는다.
396:매칭 route가 없으면 기존 fallback 규칙에 따라 `openai.target` 또는 요청의 `model`을 내부 target으로 사용한다.

agent-contract/outer/anthropic-compatible-api.md
43:When `openai.principal_tokens[]` is configured, either supported caller-auth form is hashed and matched against `token_hash_sha256`. A match supplies `iop_principal_ref`, `iop_principal_alias`, `iop_token_ref`, and `iop_principal_source` to internal dispatch metadata; no match returns `401 authentication_error` unless the legacy fallback applies.
45:### Reserved managed projection auth
49:`unmanaged`; therefore the static principal mapping and legacy fallback remain
52:When a trusted in-process caller explicitly installs a verified fresh snapshot,
53:Anthropic-compatible routes enter managed mode (`verified managed projection only`).
60:In managed mode, model discovery (`GET /anthropic/v1/models` and `GET /v1/models`
61:with anthropic-version) lists only active projected `route_id`s for the authenticated
62:principal. Request model selection binds strictly to one projected route's `slot_id`,
68:so it cannot activate managed mode. Production activation is gated on the
72:### Legacy fallback
74:`openai.principal_tokens[]`가 설정되어 있더라도, raw token이 어떤 `principal_tokens` entry에도 매칭되지 않으면 `openai.bearer_token`이 설정된 경우 legacy 단일 bearer auth가 unmapped fallback으로 동작한다. `openai.bearer_token`과 `openai.principal_tokens[]`가 모두 설정된 경우, principal token 매칭이 실패하면 legacy fallback을 시도하고, 그래도 실패하면 `401 authentication_error`를 반환한다.
191:- `id`: provider 응답 ID 또는 `"msg_iop"` prefix fallback.
267:Messages requests require a `models[]` provider-pool route. A configured model-catalog TokenCounter returns a deterministic local count for count-tokens without provider selection. Only the native upstream count-tokens fallback requires an `anthropic_messages` provider-pool candidate. Legacy direct-route and single-target fallback are not admitted to this surface.
286:- `count_tokens` capability + `count_tokens` operation (count_tokens native fallback 요청인 경우; TokenCounter local count path는 provider selection 및 capability check가 필요 없다)
```

### 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'`

```
ok  	iop/apps/edge/internal/openai	0.044s
```

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`

```
ok  	iop/apps/edge/internal/authprojection	0.036s
ok  	iop/apps/edge/internal/openai	7.514s
ok  	iop/apps/edge/internal/service	5.907s
```

6. `GOTMPDIR=/config/workspace/iop-s0/.cache/go-build GOCACHE=/config/workspace/iop-s0/.cache/go-cache go test -count=1 ./apps/edge/...`

```
ok  	iop/apps/edge/cmd/edge	0.479s
ok  	iop/apps/edge/internal/authprojection	0.060s
ok  	iop/apps/edge/internal/bootstrap	4.407s
ok  	iop/apps/edge/internal/configrefresh	0.414s
ok  	iop/apps/edge/internal/controlplane	4.543s
ok  	iop/apps/edge/internal/edgecmd	0.254s
ok  	iop/apps/edge/internal/edgevalidate	0.094s
ok  	iop/apps/edge/internal/events	0.065s
ok  	iop/apps/edge/internal/input	0.117s
ok  	iop/apps/edge/internal/input/a2a	0.093s
ok  	iop/apps/edge/internal/node	0.090s
ok  	iop/apps/edge/internal/openai	7.595s
ok  	iop/apps/edge/internal/opsconsole	0.098s
ok  	iop/apps/edge/internal/service	5.969s
ok  	iop/apps/edge/internal/transport	4.840s
```

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`

```

```

---

> **[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 |

## Code Review Result

- Overall Verdict: FAIL
- Dimension Assessment:
  - Correctness: Fail — a projected credential-slot reference is used as both the provider ID and the provider-pool candidate predicate, while the projected resource selector is ignored and the public route ID is passed as the internal model-group key.
  - Completeness: Fail — the planned immutable authentication/route view and stable trusted route/slot revision metadata are absent.
  - Test Coverage: Fail — the managed tests exercise helpers and failure responses, but not successful provider-pool admission, metadata propagation, or actual recovery re-entry on every required surface.
  - API Contract: Fail — the outer contracts promise strict route-to-slot/profile/upstream binding, but the implementation cannot dispatch a normal catalog provider whose resource identity differs from its credential-slot identity.
  - Code Quality: Pass — fresh vet, formatting, race, package, full Edge, dormant-activation, and diff checks completed without errors, and no debug or dead-code residue was found in the reviewed packet.
  - Implementation Deviation: Fail — API-2 claims stable slot metadata and recovery-safe binding even though revisions are discarded and the verification never enters provider-pool recovery.
  - Verification Trust: Pass — the recorded commands are reproducible and their outputs match fresh reviewer runs; the defect is an evidence-coverage gap, not fabricated evidence.
- Findings:
  - Required — `apps/edge/internal/openai/principal_routes.go:118`: projected identity domains are conflated. `CredentialSlotRef` is assigned to `ProviderID` and compared with `ProviderPoolCandidate.ProviderID`, while `ResourceSelector` is never used; the Chat, Responses, and Anthropic builders then pass the caller-visible route ID as `ModelGroupKey` (`apps/edge/internal/openai/chat_handler.go:180`, `apps/edge/internal/openai/responses_handler.go:345`, `apps/edge/internal/openai/anthropic_handler.go:151`). The service requires that key to equal a static catalog entry ID (`apps/edge/internal/service/provider_resolution.go:404`), so a valid projected route whose ID differs from its catalog model group fails before admission, and a provider whose ID differs from the credential slot is rejected. Resolve the projection's resource selector/profile/upstream tuple to exactly one internal catalog model-group/provider resource, carry that internal key separately in `routeDispatch`, keep the credential slot solely as credential binding, and fail closed on zero or ambiguous catalog mappings.
  - Required — `apps/edge/internal/openai/route_resolution.go:53`: managed dispatch drops `RouteRevision`, `CredentialRevision`, and the resource selector, and `resolveCallerIdentity` overwrites only principal metadata (`apps/edge/internal/openai/dispatch_context.go:59`). Consequently no trusted route/slot revision metadata reaches Run or Tunnel dispatch, despite API-2 claiming it remains stable across initial and recovery attempts. Add immutable managed binding fields plus one metadata helper that overwrites caller-supplied managed keys, apply it to Chat, Responses, Anthropic Messages, and count-tokens Run/Tunnel requests, and assert that the identical trusted values survive retry/recovery construction.
  - Required — `apps/edge/internal/openai/principal.go:181`: authentication and route selection do not consume one immutable projection view. `authenticatePrincipal` authenticates through one `LookupDigest` load, but `resolveProjectedRoute` later performs independent `State` and `RoutesForPrincipal` loads (`apps/edge/internal/openai/principal_routes.go:81`), allowing an atomic generation swap to combine a principal authenticated under generation N with routes from generation N+1. Expose an authenticated secret-free view containing principal, routes, generation, and state from one installed snapshot, store that view in request context, and make model discovery/selection consume it; add a deterministic generation-swap test proving one request cannot mix snapshots.
  - Required — `apps/edge/internal/openai/principal_routes_test.go:19`: the fixture helper hard-codes `ResourceSelector: "default"`, the slot test creates `ProviderID: "slot-bound"` (`apps/edge/internal/openai/principal_routes_test.go:238`), the recovery test only calls a composed predicate once (`apps/edge/internal/openai/principal_routes_test.go:270`), and the surface table covers only unknown-route 400 responses (`apps/edge/internal/openai/principal_routes_test.go:337`). Replace these with success-path integration tests using distinct route ID, model-group key, provider ID, and credential-slot ID; have a recording fake service evaluate candidates in adverse order, record trusted metadata, and re-enter candidate selection during simulated recovery. Cover Chat Completions, Responses, Anthropic Messages, and count-tokens, while retaining fail-closed unknown/inactive/cross-principal cases.
- Finding Counts: Required=4, Suggested=0, Nit=0
- Routing Signals:
  - `review_rework_count=1`
  - `evidence_integrity_failure=false`
- Next Step: Create and implement a freshly routed follow-up plan that fixes every Required finding; do not write `complete.log`.
