<!-- task=m-multi-provider-protocol-profile-native-messages/01_protocol_core plan=2 tag=REVIEW_API -->

# Close Protocol Profile Admission and Runtime Snapshot Gaps

## For the Implementing Agent

Filling the implementation-owned sections of `CODE_REVIEW-cloud-G09.md` is the mandatory final implementation step. Run every verification command, paste actual notes and stdout/stderr, keep both active files in place, and report ready for review; only the code-review skill may finalize, rename logs, or write `complete.log`. 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 control-plane stop files, classify the next state, archive artifacts, or write `complete.log`.

## Background

The second review found five production gaps despite green focused and full-suite checks: operation support is enforced after selection, refresh classification ignores profile state, adapter-backed providers lose their profile before Node execution, model mapping rewrites unrelated JSON values, and header/auth validation accepts invalid or inaccurate fixtures. This follow-up closes those gaps without adding public Messages ingress, changing the wire schema, or broadening the roadmap scope.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-multi-provider-protocol-profile-native-messages/01_protocol_core/plan_cloud_G09_01.log`
- Prior review: `agent-task/m-multi-provider-protocol-profile-native-messages/01_protocol_core/code_review_cloud_G09_01.log`
- Verdict: `FAIL`; Required=5, Suggested=0, Nit=0; `review_rework_count=2`; `evidence_integrity_failure=true`.
- Required carryover: filter operation-incompatible candidates before queue admission and re-resolution; classify profile-affecting and token-counter refresh changes; compile adapter-backed concrete profiles into provider-specific Node adapters; preserve all non-model JSON during model mapping; validate RFC-compatible header names and correct the built-in auth matrix.
- Affected areas: config normalization/catalog/refresh classification, Edge provider-pool admission and Node payload mapping, Node OpenAI-compatible request preparation, and the provider-first fake-upstream smoke.
- Verification evidence: fresh focused config/Edge/Node/OpenAI-compatible commands, protobuf generation, Dart generated-proto test, fake Lemonade full-cycle, full Edge/Node suites, the documented packages replacement suite, and `git diff --check` passed. None of those commands exercised the five Required boundary cases.
- Roadmap carryover: `profile-contract`, `path-resolution`, and `profile-catalog` remain unchecked until this follow-up passes.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/multi-provider-protocol-profile-native-messages.md`
- Milestone link: [Milestone document](agent-roadmap/phase/operational-observability-provider-management/milestones/multi-provider-protocol-profile-native-messages.md)
- Task ids:
  - `profile-contract`: common provider profile and immutable overlay/runtime contract
  - `path-resolution`: profile-operation URL resolver
  - `profile-catalog`: built-in/custom profile catalog and admission
- Completion mode: check-on-pass

## Analysis

### Files Read

- `agent-task/m-multi-provider-protocol-profile-native-messages/01_protocol_core/PLAN-cloud-G09.md`
- `agent-task/m-multi-provider-protocol-profile-native-messages/01_protocol_core/CODE_REVIEW-cloud-G09.md`
- `agent-task/m-multi-provider-protocol-profile-native-messages/01_protocol_core/plan_local_G08_01.log`
- `agent-task/m-multi-provider-protocol-profile-native-messages/01_protocol_core/code_review_cloud_G08_01.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/inner/edge-node-runtime-wire.md`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `agent-spec/runtime/edge-node-execution.md`
- `agent-spec/runtime/provider-pool-config-refresh.md`
- `packages/go/config/adapter_types.go`
- `packages/go/config/edge_types.go`
- `packages/go/config/provider_types.go`
- `packages/go/config/load.go`
- `packages/go/config/normalize.go`
- `packages/go/config/validate.go`
- `packages/go/config/protocol_profile.go`
- `packages/go/config/protocol_profile_test.go`
- `apps/edge/internal/configrefresh/classify.go`
- `apps/edge/internal/configrefresh/provider_classify_test.go`
- `apps/edge/internal/configrefresh/node_runtime_classify_test.go`
- `apps/edge/internal/bootstrap/runtime.go`
- `apps/edge/internal/bootstrap/runtime_refresh_test.go`
- `apps/edge/internal/bootstrap/runtime_refresh_node_test.go`
- `apps/edge/internal/node/mapper.go`
- `apps/edge/internal/node/protocol_profile_mapper_test.go`
- `apps/edge/internal/service/provider_pool.go`
- `apps/edge/internal/service/provider_resolution.go`
- `apps/edge/internal/service/provider_tunnel.go`
- `apps/edge/internal/service/protocol_profile_test.go`
- `apps/edge/internal/openai/chat_handler.go`
- `apps/edge/internal/openai/responses_handler.go`
- `apps/edge/internal/openai/provider_tunnel.go`
- `apps/node/internal/adapters/factory.go`
- `apps/node/internal/adapters/protocol_profile_config_test.go`
- `apps/node/internal/adapters/openai_compat/adapter.go`
- `apps/node/internal/adapters/openai_compat/request.go`
- `apps/node/internal/adapters/openai_compat/protocol_profile_test.go`
- `scripts/e2e-openai-lemonade.sh`
- `Makefile`

### SDD Criteria

- SDD: `agent-roadmap/sdd/operational-observability-provider-management/multi-provider-protocol-profile-native-messages/SDD.md`; status `[approved]`; implementation lock released.
- S01 / `profile-contract`: a selected provider must carry one immutable concrete profile through config normalization, refresh behavior, Edge admission, wire mapping, and Node adapter creation.
- S02 / `path-resolution`: the concrete profile, including a referenced legacy adapter endpoint overlay, must remain the sole source for the final operation URL.
- S03 / `profile-catalog`: built-in and custom profiles must admit only declared operations and apply the declared auth and model mapping exactly.
- S06 body-preservation invariant: raw provider-native extensions must survive the allowed model/auth rewrite. This is a cross-cutting guard for later native Messages work, not an expansion into public Messages ingress.
- S08 Responses regression guard: a Responses-capable provider must remain selectable when another candidate in the same model group lacks that operation.
- Evidence Map rows for S01-S03 require config validation, path resolution, and real provider request evidence. They drive the five implementation items and require mixed-candidate, refresh-apply, Edge-to-Node adapter, exact raw-body, and auth-matrix tests in final verification.

### Verification Context

- No handoff was supplied. Verification sources read: `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/node-smoke.md`, `agent-test/local/platform-common-smoke.md`, `agent-ops/rules/project/domain/edge/rules.md`, `agent-ops/rules/project/domain/node/rules.md`, `agent-ops/rules/project/domain/platform-common/rules.md`, and `agent-ops/rules/project/domain/testing/rules.md`.
- Runner: local checkout `/config/workspace/iop-s0`; branch `feature/multi-provider-protocol-profile-native-messages`; HEAD `174a2a6b1375bd71c3a977aff91e6afb9c26bd58`; dirty worktree contains the task implementation plus four unrelated user-owned roadmap edits that must remain untouched.
- Toolchain verified on 2026-08-01: Go 1.26.2 linux/arm64, protoc 29.3, Flutter 3.41.5, and an available `protoc-gen-dart`.
- Fresh reviewer commands passed: the four focused commands from the prior plan, `make proto`, `make proto-dart`, the Dart generated-proto test, fake Lemonade E2E, full `apps/edge/...`, full `apps/node/...`, a packages suite skipping exactly `TestCLIWorkspacePreflightFailures` and `TestSerialIntegratorRestartRecoversInterruptedApplyByRollback`, and `git diff --check`.
- The exact unfiltered packages command remains constrained by those two documented unrelated fixtures. Use the exact command first, then the named-skip replacement if and only if the same failures reproduce; paste both outputs.
- Official auth sources checked on 2026-08-01: Xiaomi MiMo's First API Call and API Integration FAQ document `api-key` or `Authorization: Bearer`; MiniMax's Anthropic-compatible API reference documents `Authorization: Bearer` and also recognizes `x-api-key`. The implementation must use one explicit supported form per built-in and lock it in an independent expected table.
- No live credential or external provider call is required. The repository fake Lemonade script supplies a loopback upstream and must exercise an adapter-backed profile after this follow-up.
- Confidence: high. All five failures are direct source-path contradictions; deterministic local regressions can cover every one without a schema migration or external host.

### Test Coverage Gaps

- Requested operation admission: current tests exercise arbitrary predicates but do not mix an unsupported Gemini Responses candidate with a supported OpenAI candidate or verify queued re-resolution.
- Refresh ownership: current classifier keys omit `protocol_profiles`, provider `profile`, and model `token_counter`; the profile refresh test bypasses the refresh subsystem with `SetRuntimeConfig`.
- Adapter-backed profiles: mapper tests cover direct provider-first entries and legacy entries independently, but no test compiles a profile-bearing provider from its referenced vLLM/OpenAI-compatible adapter and executes that payload.
- Raw JSON preservation: current request tests use ordinary small values; none proves that `9007199254740993`, exponent forms, nested unknown fields, and whitespace outside the replaced value survive.
- Header/auth validation: the only negative header case is empty, and current expected built-in auth values repeat the inaccurate production fixture instead of declaring an independent matrix.

### Symbol References

- Add private predicate composition and operation-admission helpers in `apps/edge/internal/service/provider_pool.go`; no exported type or field changes.
- Extend private `providerKey` and model diff fields in `apps/edge/internal/configrefresh/classify.go`; no external caller changes.
- Change private `providerAdapterKey` semantics so any provider with a concrete `RuntimeProfile` dispatches through its provider id; call sites flow through `applyProviderDispatchFields` in `apps/edge/internal/service/provider_resolution.go`.
- Add private adapter-reference resolution/compile helpers in `apps/edge/internal/node/mapper.go` and private backing-endpoint resolution in `packages/go/config/normalize.go`; keep the provider `Adapter` field as the source configuration reference.
- Replace private `mapProfileBodyModel` internals without changing its signature or call sites.
- Remove the `net/textproto` dependency from `packages/go/config/protocol_profile.go` if it becomes unused; no public symbol is removed or renamed.

### Split Judgment

- Keep one recovery plan. The indivisible invariant is that one selected provider snapshot must be operation-compatible and must name the same concrete adapter/profile semantics at config load, refresh classification, queue selection, Node payload construction, and the final HTTP request.
- Splitting admission, refresh, or mapper work would temporarily retain competing sources of truth and could make isolated green tests conceal another Edge/Node drift. The subtask has no predecessor marker and no new dependency.

### Scope Rationale

- Exclude public Anthropic Messages ingress/translation, contract/spec edits, live provider qualification, and unrelated Responses redesign; this plan only repairs the core profile boundary already introduced.
- Exclude protobuf and Dart schema changes. The existing wire has the required concrete-profile field; regeneration is verification only unless an unexpected generated drift is found, in which case stop and record the blocker rather than expanding scope.
- Preserve the four unrelated roadmap edits and all other user-owned changes. Do not clean `.gocache`/`.local`, change roadmap state, write `complete.log`, or edit central `agent-ops/rules/common/**` or `agent-ops/skills/common/**`.

### Final Routing

- `status=routed`; `evaluation_mode=isolated-reassessment`; finalizer=`finalize-task-policy.sh`; mode=`pair`.
- Build closures: scope/context/verification/evidence/ownership/decision are all `true`; build scores scope_coupling=2, state_concurrency=1, blast_irreversibility=2, evidence_diagnosis=2, verification_complexity=2 => G09; route basis=`grade-boundary`; lane=`cloud`; filename=`PLAN-cloud-G09.md`; capability gap: none.
- Review closures: all `true`; review scores 2/1/2/2/2 => G09; route=`official-review`; lane=`cloud`; adapter=`codex`; model=`gpt-5.6-sol`; reasoning=`xhigh`; filename=`CODE_REVIEW-cloud-G09.md`.
- Signals: `large_indivisible_context=false`; matched loop risks=`temporal_state`, `concurrent_consistency`, `boundary_contract`, `structured_interpretation`, `variant_product`; count=5; `review_rework_count=2`; `evidence_integrity_failure=true`; recovery boundary=true. Grade boundary remains the final route basis.

## Implementation Checklist

- [ ] Filter concrete profiles that do not support the requested operation before initial provider-pool admission and every queued re-resolution, while preserving deliberate nil-profile compatibility.
- [ ] Classify top-level profile catalogs and provider selectors as restart-required, classify model token counters as applied, and prove a profile-changing apply cannot silently commit a divergent runtime snapshot.
- [ ] Compile each adapter-backed profile provider into a provider-id-keyed OpenAI-compatible Node adapter derived from its exact backing instance while retaining the legacy adapter and rejecting missing, disabled, or ambiguous references.
- [ ] Rewrite only the top-level JSON `model` string so all non-model bytes and provider-native values, including large integers, remain unchanged.
- [ ] Enforce HTTP token syntax for auth header names and lock the built-in MiniMax/MiMo auth declarations to independently expected currently documented values.
- [ ] Run fresh focused, refresh, full regression, generated-consumer, and adapter-backed Edge→Node→fake-upstream verification and preserve unrelated user changes.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_API-1] Admit only operation-compatible candidates

#### Problem

`SubmitProviderPool` filters only the caller's optional `AcceptCandidate` before queue admission (`apps/edge/internal/service/provider_pool.go:104-109`) and wraps re-resolution only when that predicate is non-nil (`provider_pool.go:123-137`). `PrepareProtocolTunnel` checks the operation after a candidate already owns the reservation. A Responses request can therefore choose a Chat-only profile and fail even when another candidate supports Responses.

Before (`apps/edge/internal/service/provider_pool.go:104-109`):

```go
candidates, returnedPolicy, err := s.resolveQueueCandidates(req.Run)
if err != nil {
    return nil, err
}
candidates, rejected := filterProviderPoolCandidates(candidates, req.AcceptCandidate)
```

#### Solution

Derive a request-local operation predicate from `req.Tunnel.Operation` and compose it with any existing `AcceptCandidate` once. Apply the composed predicate to both the initial candidates and every re-resolution closure. A concrete profile must contain the exact operation key; a nil profile remains eligible so mixed-version legacy `Path` fallback is not silently removed. Do not move the check into only Chat or Responses handlers because that would leave other tunnel callers and queue refresh inconsistent.

After (representative):

```go
accept := composeProviderPoolCandidatePredicates(
    req.AcceptCandidate,
    providerOperationCandidatePredicate(req.Tunnel.Operation),
)
candidates, rejected := filterProviderPoolCandidates(candidates, accept)
resolveCandidates := s.filteredQueueCandidatesClosure(req.Run, accept)
```

#### Modified Files and Checklist

- [ ] `apps/edge/internal/service/provider_pool.go`: compose operation support with existing admission predicates for initial and refreshed candidates.
- [ ] `apps/edge/internal/service/protocol_profile_test.go`: mixed Gemini/OpenAI Responses and re-resolution regressions, including nil-profile compatibility.

#### Test Strategy

Add `TestProtocolProfileOperationAdmissionFiltersUnsupportedCandidate`, `TestProtocolProfileOperationAdmissionReResolution`, and a nil-profile compatibility case in `apps/edge/internal/service/protocol_profile_test.go`. Fixtures must force the unsupported candidate to win without filtering, assert the supported provider is selected, assert the unsupported candidate is never prepared/dispatched, and repeat after a runtime snapshot change.

#### Verification

```bash
go test -count=1 ./apps/edge/internal/service -run 'TestProtocolProfileOperationAdmission|Test.*TunnelOperation'
go test -count=1 ./apps/edge/internal/openai -run 'Test.*(ProtocolProfile|ProviderDispatch|Responses)'
```

Expected: operation-incompatible concrete profiles never reach queue selection or preparation, re-resolution applies the same filter, and legacy nil-profile tests remain green.

### [REVIEW_API-2] Classify every profile-affecting refresh input

#### Problem

`providerKey` omits the provider selector (`apps/edge/internal/configrefresh/classify.go:81-112`), provider structural comparison omits the resolved selector (`classify.go:267-298`), `appendModelChanges` omits `TokenCounter` (`classify.go:344-375`), and `Classify` never compares top-level `ProtocolProfiles` (`classify.go:409-416`). A profile-only apply is therefore reported as no change; `Runtime.RefreshConfig` can commit Edge state without sending the changed adapter snapshot to Node.

Before (`apps/edge/internal/configrefresh/classify.go:409-416`):

```go
func Classify(current, candidate *config.EdgeConfig) Result {
    var changes []Change
    appendEdgeChanges(&changes, current, candidate)
    appendNodeChanges(&changes, current, candidate)
    appendProviderChanges(&changes, current, candidate)
    appendModelChanges(&changes, current, candidate)
    return resultFromChanges(changes)
}
```

#### Solution

Add the source `Profile` selector to `providerKey` and classify selector changes as `restart_required`. Compare the complete top-level `ProtocolProfiles` map once at `protocol_profiles` and classify any change as `restart_required`; do not compare derived `RuntimeProfile` pointers separately. Add `TokenCounter` to the model diff and classify it as `applied`, consistent with the existing live model-catalog transaction. Through `Runtime.RefreshConfig`, prove a profile-changing apply returns restart-required, does not replace `rt.Cfg`, and does not create a Node refresh payload; also keep a token-counter-only applied test.

After (representative):

```go
appendDeepIfChanged(changes, "protocol_profiles", StatusRestartRequired,
    current.ProtocolProfiles, candidate.ProtocolProfiles)
appendIfChanged(changes, providerPath(id, "profile"), StatusRestartRequired,
    cur.Profile, next.Profile)
appendDeepIfChanged(changes, modelPath(id, "token_counter"), StatusApplied,
    cur.TokenCounter, next.TokenCounter)
```

#### Modified Files and Checklist

- [ ] `apps/edge/internal/configrefresh/classify.go`: include catalog, selector, and token-counter inputs with explicit classifications.
- [ ] `apps/edge/internal/configrefresh/provider_classify_test.go`: catalog and provider-selector restart-required tables.
- [ ] `apps/edge/internal/configrefresh/node_runtime_classify_test.go`: token-counter-only applied regression.
- [ ] `apps/edge/internal/bootstrap/runtime_refresh_test.go`: actual profile-changing apply preserves the running Edge snapshot and reports restart-required.

#### Test Strategy

Add table cases `TestClassifyProtocolProfileChangeRestartRequired` and `TestClassifyProviderProfileSelectorRestartRequired`, plus `TestClassifyModelTokenCounterApplied`. Add `TestRefreshApplyDoesNotMutateOnProtocolProfileRestartRequired` using a temporary config and the real `RefreshConfig` apply path; assert status/path, pointer/state preservation, and unchanged model/provider behavior.

#### Verification

```bash
go test -count=1 ./apps/edge/internal/configrefresh -run 'TestClassify(ProtocolProfile|ProviderProfile|ModelTokenCounter)'
go test -count=1 ./apps/edge/internal/bootstrap -run 'TestRefreshApplyDoesNotMutateOnProtocolProfileRestartRequired'
```

Expected: every profile-affecting diff is visible and restart-gated, token-counter-only changes remain applied, and the runtime does not silently commit profile drift.

### [REVIEW_API-3] Compile adapter-backed providers into concrete Node adapters

#### Problem

Normalization attaches a concrete profile to legacy-compatible providers (`packages/go/config/normalize.go:31-55`), but `BuildConfigPayload` skips every provider with `Adapter != ""` (`apps/edge/internal/node/mapper.go:47-51`). `providerAdapterKey` also returns the backing adapter name before considering `RuntimeProfile` (`apps/edge/internal/service/provider_resolution.go:280-287`). The selected provider and Edge preparation therefore use the concrete profile while Node executes an unprofiled legacy adapter.

Before (`apps/edge/internal/node/mapper.go:47-51`):

```go
for _, p := range rec.Providers {
    if p.Adapter != "" {
        // references a legacy adapter instance; skip provider-first compile
        continue
    }
```

#### Solution

During config normalization, resolve an explicit adapter reference against the normalized enabled vLLM/OpenAI-compatible instance namespace. For a profile-backed provider, overlay the referenced endpoint onto only that provider's cloned `RuntimeProfile` using the existing root-versus-explicit-path rule. Reject no match, disabled-only match, or more than one supported-type match with node/provider/adapter ids.

In the Edge mapper, compile a provider-specific `openai_compat` payload named by provider id. Copy endpoint, headers, capacity, queue, and request-timeout values from the referenced instance, overlay any explicit provider fields by the already documented precedence, and attach the provider's concrete cloned profile. Still append the original legacy adapter payload for direct compatibility. Change `providerAdapterKey` to return provider id whenever `RuntimeProfile != nil`; otherwise retain current legacy behavior.

After (representative):

```go
if p.RuntimeProfile != nil && strings.TrimSpace(p.Adapter) != "" {
    backing, err := resolveProfileBackingAdapter(rec.Adapters, p.Adapter)
    if err != nil {
        return nil, fmt.Errorf("provider %q: %w", p.ID, err)
    }
    ac = compileProfileBackedAdapter(p, backing)
} else {
    ac, err = providerToAdapterConfig(p)
}
```

#### Modified Files and Checklist

- [ ] `packages/go/config/normalize.go`: resolve backing endpoints during concrete-profile creation and reject missing/disabled/ambiguous references.
- [ ] `packages/go/config/protocol_profile_test.go`: config-load tables for backing endpoint overlay and invalid references.
- [ ] `apps/edge/internal/node/mapper.go`: synthesize provider-id-keyed profile adapters from exact legacy backing instances while retaining legacy payloads.
- [ ] `apps/edge/internal/node/protocol_profile_mapper_test.go`: assert provider-specific and legacy entries, copied operational fields, cloned profile, and ambiguity failures.
- [ ] `apps/edge/internal/service/provider_resolution.go`: dispatch profile-backed providers by provider id.
- [ ] `apps/edge/internal/service/protocol_profile_test.go`: selected candidate adapter-key regression.
- [ ] `apps/node/internal/adapters/protocol_profile_config_test.go`: build the provider-specific payload into a Node config set and assert profile/fingerprint retention.
- [ ] `scripts/e2e-openai-lemonade.sh`: make fake mode use a provider referencing a named legacy OpenAI-compatible adapter and retain exact path/auth/model assertions.

#### Test Strategy

Use a named OpenAI-compatible backing adapter with a loopback endpoint, custom header, capacity/timeouts, and a profile with a custom operation path and model mapping. Edge mapper tests must see both adapter names; service tests must dispatch the provider id; Node config tests must retain the profile in the provider-specific fingerprint. The fake Lemonade full-cycle must remove the provider's direct endpoint, reference the legacy adapter, and fail unless Models/Chat/Responses reach the expected loopback paths with correct auth and mapped model.

#### Verification

```bash
go test -count=1 ./packages/go/config/... -run 'TestProtocolProfile.*(Adapter|Backing|Endpoint)'
go test -count=1 ./apps/edge/internal/node ./apps/edge/internal/service -run 'TestProtocolProfile.*(Adapter|Backing|Dispatch)'
go test -count=1 ./apps/node/internal/adapters -run 'TestProtocolProfile.*(Config|Adapter|Fingerprint)'
IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh
```

Expected: one adapter-backed provider creates a provider-id profile adapter plus the retained legacy adapter, all invalid references fail deterministically, and the full-cycle reaches only the fake upstream.

### [REVIEW_API-4] Preserve non-model raw JSON during model mapping

#### Problem

`mapProfileBodyModel` decodes the entire request into `map[string]any` and marshals it again (`apps/node/internal/adapters/openai_compat/request.go:51-69`). This rounds integers such as `9007199254740993`, can normalize provider-native lexical forms, and rewrites unrelated content even though only the top-level model is allowed to change.

Before (`apps/node/internal/adapters/openai_compat/request.go:55-65`):

```go
var payload map[string]any
if err := json.Unmarshal(body, &payload); err != nil {
    return nil, fmt.Errorf("decode profile request body: %w", err)
}
model, _ := payload["model"].(string)
mapped := a.profile.MapModel(model)
payload["model"] = mapped
prepared, err := json.Marshal(payload)
```

#### Solution

Replace whole-object `any` decoding with a focused top-level JSON string-field rewriter. Validate that the body is one JSON object, scan string escapes and nesting to find the top-level `model` value span, decode only that string, and splice the JSON-encoded mapped string into the original byte slice. Return the original bytes when the field is absent, non-string, or maps identically. Malformed JSON must retain the current descriptive error behavior. Do not change nested `model` fields or any byte outside the replaced top-level value.

After (representative):

```go
model, valueStart, valueEnd, ok, err := topLevelJSONStringField(body, "model")
if err != nil || !ok {
    return bodyOrError(body, err)
}
mapped := a.profile.MapModel(model)
return spliceJSONStringValue(body, valueStart, valueEnd, mapped), nil
```

#### Modified Files and Checklist

- [ ] `apps/node/internal/adapters/openai_compat/request.go`: surgically replace only the top-level model string.
- [ ] `apps/node/internal/adapters/openai_compat/protocol_profile_test.go`: direct helper and actual HTTP request assertions for exact non-model byte preservation.

#### Test Strategy

Add `TestProtocolProfileModelMappingPreservesRawBody` and extend the actual request test. Use one body containing `9007199254740993`, `1.2300e+04`, nested unknown objects/arrays, escaped strings, a nested `model`, and deliberate whitespace. Construct the exact expected output by changing only the top-level model value and compare bytes at the fake upstream. Add malformed, absent, non-string, and identity-mapping cases.

#### Verification

```bash
go test -count=1 ./apps/node/internal/adapters/openai_compat -run 'TestProtocolProfile.*(ModelMapping|RawBody|ActualRequest)'
```

Expected: the top-level model maps once, every other byte is identical, large integers remain exact, and invalid JSON still returns a contextual error.

### [REVIEW_API-5] Validate header tokens and correct built-in auth fixtures

#### Problem

`validateConcreteProtocolProfile` relies on `textproto.CanonicalMIMEHeaderKey(...) == ""` (`packages/go/config/protocol_profile.go:573-577`), but invalid names such as `Bad Header` are returned unchanged and pass. The built-in `mimo_messages` fixture uses `X-Api-Key` (`protocol_profile.go:236-243`) although current Xiaomi documentation declares `api-key` or `Authorization: Bearer`; the expected table mirrors implementation instead of detecting this drift.

Before (`packages/go/config/protocol_profile.go:573-577`):

```go
if strings.TrimSpace(p.Auth.Header) == "" {
    return fmt.Errorf("profile %q: auth.header must not be empty", id)
}
if textproto.CanonicalMIMEHeaderKey(strings.TrimSpace(p.Auth.Header)) == "" {
    return fmt.Errorf("profile %q: auth.header %q is not a valid HTTP header name", id, p.Auth.Header)
}
```

#### Solution

Implement a local ASCII HTTP field-name token validator using the RFC token set (letters, digits, and `!#$%&'*+-.^_`\|`~`) and reject whitespace, colon, controls, separators, and non-ASCII bytes. Keep the existing Authorization/Bearer versus raw-header scheme matrix.

Set `mimo_messages` to raw `api-key`. Set `minimax_messages` to `Authorization` with `Bearer`, matching the checked primary reference while remaining within MiniMax's accepted forms. Replace implementation-derived expectations with an explicit built-in auth matrix covering every Chat/Messages variant and assert the actual outbound header in Node adapter HTTP tests. Record the documentation check date and official URLs in test comments without making network access part of tests.

After (representative):

```go
header := strings.TrimSpace(p.Auth.Header)
if !validHTTPFieldName(header) {
    return fmt.Errorf("profile %q: auth.header %q is not a valid HTTP header name", id, p.Auth.Header)
}
```

#### Modified Files and Checklist

- [ ] `packages/go/config/protocol_profile.go`: RFC token validation and supported MiniMax/MiMo built-in auth values.
- [ ] `packages/go/config/protocol_profile_test.go`: invalid-token tables and an independent exact built-in auth matrix.
- [ ] `apps/node/internal/adapters/openai_compat/protocol_profile_test.go`: actual outbound `api-key` and Authorization/Bearer assertions.

#### Test Strategy

Add invalid header fixtures for spaces, colon, tab/control, non-ASCII, empty, and separator characters plus valid punctuation-token cases. The auth matrix must spell out every expected header and scheme without reading the production map to generate expected values. Actual HTTP tests must prove the configured credential is moved to the selected header and the stale header is absent.

#### Verification

```bash
go test -count=1 ./packages/go/config/... -run 'TestProtocolProfile.*(Header|Auth)|TestBuiltInProtocolProfileCatalog'
go test -count=1 ./apps/node/internal/adapters/openai_compat -run 'TestProtocolProfile.*Auth'
```

Expected: all invalid field names fail at config validation, valid token names pass, and exact MiniMax/MiMo outbound auth matches the independent matrix.

## Modified Files Summary

| File | Items |
|------|-------|
| `packages/go/config/normalize.go` | REVIEW_API-3 |
| `packages/go/config/protocol_profile.go` | REVIEW_API-5 |
| `packages/go/config/protocol_profile_test.go` | REVIEW_API-3, REVIEW_API-5 |
| `apps/edge/internal/configrefresh/classify.go` | REVIEW_API-2 |
| `apps/edge/internal/configrefresh/provider_classify_test.go` | REVIEW_API-2 |
| `apps/edge/internal/configrefresh/node_runtime_classify_test.go` | REVIEW_API-2 |
| `apps/edge/internal/bootstrap/runtime_refresh_test.go` | REVIEW_API-2 |
| `apps/edge/internal/node/mapper.go` | REVIEW_API-3 |
| `apps/edge/internal/node/protocol_profile_mapper_test.go` | REVIEW_API-3 |
| `apps/edge/internal/service/provider_pool.go` | REVIEW_API-1 |
| `apps/edge/internal/service/provider_resolution.go` | REVIEW_API-3 |
| `apps/edge/internal/service/protocol_profile_test.go` | REVIEW_API-1, REVIEW_API-3 |
| `apps/node/internal/adapters/protocol_profile_config_test.go` | REVIEW_API-3 |
| `apps/node/internal/adapters/openai_compat/request.go` | REVIEW_API-4 |
| `apps/node/internal/adapters/openai_compat/protocol_profile_test.go` | REVIEW_API-4, REVIEW_API-5 |
| `scripts/e2e-openai-lemonade.sh` | REVIEW_API-3 |
| `agent-task/m-multi-provider-protocol-profile-native-messages/01_protocol_core/CODE_REVIEW-cloud-G09.md` | REVIEW_API-1, REVIEW_API-2, REVIEW_API-3, REVIEW_API-4, REVIEW_API-5 |

## Final Verification

Go test cache is not acceptable. Use the workspace cache/temp workaround already proven by review; do not clean it or create verification source code.

```bash
cd /config/workspace/iop-s0
mkdir -p .gocache .local/gotmp-g09-followup
export GOCACHE=/config/workspace/iop-s0/.gocache
export GOTMPDIR=/config/workspace/iop-s0/.local/gotmp-g09-followup
go version
protoc --version
flutter --version
command -v protoc-gen-dart
go test -count=1 ./packages/go/config/... -run 'Test(ProtocolProfile|LegacyProviderAlias|BuiltInProtocolProfile)'
go test -count=1 ./apps/edge/internal/configrefresh -run 'TestClassify(ProtocolProfile|ProviderProfile|ModelTokenCounter)'
go test -count=1 ./apps/edge/internal/bootstrap -run 'TestRefreshApplyDoesNotMutateOnProtocolProfileRestartRequired'
go test -count=1 ./apps/edge/internal/node ./apps/edge/internal/service -run 'Test.*ProtocolProfile|Test.*TunnelOperation'
go test -count=1 ./apps/node/internal/adapters ./apps/node/internal/adapters/openai_compat -run 'Test.*ProtocolProfile|Test.*LegacyEndpointPathFallback|Test.*OpenAICompat.*Models'
go test -count=1 ./apps/edge/internal/openai -run 'Test.*(ProtocolProfile|ProviderDispatch|Responses)'
make proto
make proto-dart
go test -count=1 ./packages/go/...
go test -count=1 ./packages/go/... -skip 'TestCLIWorkspacePreflightFailures|TestSerialIntegratorRestartRecoversInterruptedApplyByRollback'
go test -count=1 ./apps/edge/...
go test -count=1 ./apps/node/...
cd apps/client && flutter test test/iop_wire/generated_proto_import_test.dart
cd /config/workspace/iop-s0 && IOP_LEMONADE_MODE=fake ./scripts/e2e-openai-lemonade.sh
cd /config/workspace/iop-s0 && rg -n --sort path 'CanonicalMIMEHeaderKey|var payload map\[string\]any|Auth:.*X-Api-Key' packages/go/config/protocol_profile.go apps/node/internal/adapters/openai_compat/request.go
git diff --check
git status --short
```

Expected: every focused and full task-owned suite passes uncached; the first packages command either passes or reproduces only the two documented unrelated fixtures and the exact named-skip replacement passes; generators and Dart consumer remain current; adapter-backed fake Lemonade reaches only the loopback upstream and prints its PASS marker; the drift search has no matches in the named files; diff check is clean; unrelated roadmap edits remain present.

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