<!-- task=m-multi-provider-protocol-profile-native-messages/03+02_responses_profile plan=3 tag=REVIEW_API -->

# Implement typed Responses operation rejection and complete verification evidence

## For the Implementing Agent

Filling the implementation-owned sections of `CODE_REVIEW-*-G??.md` is the mandatory final implementation step. Run every planned verification command, record actual notes and stdout/stderr, keep the active PLAN and CODE_REVIEW files in place, and report ready for review; only the code-review skill may append the verdict, archive logs, write `complete.log`, or move the task directory. If blocked, record only the exact blocker, attempted commands/output, and resume conditions in implementation-owned evidence fields; do not ask the user, call user-input tools, create control-plane stop files, classify the next state, archive logs, or write `complete.log`.

## Background

The latest official review found that the typed Responses operation rejection follow-up was not implemented: the service still collapses operation and request-local rejection, the public handler still emits the output-validation message, and the regression still expects the preparer-only 502. The active review artifact also contains no implementation notes or command output. This follow-up implements the unchanged service/HTTP boundary and replaces every placeholder with deterministic fresh evidence.

## Archive Evidence Snapshot

- Current loop archives after review finalization: `agent-task/m-multi-provider-protocol-profile-native-messages/03+02_responses_profile/plan_cloud_G07_02.log` and `agent-task/m-multi-provider-protocol-profile-native-messages/03+02_responses_profile/code_review_cloud_G07_02.log`.
- Verdict: FAIL. Required: 3; Suggested: 0; Nit: 0.
- Required finding 1: `SubmitProviderPool` still combines operation admission with request-local predicates and returns only `ErrProviderPoolCandidateRejected` during initial and live re-resolution.
- Required finding 2: `/v1/responses` still maps the generic sentinel to the output-validation message, the handler fake skips operation admission, and the unsupported regression still expects HTTP 502.
- Required finding 3: API-1 through API-3 remain unchecked and all eleven verification blocks still contain placeholders.
- The Messages predecessor remains satisfied by `agent-task/archive/2026/08/m-multi-provider-protocol-profile-native-messages/02+01_messages_ingress/complete.log`.
- Roadmap carryover: this split child supplies only the Responses profile/runtime portion of S08; Pi and contract/spec siblings remain responsible for the rest of `responses-regression`, so this plan intentionally has no `Roadmap Targets` section.

## Analysis

### Files Read

- `agent-task/m-multi-provider-protocol-profile-native-messages/03+02_responses_profile/PLAN-cloud-G07.md`
- `agent-task/m-multi-provider-protocol-profile-native-messages/03+02_responses_profile/CODE_REVIEW-cloud-G07.md`
- `agent-task/archive/2026/08/m-multi-provider-protocol-profile-native-messages/02+01_messages_ingress/complete.log`
- `agent-roadmap/phase/operational-observability-provider-management/PHASE.md`
- `agent-roadmap/phase/operational-observability-provider-management/milestones/multi-provider-protocol-profile-native-messages.md`
- `agent-roadmap/sdd/operational-observability-provider-management/multi-provider-protocol-profile-native-messages/SDD.md`
- `agent-contract/outer/openai-compatible-api.md`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `agent-spec/input/openai-compatible-surface.md`
- `agent-spec/runtime/provider-pool-config-refresh.md`
- `packages/go/config/provider_types.go`
- `apps/edge/internal/service/provider_pool.go`
- `apps/edge/internal/service/protocol_profile_test.go`
- `apps/edge/internal/openai/responses_handler.go`
- `apps/edge/internal/openai/provider_test_support_test.go`
- `apps/edge/internal/openai/responses_protocol_profile_test.go`
- `apps/node/internal/adapters/openai_compat/protocol_profile_test.go`
- `apps/node/internal/adapters/openai_compat/provider_tunnel_test.go`
- `scripts/e2e-openai-lemonade.sh`

### SDD Criteria

- SDD: `agent-roadmap/sdd/operational-observability-provider-management/multi-provider-protocol-profile-native-messages/SDD.md`; status `[승인됨]`, lock released, no pending user review.
- Targeted Acceptance Scenario: S08 / Milestone Task `responses-regression`. Existing normalized, passthrough, stream, and provider-auth Responses fixtures must preserve status/body/stream behavior when optional protocol profiles are present.
- Evidence Map S08 requires the existing Responses route/stream/provider-auth regression suite. This child adds only the operation-admission/public-error regression evidence and intentionally does not claim the full Roadmap Task.

### Verification Context

- No external handoff was supplied. Repository-native evidence came from the active pair, current source/tests, the approved SDD, matching contract/spec documents, local test rules, and the repository smoke helper.
- Local preflight on 2026-08-01 resolved Go to `/config/.local/bin/go`, `go version go1.26.2 linux/arm64`, and `GOROOT=/config/opt/go`.
- `test -f agent-task/archive/2026/08/m-multi-provider-protocol-profile-native-messages/02+01_messages_ingress/complete.log` passed.
- With `GOCACHE=/config/.cache/iop-responses-profile-g07` and `GOTMPDIR=/config/workspace/iop-s0/.local/gotmp-responses-profile-g07`, the current focused service and Responses commands execute successfully. Their current PASS only proves the pre-follow-up assertions run; it does not prove the missing typed error or 400 contract.
- `git diff --check` passed before planning. Fresh `-count=1` results after implementation are required; cached test output is not acceptable.
- External Verification Preflight: not applicable. The selected fake-mode smoke runs the real repository Edge/Node entrypoints with a loopback fake provider and requires no credential or external host.

### Test Coverage Gaps

- Initial all-unsupported operation admission has no service regression that asserts a typed error, operation value, generic-sentinel compatibility, and zero Node dispatch.
- Live re-resolution asserts only `ErrProviderPoolCandidateRejected`; it does not prove the operation-specific type or operation value.
- The OpenAI handler fake does not perform operation admission before request-local policy/preparation, and the unsupported Responses tests still assert a preparer-only 502 without the exact error envelope.
- Supported explicit profiles, nil-profile compatibility, body/status/stream preservation, provider auth, and Node operation-over-Path behavior already have regression coverage and must remain passing.

### Symbol References

- New symbol: `edgeservice.ProviderPoolOperationUnsupportedError`, to be referenced by `apps/edge/internal/service/provider_pool.go`, `apps/edge/internal/openai/responses_handler.go`, `apps/edge/internal/openai/provider_test_support_test.go`, and focused service/Responses tests.
- Existing `ErrProviderPoolCandidateRejected` remains the generic request-local sentinel and the unwrap target for terminal queue compatibility. No symbol is renamed.
- `composeProviderPoolCandidatePredicates` is local to `provider_pool.go`; remove it if ordered filtering leaves it unused.

### Split Judgment

- This remains one compact boundary: service-owned operation classification, the Responses HTTP mapping, and the faithful handler test double must change together for the public regression to exercise production behavior.
- Dependency index `02` is satisfied by `agent-task/archive/2026/08/m-multi-provider-protocol-profile-native-messages/02+01_messages_ingress/complete.log`.

### Scope Rationale

- Chat, Anthropic, and general stream-gate rendering are excluded because the typed error wraps the existing generic sentinel and only `/v1/responses` receives the new public message.
- Node request construction, profile schema/catalog, and protobuf are excluded because no wire field changes and existing Node operation/path tests cover compatibility.
- `agent-contract/**`, `agent-spec/**`, `agent-client/pi/**`, roadmap files, and central `agent-ops/**` are excluded because sibling tasks own contract/spec/Pi closure and this child cannot claim all of S08.
- No success-path body/status/stream/provider-auth behavior is redesigned.

### Final Routing

- `evaluation_mode=isolated-reassessment`; `finalizer=finalize-task-policy.sh`, mode `pair`.
- Build closures: scope, context, verification, evidence, ownership, and decision are closed; capability gap: none.
- Build grade scores: scope coupling=2, state/concurrency=1, blast/irreversibility=1, evidence/diagnosis=1, verification complexity=1; grade G06; base `local-fit`.
- `large_indivisible_context=false`; positive loop risks: `boundary_contract`, `temporal_state`; loop-risk count=2; `review_rework_count=2`; `evidence_integrity_failure=false`; recovery boundary matched, so build route is `recovery-boundary`, cloud.
- Review closures are closed with the same 2/1/1/1/1 scores; route `official-review`, cloud, G06, Codex `gpt-5.6-sol`, `xhigh`.
- Canonical files: `PLAN-cloud-G06.md` and `CODE_REVIEW-cloud-G06.md`.

## Dependencies and Execution Order

1. Keep the archived `02+01_messages_ingress` completion as the satisfied split dependency.
2. Implement and test ordered service admission before updating the handler mapping and fake.
3. Run focused tests first, then broad/full suites and the fake-mode Edge/Node/provider smoke.

## Implementation Checklist

- [ ] Add typed unsupported-operation admission before request-local filtering and preserve generic terminal compatibility across initial and live re-resolution.
- [ ] Map the typed Responses rejection to the sanitized 400 contract and make the handler fake/tests follow production admission ordering.
- [ ] Run every deterministic focused, broad, full Edge/Node, vet, smoke, and diff command and record actual output.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_API-1] Implement ordered typed operation admission

#### Problem

`apps/edge/internal/service/provider_pool.go:105-115` composes the operation predicate with `AcceptCandidate`, so an all-unsupported pool loses its cause and returns only `ErrProviderPoolCandidateRejected`. The same collapse occurs in the live resolver at lines 128-137, and `TestProtocolProfileOperationAdmissionReResolution` asserts only the generic sentinel.

#### Solution

Add exported `ProviderPoolOperationUnsupportedError` with a trimmed `Operation`, an `Error` method that does not expose provider details, and `Unwrap() error` returning `ErrProviderPoolCandidateRejected`. Filter operation support before `AcceptCandidate` during initial resolution and inside the live resolver. Return the typed error only when concrete-profile operation filtering removes every otherwise available candidate; retain generic request-local rejection and nil-profile legacy admission.

Before (`provider_pool.go:105`):

~~~go
acceptCandidate := composeProviderPoolCandidatePredicates(
    req.AcceptCandidate,
    providerOperationCandidatePredicate(req.Tunnel.Operation),
)
candidates, rejected := filterProviderPoolCandidates(candidates, acceptCandidate)
if rejected {
    return nil, ErrProviderPoolCandidateRejected
}
~~~

After:

~~~go
operation := strings.TrimSpace(req.Tunnel.Operation)
operationPredicate := providerOperationCandidatePredicate(operation)
candidates, rejected := filterProviderPoolCandidates(candidates, operationPredicate)
if rejected {
    return nil, &ProviderPoolOperationUnsupportedError{Operation: operation}
}
candidates, rejected = filterProviderPoolCandidates(candidates, req.AcceptCandidate)
if rejected {
    return nil, ErrProviderPoolCandidateRejected
}
~~~

Apply the same ordered checks to every live re-resolution. Do not change queue timeout/capacity selection or success dispatch.

#### Modified Files and Checklist

- [ ] `apps/edge/internal/service/provider_pool.go`: add the typed error, unwrap compatibility, and ordered initial/live filtering; remove the compose helper if unused.
- [ ] `apps/edge/internal/service/protocol_profile_test.go`: add initial all-unsupported typed coverage and strengthen live re-resolution assertions for type, operation, sentinel compatibility, terminal return, and zero Node dispatch.

#### Test Strategy

Add `TestProtocolProfileOperationAdmissionRejectsUnsupportedCandidate` using an explicit Anthropic profile and Responses operation. Assert `errors.As`, `Operation == "responses"`, `errors.Is(..., ErrProviderPoolCandidateRejected)`, no captured Node request, and no stranded admission. Strengthen `TestProtocolProfileOperationAdmissionReResolution` with the same typed/zero-dispatch assertions while retaining supported mixed-pool and nil-profile cases.

#### Verification

~~~bash
env GOCACHE=/config/.cache/iop-responses-profile-g07 GOTMPDIR=/config/workspace/iop-s0/.local/gotmp-responses-profile-g07 go test -count=1 ./apps/edge/internal/service -run 'TestProtocolProfileOperationAdmission'
~~~

Expected: all operation-admission cases pass; unsupported initial/live paths return the typed Responses error without Node dispatch.

### [REVIEW_API-2] Map the typed Responses error and align the test double

#### Problem

`apps/edge/internal/openai/responses_handler.go:466-468` maps every generic candidate rejection to the output-validation-only message. `provider_test_support_test.go:202-227` checks only `AcceptCandidate` before preparation, and `responses_protocol_profile_test.go:182-231` still expects the preparer-only HTTP 502 path.

#### Solution

Check `ProviderPoolOperationUnsupportedError` before the generic sentinel and return HTTP 400, `invalid_request_error`, and `no provider profile supports the requested Responses operation`. Add one test-only admission helper used by both fake result branches: for a non-nil concrete profile and non-empty requested operation, reject missing operations with the typed error before request-local policy, preparation, recording, or normalized dispatch; nil profiles remain legacy-compatible.

Before (`responses_handler.go:466`):

~~~go
if errors.Is(err, edgeservice.ErrProviderPoolCandidateRejected) {
    writeError(w, http.StatusBadRequest, "invalid_request_error", openAIStreamGateCandidateRejectedMessage)
    return
}
~~~

After:

~~~go
var operationErr *edgeservice.ProviderPoolOperationUnsupportedError
if errors.As(err, &operationErr) {
    writeError(w, http.StatusBadRequest, "invalid_request_error", "no provider profile supports the requested Responses operation")
    return
}
if errors.Is(err, edgeservice.ErrProviderPoolCandidateRejected) {
    writeError(w, http.StatusBadRequest, "invalid_request_error", openAIStreamGateCandidateRejectedMessage)
    return
}
~~~

#### Modified Files and Checklist

- [ ] `apps/edge/internal/openai/responses_handler.go`: map the typed operation error before the generic output-validation rejection.
- [ ] `apps/edge/internal/openai/provider_test_support_test.go`: mirror production operation-before-request admission in custom/default fake branches.
- [ ] `apps/edge/internal/openai/responses_protocol_profile_test.go`: assert the exact 400/type/message contract and zero tunnel/normalized dispatch in dedicated and table coverage.

#### Test Strategy

Decode the dedicated unsupported response envelope and assert status 400, `error.type == "invalid_request_error"`, exact sanitized message, and zero dispatch. Extend the admission table with expected error type/message for the unsupported row. Retain supported non-stream/stream, auth, and nil-profile expectations unchanged.

#### Verification

~~~bash
env GOCACHE=/config/.cache/iop-responses-profile-g07 GOTMPDIR=/config/workspace/iop-s0/.local/gotmp-responses-profile-g07 go test -count=1 ./apps/edge/internal/openai -run 'TestResponses(ProtocolProfile|LegacyProfileFallback)'
env GOCACHE=/config/.cache/iop-responses-profile-g07 GOTMPDIR=/config/workspace/iop-s0/.local/gotmp-responses-profile-g07 go test -count=1 ./apps/edge/internal/openai -run 'TestResponses|Test.*ProviderAuth'
~~~

Expected: unsupported concrete profiles receive only the operation-specific 400 before dispatch; success, provider-auth, validation, and legacy cases remain passing.

### [REVIEW_API-3] Record deterministic fresh verification evidence

#### Problem

`CODE_REVIEW-cloud-G07.md:48-57` leaves every item unchecked, and lines 101-202 leave every command output as a placeholder. The implementation and verification cannot be judged.

#### Solution

Use the exact task-specific cache/temp roots for every Go command, retain `-count=1`, and paste actual stdout/stderr plus exit status into the active review stub. Fill implementation items, design decisions, deviations, and all verification blocks. Do not summarize an unexecuted or failed command as PASS.

#### Modified Files and Checklist

- [ ] `agent-task/m-multi-provider-protocol-profile-native-messages/03+02_responses_profile/CODE_REVIEW-cloud-G06.md`: fill every implementation-owned field with current-loop evidence.

#### Test Strategy

No separate test file is added for evidence capture. REVIEW_API-1 and REVIEW_API-2 own regression tests; this item proves all claimed commands ran against the current checkout.

#### Verification

Run every command under `Final Verification`; actual output must match the implemented source and contain no placeholder.

## Modified Files Summary

| File | Items |
|------|-------|
| `apps/edge/internal/service/provider_pool.go` | REVIEW_API-1 |
| `apps/edge/internal/service/protocol_profile_test.go` | REVIEW_API-1 |
| `apps/edge/internal/openai/responses_handler.go` | REVIEW_API-2 |
| `apps/edge/internal/openai/provider_test_support_test.go` | REVIEW_API-2 |
| `apps/edge/internal/openai/responses_protocol_profile_test.go` | REVIEW_API-2 |
| `agent-task/m-multi-provider-protocol-profile-native-messages/03+02_responses_profile/CODE_REVIEW-cloud-G06.md` | REVIEW_API-3 |

## Final Verification

~~~bash
test -f agent-task/archive/2026/08/m-multi-provider-protocol-profile-native-messages/02+01_messages_ingress/complete.log
command -v go && go version && go env GOROOT
mkdir -p /config/.cache/iop-responses-profile-g07 /config/workspace/iop-s0/.local/gotmp-responses-profile-g07
env GOCACHE=/config/.cache/iop-responses-profile-g07 GOTMPDIR=/config/workspace/iop-s0/.local/gotmp-responses-profile-g07 go test -count=1 ./apps/edge/internal/service -run 'TestProtocolProfileOperationAdmission'
env GOCACHE=/config/.cache/iop-responses-profile-g07 GOTMPDIR=/config/workspace/iop-s0/.local/gotmp-responses-profile-g07 go test -count=1 ./apps/edge/internal/openai -run 'TestResponses(ProtocolProfile|LegacyProfileFallback)'
env GOCACHE=/config/.cache/iop-responses-profile-g07 GOTMPDIR=/config/workspace/iop-s0/.local/gotmp-responses-profile-g07 go test -count=1 ./apps/edge/internal/openai -run 'TestResponses|Test.*ProviderAuth'
env GOCACHE=/config/.cache/iop-responses-profile-g07 GOTMPDIR=/config/workspace/iop-s0/.local/gotmp-responses-profile-g07 go test -count=1 ./apps/node/internal/adapters/openai_compat -run 'TestProtocolProfile|TestLegacyEndpointPathFallback|TestOpenAICompatTunnelProvider_ResponsesPath'
env GOCACHE=/config/.cache/iop-responses-profile-g07 GOTMPDIR=/config/workspace/iop-s0/.local/gotmp-responses-profile-g07 go test -count=1 ./apps/edge/...
env GOCACHE=/config/.cache/iop-responses-profile-g07 GOTMPDIR=/config/workspace/iop-s0/.local/gotmp-responses-profile-g07 go vet ./apps/edge/...
IOP_LEMONADE_MODE=fake IOP_LEMONADE_GOCACHE=/config/.cache/iop-responses-profile-g07 ./scripts/e2e-openai-lemonade.sh
git diff --check
~~~

Expected: dependency/toolchain/setup checks pass; fresh service, Responses, Node, full Edge, and vet commands pass; fake-mode smoke reports `PASSED` and its loopback assertion observes exactly one Responses request; `git diff --check` emits no output.

After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
