<!-- task=m-iop-hot-path-one-shot-execution/15+13_chat_gate plan=2 tag=REVIEW_API milestone-task=terminal-control,chat-gate -->

# Repair progressive Chat release and provider response identity

## For the Implementing Agent

Implement this review follow-up only. Preserve the approved selector-classification gate, keep all transport and logical correlation identifiers internal, and leave the active Plan/Review pair in place for the next review agent. Before changing the inner contract, follow the project `update-contract` workflow routed by `agent-ops/skills/common/router.md`.

## Background

The prior implementation added a Chat caller codec, but selected Light stages still complete through compatibility collectors before the codec writes any SSE. It also seeds the public outer identity from logical request or normalized run identifiers. The repair must make already-classified Light stages progressively release safe deltas while holding only the outer terminal, and must carry the provider-owned Chat response identity across normalized execution instead of substituting an internal ID.

## Archive Evidence Snapshot

- The failed implementation pair will be archived as `agent-task/m-iop-hot-path-one-shot-execution/15+13_chat_gate/plan_cloud_G09_1.log` and `agent-task/m-iop-hot-path-one-shot-execution/15+13_chat_gate/code_review_cloud_G10_1.log`.
- The archived review verdict is `FAIL` with two Required findings, zero Suggested findings, and zero Nit findings.
- Required finding 1: selected Light stages buffer through `collectPreset*Result` and `hotPathOuterTurn.released` until `hotPathChatOuterCodec.writeResponse`; no caller-visible delta is flushed before the provider/stage terminal.
- Required finding 2: the Chat outer identity is seeded from `requestID` and normalized `RunDispatch.RunID`, exposing internal logical/transport correlation on the public wire.
- Fresh review evidence passed the targeted race suite, the common race suite, `go vet ./apps/edge/...`, implementation-file formatting, and `git diff --check`; the failures are semantic coverage and contract failures, not evidence-integrity failures.
- Routing signals: `review_rework_count=1`, `evidence_integrity_failure=false`.

## Analysis

### Files Read

- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-hot-path-one-shot-execution.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-hot-path-one-shot-execution/SDD.md`
- `agent-contract/index.md`
- `agent-contract/outer/openai-compatible-api.md`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-spec/index.md`
- `agent-spec/input/openai-compatible-surface.md`
- `agent-test/local/rules.md`
- `apps/node/internal/adapters/openai_compat/execute.go`
- `apps/node/internal/adapters/openai_compat/request.go`
- `apps/node/internal/adapters/openai_compat/stream.go`
- `apps/node/internal/adapters/openai_compat/execute_test.go`
- `apps/node/internal/node/runtime_bridge.go`
- `apps/edge/internal/openai/chat_handler.go`
- `apps/edge/internal/openai/normalized_sse.go`
- `apps/edge/internal/openai/stream_gate_runtime.go`
- `apps/edge/internal/openai/hot_path_dispatch.go`
- `apps/edge/internal/openai/hot_path_light.go`
- `apps/edge/internal/openai/hot_path_stage_stream.go`
- `apps/edge/internal/openai/hot_path_terminal_control.go`
- `apps/edge/internal/openai/hot_path_chat_gate_test.go`
- `apps/edge/internal/openai/hot_path_direct_test.go`
- `agent-task/m-iop-hot-path-one-shot-execution/15+13_chat_gate/PLAN-cloud-G09.md`
- `agent-task/m-iop-hot-path-one-shot-execution/15+13_chat_gate/CODE_REVIEW-cloud-G10.md`
- `agent-task/m-iop-hot-path-one-shot-execution/15+13_chat_gate/plan_cloud_G09_0.log`
- `agent-task/m-iop-hot-path-one-shot-execution/15+13_chat_gate/code_review_cloud_G10_0.log`
- `agent-task/m-iop-hot-path-one-shot-execution/13+12_outer_turn_integration/complete.log`

### SDD and Contract Criteria

- SDD S10 requires one outer envelope, stable public tool/index space, aggregate usage, and exactly one per-turn terminal while stage terminals remain internal transition evidence.
- SDD S12 requires endpoint-native Chat deltas, finish semantics, and `[DONE]` across stage continuation.
- The OpenAI-compatible outer contract requires provider-reported response identity and keeps logical request IDs, run IDs, stage IDs, frame timestamps, and node IDs internal. Missing provider identity must fail closed before public commitment.
- The Edge-Node wire already preserves `RunEvent.metadata`; the normalized OpenAI adapter currently drops the upstream Chat chunk `id`. The repair must define one stable metadata key for that provider identity and preserve it through the existing runtime bridge without changing protobuf fields.
- Initial selector output remains buffered until immutable structural classification. Progressive release begins only after the request has been classified as Light; Direct selector output remains a collected response because it cannot be exposed before classification.

### Fresh Verification Context

- `go test -race -count=1 ./apps/edge/internal/openai -run 'TestHotPathChat|TestChatStreamSession'` passed.
- `go test -race -count=1 ./packages/go/streamgate ./packages/go/config ./apps/edge/internal/openai ./apps/edge/internal/service` passed.
- `go vet ./apps/edge/...`, implementation-file `gofmt -l`, and `git diff --check` passed with no output.
- No repository smoke, credentialed provider smoke, or full-cycle Pi execution was run. Actual Pi smoke remains assigned to child 21 and is not completion evidence for this follow-up.

### Test Coverage Gaps

- No handler test blocks a provider after a visible delta and proves that the caller has already received and flushed that delta.
- The mixed local/review fixture expects the logical request ID as the public Chat ID.
- The normalized fixture expects `RunDispatch.RunID` as the public Chat ID and provides no explicit provider response identity.
- The Node normalized OpenAI adapter has no test that preserves a stable upstream SSE `id` on RuntimeEvents or rejects missing/conflicting identities before a visible delta.

### Symbol References

- `hotPathChatOuterCodec.runInitialPresetTurn` and `dispatchPresetTurn` own the selector classification boundary; they must not expose selector control output before classification or redispatch it.
- `hotPathOuterTurn.releaseDelta` and `hotPathStageReleaseSink.Release` are the existing progressive Core release boundary, but they currently append only to memory.
- `newHotPathNormalizedStageSource`, `newHotPathTunnelStageSource`, and `runHotPathStage` are implemented incremental stage paths; production Light dispatch currently bypasses them through `collectPresetNormalizedResult` and `collectPresetTunnelResult`.
- `chatStreamSession.handlePayload` decodes upstream normalized OpenAI SSE chunks but `chatChunk` has no response ID field and emitted RuntimeEvents carry no public identity metadata.
- `openAIRunEventSource.NextEvent` is the existing RunEvent-to-Core adapter and must observe the identity metadata before returning its first visible normalized delta.
- No public Go symbol rename or protobuf schema change is required.

### Split Judgment

- Provider identity provenance and progressive release are one atomic public-wire invariant: the codec cannot flush the first delta until a non-internal identity is bound. Splitting them would either preserve buffering or permit an identity leak.
- Node adapter propagation is bounded to the normalized OpenAI producer; Edge consumes the metadata through the already-preserved wire map. Provider tunnel identity continues to come from decoded provider chunks.
- Exhaustive post-commit error mapping remains child 17 scope; actual Pi smoke remains child 21 scope.

### Scope Rationale

- Included: normalized OpenAI response-ID propagation, the matching inner wire contract entry, request-local outer identity binding, live Light-stage sources, progressive Chat SSE emission, and deterministic regression evidence.
- Excluded: selector-gate relaxation, `/v1/responses`, Anthropic caller encoding, protobuf field additions, provider selection, credential handling, observability, and roadmap mutation.

### Final Routing

- `evaluation_mode=prepare-follow-up`; the failed review is rework iteration 1 with trustworthy evidence.
- Build route: grade-boundary, scores `2/2/2/2/2`, loop risks `temporal_state,concurrent_consistency,boundary_contract,structured_interpretation,variant_product` (5), `large_indivisible_context=false`, recovery boundary false, producing `PLAN-cloud-G10.md`.
- Review route: official-review scores `2/2/2/2/2`, producing `CODE_REVIEW-cloud-G10.md` with `codex`, `gpt-5.6-sol`, reasoning effort `xhigh`.

## Implementation Checklist

- [ ] [REVIEW_API-1] Preserve one stable provider Chat response ID across normalized RuntimeEvents and fail closed on missing or conflicting identity without substituting `RunId`.
- [ ] [REVIEW_API-2] Wire already-classified streaming Light stages through live Core sources and flush each safe Chat delta immediately while holding one outer terminal.
- [ ] [REVIEW_API-3] Add Node and Edge regressions for early flush, single public identity, normalized identity propagation, and logical/run-ID non-exposure.
- [ ] Fill every implementation-owned section in `CODE_REVIEW-cloud-G10.md` with actual changes and fresh command output, then leave both active files in place for review.

### [REVIEW_API-1] Provider identity provenance

**Problem:** `chatChunk` discards upstream `id`; `completeEvent` therefore cannot preserve it, while `collectPresetNormalizedResult` initializes `normalizedStageOutput.ResponseID` from `RunDispatch.RunID`. The public codec consequently treats an internal transport ID as provider identity.

**Solution:** Add `id` decoding and a single-assignment identity field to `chatStreamSession`. Before emitting any reasoning/content delta, require a non-empty upstream ID observed on that or an earlier chunk; reject a conflicting later ID. Attach the stable value as `RunEvent.metadata["openai_response_id"]` to every visible delta and the complete event. Document this key in the Edge-Node runtime wire contract as provider-owned, stable for one run, and never synthesized from `run_id`. Preserve the existing runtime bridge map unchanged. Teach `openAIRunEventSource` to accept a Hot-Path-only event observer, and have `hotPathNormalizedStageSource` bind and validate this metadata before returning a visible Core event. Add a stage identity probe used by the release sink. For provider tunnels, expose the decoder's parsed response ID through the same probe. Remove `RunDispatch.RunID` and `RunEvent.RunId` as `normalizedStageOutput.ResponseID` fallbacks; missing normalized identity becomes a sanitized pre-commit failure.

**Before:**

```go
stage := normalizedStageOutput{ResponseID: selected.RunID}
if event.GetRunId() != "" {
    stage.ResponseID = event.GetRunId()
}
```

**After:**

```go
providerID := strings.TrimSpace(event.GetMetadata()["openai_response_id"])
if err := identity.Bind(providerID); err != nil {
    return providerIdentityError()
}
stage.ResponseID = identity.Value() // never RunId
```

**Modified Files and Checklist:**

- [ ] Modify `agent-contract/inner/edge-node-runtime-wire.md` to define `RunEvent.metadata["openai_response_id"]`, stability, provenance, and fail-closed consumption.
- [ ] Modify `apps/node/internal/adapters/openai_compat/stream.go` to decode, single-bind, validate, and attach the provider response ID before visible RuntimeEvents.
- [ ] Modify `apps/node/internal/adapters/openai_compat/request.go` so terminal metadata carries the same stable response ID.
- [ ] Modify `apps/edge/internal/openai/stream_gate_runtime.go` to support a request-local raw RunEvent observer without changing ordinary callers.
- [ ] Modify `apps/edge/internal/openai/hot_path_stage_stream.go` to implement normalized/tunnel stage identity probes and reject missing/conflicting provider identity.
- [ ] Modify `apps/edge/internal/openai/hot_path_dispatch.go` to remove internal run-ID response fallbacks and consume only the verified provider identity.

**Test Strategy:** Node tests assert stable identity metadata on deltas and completion, then assert missing-before-visible and conflicting IDs fail. Edge tests assert the public ID equals the explicit provider ID and that neither logical request IDs nor `RunDispatch.RunID` appear in body or SSE.

**Verification:** REVIEW_API-3 targeted Node and Edge commands exit 0 under `-race`.

### [REVIEW_API-2] Progressive Chat release after classification

**Problem:** production Light dispatch calls compatibility collectors and later replays `outer.releasedDeltas()` from `writeResponse`. This holds content/reasoning/tool deltas until the stage or entire turn has already terminated. The outer is also constructed before classification or from `requestID`, so the wrong identity is fixed before the first visible stage.

**Solution:** Create the Direct outer only after classification with the collected Direct provider identity. Create Light outer turns unbound, including caller tool-result continuations, and add a single-assignment `bindPublicResponseID` operation that rejects empty/internal fallback values. Give the outer turn a request-local release callback. The Chat codec lazily writes SSE headers and the one assistant-role chunk only after the first visible stage identity is bound, then serializes each released reasoning/content/tool fragment and flushes it from `hotPathStageReleaseSink.Release`; terminal finish, aggregate usage, and `[DONE]` remain in the final writer exactly once. Invoke callbacks outside the outer mutex while preserving release order and propagate writer failures back through Core.

For `stream=true` Light stages, replace compatibility collection in `submitHotPathStage` with `hotPathNormalizedStageSource` or `hotPathTunnelStageSource` plus `runHotPathStage`. Extend the release sink with a stage-local output projection so the Light state machine still receives assembled content/reasoning/tool calls and provider correlation after the held stage terminal. Preserve the existing collectors for non-stream requests and for the initial selector classification gate. The first visible Light stage binds the one outer public ID; later stage IDs remain usage/correlation inputs and never replace the outer ID.

**Before:**

```go
output, correlation, err := s.dispatchHotPathStage(ctx, r, snapshot)
runHotPathCollectedStage(ctx, outer, snapshot.StageID, output)
// writeResponse later replays outer.releasedDeltas()
```

**After:**

```go
output, correlation, err := s.dispatchHotPathStage(ctx, r, snapshot, outer)
// live stage source -> Core -> release sink -> Chat codec -> Flush
// stage terminal is retained for the Light transition; outer terminal is not.
```

**Modified Files and Checklist:**

- [ ] Modify `apps/edge/internal/openai/hot_path_terminal_control.go` to single-bind public identity, invoke ordered release callbacks safely, and expose a stage-local output projection without committing the outer terminal.
- [ ] Modify `apps/edge/internal/openai/hot_path_dispatch.go` to construct and run live normalized/tunnel stage sources for streaming Light dispatch while retaining collected initial-selector and non-stream paths.
- [ ] Modify `apps/edge/internal/openai/hot_path_light.go` to create unbound Light outers, pass them into stage dispatch, and remove compatibility replay for live streaming stages.
- [ ] Modify `apps/edge/internal/openai/normalized_sse.go` to lazily open one Chat SSE response after identity binding, emit and flush each released delta, and write one terminal/usage/`[DONE]` sequence.

**Test Strategy:** A synchronized ResponseWriter/Flusher and staged provider channel must prove that the handler has emitted role plus visible content before the provider END/complete event is released. The same test then releases terminal input and asserts one finish chunk, aggregate usage, and one `[DONE]`.

**Verification:** the targeted Chat test command completes without deadlock or race and the blocking assertion completes before provider terminal release.

### [REVIEW_API-3] Regression and verification evidence

**Problem:** all current tests can pass even when the full response is buffered, and two fixtures explicitly bless internal correlation as a public response ID.

**Solution:** Extend Node normalized adapter coverage with provider-ID propagation and fail-closed cases. Add a blocking Chat handler fixture that uses a real streaming Light dispatch and inspects flushed bytes before unblocking provider terminal. Update mixed local/review assertions to use the first visible provider ID for every public chunk and reject the logical request ID, stage IDs, and later provider IDs. Update normalized handler coverage to supply `openai_response_id`, assert it publicly, reject `RunDispatch.RunID`, and add a missing-identity pre-commit endpoint error case. Retain fragmented tools, cap, usage, non-stream, and one-terminal assertions.

**Modified Files and Checklist:**

- [ ] Modify `apps/node/internal/adapters/openai_compat/execute_test.go` for stable, missing, and conflicting provider response-ID RuntimeEvent evidence.
- [ ] Modify `apps/edge/internal/openai/hot_path_chat_gate_test.go` for pre-terminal flush, mixed-stage single identity, and internal-ID rejection.
- [ ] Modify `apps/edge/internal/openai/hot_path_direct_test.go` for normalized explicit identity and missing-identity fail-closed coverage.
- [ ] Record implementation notes and exact fresh outputs in `agent-task/m-iop-hot-path-one-shot-execution/15+13_chat_gate/CODE_REVIEW-cloud-G10.md`.

**Test Strategy:** Parse SSE structurally; use channels rather than sleeps; assert the first provider-owned ID is constant across all caller chunks, role appears once, visible delta precedes provider terminal, finish appears once, and `[DONE]` is last. Search public payloads for known logical/run/stage IDs.

**Verification:** run every Final Verification command from the repository root and record unabridged exit status/output.

## Dependencies and Execution Order

1. The completed predecessor remains `agent-task/m-iop-hot-path-one-shot-execution/13+12_outer_turn_integration/complete.log`.
2. Follow `update-contract` for the metadata contract, then implement REVIEW_API-1 producer/consumer identity provenance.
3. Implement REVIEW_API-2 on top of the verified identity probe; do not enable early flush before identity is bound.
4. Implement REVIEW_API-3 and run Final Verification.
5. Do not modify roadmap state or claim the child-21 Pi smoke.

## Modified Files Summary

| File | Item |
|---|---|
| `agent-contract/inner/edge-node-runtime-wire.md` | REVIEW_API-1 |
| `apps/node/internal/adapters/openai_compat/stream.go` | REVIEW_API-1 |
| `apps/node/internal/adapters/openai_compat/request.go` | REVIEW_API-1 |
| `apps/edge/internal/openai/stream_gate_runtime.go` | REVIEW_API-1 |
| `apps/edge/internal/openai/hot_path_stage_stream.go` | REVIEW_API-1 |
| `apps/edge/internal/openai/hot_path_dispatch.go` | REVIEW_API-1, REVIEW_API-2 |
| `apps/edge/internal/openai/hot_path_terminal_control.go` | REVIEW_API-2 |
| `apps/edge/internal/openai/hot_path_light.go` | REVIEW_API-2 |
| `apps/edge/internal/openai/normalized_sse.go` | REVIEW_API-2 |
| `apps/node/internal/adapters/openai_compat/execute_test.go` | REVIEW_API-3 |
| `apps/edge/internal/openai/hot_path_chat_gate_test.go` | REVIEW_API-3 |
| `apps/edge/internal/openai/hot_path_direct_test.go` | REVIEW_API-3 |
| `agent-task/m-iop-hot-path-one-shot-execution/15+13_chat_gate/CODE_REVIEW-cloud-G10.md` | REVIEW_API-3 |

## Final Verification

```bash
go test -race -count=1 ./apps/node/internal/adapters/openai_compat -run 'TestOpenAICompatExecute'
go test -race -count=1 ./apps/edge/internal/openai -run 'TestHotPathChat|TestHotPathDirect|TestHotPathOuterTurn|TestChatStreamSession'
go test -race -count=1 ./packages/go/streamgate ./packages/go/config ./apps/node/internal/adapters/openai_compat ./apps/edge/internal/openai ./apps/edge/internal/service
go vet ./apps/node/internal/adapters/openai_compat ./apps/edge/...
gofmt -l apps/node/internal/adapters/openai_compat/stream.go apps/node/internal/adapters/openai_compat/request.go apps/node/internal/adapters/openai_compat/execute_test.go apps/edge/internal/openai/stream_gate_runtime.go apps/edge/internal/openai/hot_path_stage_stream.go apps/edge/internal/openai/hot_path_dispatch.go apps/edge/internal/openai/hot_path_terminal_control.go apps/edge/internal/openai/hot_path_light.go apps/edge/internal/openai/normalized_sse.go apps/edge/internal/openai/hot_path_chat_gate_test.go apps/edge/internal/openai/hot_path_direct_test.go
git diff --check
```

Expected: every command exits 0; `gofmt -l` and `git diff --check` print nothing; a visible Chat delta is flushed before provider terminal release; one provider-owned response ID is stable across the outer turn; known logical request, run, and stage IDs never appear on the public wire; terminal, usage, and `[DONE]` appear exactly once.

After completing all changes, fill the implementation-owned sections in `CODE_REVIEW-cloud-G10.md` and stop with the active pair in place.
