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

# Typed Responses operation rejection and trustworthy regression 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 first Responses profile regression loop proved supported-profile passthrough but used a test double that bypassed the service-owned operation predicate for the unsupported case. Production therefore returned a generic output-validation admission message instead of a Responses operation-specific rejection, while the recorded default Go commands were contradicted by the reviewer's inaccessible configured cache. This follow-up gives operation admission a typed service error, maps it at the Responses surface, aligns the test double with production ordering, and records 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_local_G04_02.log` and `agent-task/m-multi-provider-protocol-profile-native-messages/03+02_responses_profile/code_review_cloud_G04_02.log`.
- Verdict: FAIL. Required: 2; Suggested: 0; Nit: 0.
- Required finding 1: `apps/edge/internal/openai/responses_protocol_profile_test.go` asserted a preparer-only 502 because `providerFakeRunService` skipped `providerOperationCandidatePredicate`; production rejected earlier with `ErrProviderPoolCandidateRejected` and the unrelated output-validation 400 message.
- Required finding 2: the recorded default `go test -count=1` commands were not reproducible because the configured `GOCACHE` was inaccessible. Reviewer-owned executable `GOTMPDIR` and writable `GOCACHE` overrides passed the focused Responses suite, service operation tests, Node profile tests, full Edge tests, Edge vet, and `git diff --check`.
- Affected production/test files: `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`, and `apps/edge/internal/openai/responses_protocol_profile_test.go`.
- The Messages predecessor is satisfied by archived PASS evidence at `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; the Pi and contract/spec sibling tasks 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-local-G04.md`
- `agent-task/m-multi-provider-protocol-profile-native-messages/03+02_responses_profile/CODE_REVIEW-cloud-G04.md`
- `agent-task/m-multi-provider-protocol-profile-native-messages/03+02_responses_profile/plan_local_G05_01.log`
- `agent-task/m-multi-provider-protocol-profile-native-messages/03+02_responses_profile/code_review_cloud_G05_01.log`
- `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/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/index.md`
- `agent-contract/outer/openai-compatible-api.md`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `agent-spec/index.md`
- `agent-spec/input/openai-compatible-surface.md`
- `agent-spec/runtime/edge-node-execution.md`
- `agent-spec/runtime/provider-pool-config-refresh.md`
- `packages/go/config/provider_types.go`
- `proto/iop/runtime.proto`
- `apps/edge/internal/service/provider_pool.go`
- `apps/edge/internal/service/model_queue_admission.go`
- `apps/edge/internal/service/protocol_profile_test.go`
- `apps/edge/internal/openai/provider_tunnel.go`
- `apps/edge/internal/openai/chat_handler.go`
- `apps/edge/internal/openai/responses_handler.go`
- `apps/edge/internal/openai/responses_stream_gate.go`
- `apps/edge/internal/openai/stream_gate_dispatcher.go`
- `apps/edge/internal/openai/stream_gate_release_sink.go`
- `apps/edge/internal/openai/provider_test_support_test.go`
- `apps/edge/internal/openai/provider_dispatch_test.go`
- `apps/edge/internal/openai/responses_handler_test.go`
- `apps/edge/internal/openai/openai_auth_routes_models_test.go`
- `apps/edge/internal/openai/responses_protocol_profile_test.go`
- `apps/node/internal/adapters/openai_compat/provider_tunnel.go`
- `apps/node/internal/adapters/openai_compat/provider_tunnel_test.go`
- `apps/node/internal/adapters/openai_compat/protocol_profile_test.go`
- `apps/node/internal/adapters/openai_compat/capabilities_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 `[Approved]`, lock released, no pending user review.
- Targeted Acceptance Scenario: S08 / Milestone Task `responses-regression`. Given the existing normalized, passthrough, and provider-auth Responses fixtures, running them with the optional `openai_responses` profile/runtime must preserve status, body, stream semantics, and the Pi consumption path.
- Evidence Map row S08 requires the existing Responses route/stream/provider-auth regression suite and links its PASS evidence to `responses-regression`.
- These criteria require this plan to reject an explicit profile that lacks the `responses` operation before Node dispatch, while retaining successful explicit-profile and legacy no-profile behavior. They also require the final suite and fake-mode smoke to preserve existing Responses semantics; live Responses smoke remains outside the common-provider baseline.

### Verification Context

- No external handoff was supplied. The active plan/review, archived predecessor completion, current production/test sources, SDD, matching contracts/specs, local test rules, and the repository fake-mode smoke script were read directly.
- Fresh review commands with the checkout's default Go environment failed during package setup because `go env GOCACHE` pointed into an inaccessible prior scratch directory. This is an environment/evidence failure, not a product-code test failure.
- Reviewer reruns succeeded with a writable cache under `/config/.cache` and an executable Go temp root under `/config/workspace/iop-s0/.local`; all follow-up Go commands therefore set both `GOCACHE` and `GOTMPDIR` explicitly and retain `-count=1`.
- Preconditions: Go 1.26.2 is available at `/config/.local/bin/go`; no credential or external provider is required; the archived Messages predecessor PASS exists; the workspace is already dirty with user-owned work that must be preserved.
- Applied criteria: package-focused service and Responses tests, broad Responses/provider-auth tests, Node operation/path compatibility tests, full Edge tests, Edge vet, fake-mode Edge/Node/provider smoke, and whitespace validation.
- Constraint: do not modify contract/spec, Pi, milestone, or central `agent-ops` files. The contract/spec closure sibling owns documentation drift; this child changes only runtime error classification and regression fidelity.
- Confidence: high for the diagnosed service/handler mismatch because the real service filters operation support before `PrepareProtocolTunnel`, the handler currently maps the shared sentinel to the output-validation message, and reviewer-owned environment overrides passed the affected suites.
- Repository-native fallback evidence is `IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh`, which launches deterministic local Edge, Node, and provider processes and asserts one Responses request without network credentials.
- External Verification Preflight: not applicable; all required evidence remains inside this checkout and uses local deterministic fixtures.

### Test Coverage Gaps

- The service tests cover mixed supported/unsupported selection, legacy nil-profile compatibility, and queue re-resolution, but they do not prove a typed operation-specific error when every initial candidate lacks `responses`.
- The queue re-resolution test asserts only the generic `ErrProviderPoolCandidateRejected`, so it cannot distinguish operation admission from stream/output-validation policy rejection.
- The Responses unsupported-profile tests use `providerFakeRunService`, which does not apply the production operation predicate and therefore observes the later preparer error and a 502.
- The unsupported handler cases do not assert the exact OpenAI error envelope type/message, so the unrelated output-validation message can pass unnoticed.
- Supported explicit-profile, legacy fallback, non-stream/stream body preservation, provider-auth, and Node operation-vs-path resolution already have coverage and must remain unchanged.

### Symbol References

- New symbol: `edgeservice.ProviderPoolOperationUnsupportedError`, 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 the focused service/Responses tests.
- Existing `ErrProviderPoolCandidateRejected` references were found in service queue admission and OpenAI/Anthropic stream-gate error handling. The new typed error must remain `errors.Is(..., ErrProviderPoolCandidateRejected)` compatible so those call sites retain terminal rejection behavior.
- No symbol is renamed or removed.

### Split Judgment

- This is one compact boundary: service-owned operation admission classification and the Responses HTTP/test interpretation of that classification must change together or the public regression remains false.
- Predecessor `02+01_messages_ingress` is satisfied by archived `complete.log` at `agent-task/archive/2026/08/m-multi-provider-protocol-profile-native-messages/02+01_messages_ingress/complete.log`.
- The original combined child was split into Responses profile runtime, Pi consumption, and contract/spec closure. This child remains limited to the first contract; sibling completion is not inferred here.

### Scope Rationale

- `apps/edge/internal/openai/chat_handler.go`, Anthropic handling, and stream-gate recovery rendering are excluded because the Required finding concerns initial/queued `/v1/responses` admission before dispatch; the typed error continues to wrap the existing generic sentinel for unaffected consumers.
- Node request building, profile schema/catalog, and wire fields are excluded because fresh tests already prove operation URL precedence and legacy Path fallback.
- `agent-contract/**`, `agent-spec/**`, `agent-client/pi/**`, and roadmap files are excluded because dependent siblings own contract/spec and Pi closure, and this split child must not claim the complete S08 roadmap Task independently.
- No production success-path body/status/stream/provider-auth logic is redesigned.

### Final Routing

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

## Dependencies and Execution Order

1. Confirm the archived `02+01_messages_ingress` PASS evidence before changing code.
2. Add the typed service rejection and initial/live re-resolution tests before updating the Responses handler and its test double.
3. Run focused tests first, then the broad/full suites and fake-mode smoke with deterministic Go cache/temp roots.

## Implementation Checklist

- [ ] Separate unsupported operation admission from request-local predicate rejection and preserve terminal queue behavior across initial and live re-resolution.
- [ ] Map the typed Responses operation rejection to a sanitized 400 response and make the handler test double mirror production operation admission ordering.
- [ ] Run deterministic fresh focused, broad, full Edge/Node, vet, smoke, and diff verification with explicit executable cache/temp roots.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [API-1] Type operation admission without changing queue terminal semantics

#### Problem

`apps/edge/internal/service/provider_pool.go:105-115` composes request-local and operation predicates, then reports every all-candidate rejection as `ErrProviderPoolCandidateRejected`. The same collapse occurs in live queue re-resolution at lines 128-137. The caller cannot distinguish a missing protocol operation from output-validation policy, and `TestProtocolProfileOperationAdmissionReResolution` verifies only the generic sentinel.

#### Solution

Filter the operation predicate separately before applying `AcceptCandidate` during both initial resolution and the live resolver closure. Return a new exported typed error containing the trimmed operation when operation filtering removes every otherwise available candidate; make that error unwrap to `ErrProviderPoolCandidateRejected` so `model_queue_admission.go` continues to classify it as terminal and existing generic consumers remain compatible.

Before:

~~~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 filtering inside the live resolver closure. Keep nil-profile legacy candidates admitted and keep supported mixed pools selecting the compatible provider.

#### Modified Files and Checklist

- [ ] `apps/edge/internal/service/provider_pool.go`: add `ProviderPoolOperationUnsupportedError`, generic-sentinel compatibility, and ordered initial/live filtering.
- [ ] `apps/edge/internal/service/protocol_profile_test.go`: add initial all-unsupported coverage and strengthen re-resolution assertions for error type, operation, terminal completion, and zero Node dispatch.

#### Test Strategy

Write `TestProtocolProfileOperationAdmissionRejectsUnsupportedCandidate` in `apps/edge/internal/service/protocol_profile_test.go` with an explicit Anthropic profile and a Responses operation. Assert `errors.As` finds `ProviderPoolOperationUnsupportedError`, its operation is `responses`, `errors.Is` still matches `ErrProviderPoolCandidateRejected`, and the fake Node captured no request. Update `TestProtocolProfileOperationAdmissionReResolution` with the same typed assertions after the queued profile refresh; retain the existing mixed supported selection and nil-profile compatibility tests.

#### 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: initial and live re-resolution return the typed Responses operation error without dispatch, mixed pools select the supported candidate, and legacy nil-profile candidates remain compatible.

### [API-2] Surface the real Responses operation rejection and align the test double

#### Problem

`apps/edge/internal/openai/responses_handler.go:466-468` maps the service's generic candidate rejection to `no provider supports the required output validation capability`. `apps/edge/internal/openai/provider_test_support_test.go:189-260` checks only `AcceptCandidate` and then runs `PrepareProtocolTunnel`, so `TestResponsesProtocolProfileUnsupportedRejectsBeforeDispatch` and its table case observe a test-double-only 502 instead of production admission.

#### Solution

In the Responses handler, check `ProviderPoolOperationUnsupportedError` before the generic sentinel and emit HTTP 400 with OpenAI type `invalid_request_error` and the sanitized message `no provider profile supports the requested Responses operation`. Keep generic stream/output-validation rejection mapping unchanged.

Update `providerFakeRunService.SubmitProviderPool` to apply the same operation rule as production before `AcceptCandidate`, tunnel preparation, request recording, or normalized dispatch: nil profiles remain legacy-compatible; concrete profiles must declare `req.Tunnel.Operation`; an all-unsupported selected candidate returns the typed error. This test-only ordering makes the existing unsupported fixture exercise the handler's real public mapping instead of the preparer fallback.

Before:

~~~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 rejection before the generic output-validation rejection.
- [ ] `apps/edge/internal/openai/provider_test_support_test.go`: mirror production operation admission before preparation or dispatch recording.
- [ ] `apps/edge/internal/openai/responses_protocol_profile_test.go`: change unsupported expectations to the exact 400/type/message contract and retain zero tunnel/normalized dispatch assertions in both dedicated and table coverage.

#### Test Strategy

Update `TestResponsesProtocolProfileUnsupportedRejectsBeforeDispatch` to assert HTTP 400, JSON error type `invalid_request_error`, the exact sanitized Responses operation message, and zero tunnel/normalized requests. Update the unsupported row in `TestResponsesProtocolProfileOperationAdmissionTable` to expect 400 and no dispatch. Keep supported non-stream/stream, provider-auth, and legacy fallback assertions unchanged so the test-double change cannot drift successful paths.

#### 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 the operation-specific 400 before any dispatch, while all existing Responses/profile/auth success and validation cases pass.

### [API-3] Replace contradicted verification claims with deterministic fresh evidence

#### Problem

The prior review recorded bare `go test -count=1` commands as PASS, but the reviewer could not reproduce either command because the configured `GOCACHE` was inaccessible. Passing results obtained only after environment overrides must be recorded with their actual command lines and output; otherwise verification trust remains failed.

#### Solution

Create deterministic task-specific cache/temp roots, set both environment variables on every Go test/vet command, retain `-count=1`, and paste actual stdout/stderr plus exit status into the active review stub. Record the command deviation explicitly. Run focused service/Responses tests first, then Node compatibility, full Edge, vet, local fake-mode smoke, and diff validation.

#### Modified Files and Checklist

- [ ] `agent-task/m-multi-provider-protocol-profile-native-messages/03+02_responses_profile/CODE_REVIEW-cloud-G07.md`: fill implementation notes, deviations, and exact fresh command output without claiming unexecuted commands.

#### Test Strategy

No new test file is owned by this evidence item; API-1 and API-2 add the behavioral tests. This item verifies that every claimed command is rerunnable in the current environment and that the fake smoke covers the repository-native Edge→Node→Responses path without credentials.

#### Verification

Run every command in `Final Verification` and record actual output. Any command change must appear in `Deviations from Plan`; do not summarize a failed command as PASS.

## Modified Files Summary

| File | Items |
|------|-------|
| `apps/edge/internal/service/provider_pool.go` | API-1 |
| `apps/edge/internal/service/protocol_profile_test.go` | API-1 |
| `apps/edge/internal/openai/responses_handler.go` | API-2 |
| `apps/edge/internal/openai/provider_test_support_test.go` | API-2 |
| `apps/edge/internal/openai/responses_protocol_profile_test.go` | API-2 |
| `agent-task/m-multi-provider-protocol-profile-native-messages/03+02_responses_profile/CODE_REVIEW-cloud-G07.md` | 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: the archived dependency exists; every fresh package/full/vet command passes with the explicit cache/temp roots; fake-mode smoke reports `PASSED` and asserts one Responses request; `git diff --check` emits no output. Cached test output is not acceptable because all Go tests use `-count=1`.

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