<!-- task=m-iop-hot-path-one-shot-execution/19+17,18_observation_lifecycle plan=3 tag=REVIEW_REVIEW_API milestone-task=route-observability -->

# Correct post-write Hot Path terminal disposition ownership

## For the Implementing Agent

Implement only this plan. Run every verification command exactly as written, paste actual stdout/stderr into `CODE_REVIEW-cloud-G07.md`, complete its implementation-owned sections, and leave both active files in place for official review. If blocked, record the exact blocker, attempted command/output, and resume condition only in those implementation-owned evidence fields. Do not ask the user, call user-input tools, create control-plane stop files, classify the next state, archive task files, or write `complete.log`.

## Background

The observation lifecycle now has bounded production logging and broad exact-trace coverage, but response-write cancellation still loses to provisional terminal outcomes on untested exits. Direct writes hard-code `provider_error`, while Light length/output-budget exits publish `length` before the endpoint write can select `caller_cancel`. This follow-up makes the post-write winning-disposition rule uniform and proves it for both supported protocols.

## Archive Evidence Snapshot

- `agent-task/m-iop-hot-path-one-shot-execution/19+17,18_observation_lifecycle/plan_cloud_G09_2.log` and `agent-task/m-iop-hot-path-one-shot-execution/19+17,18_observation_lifecycle/code_review_cloud_G09_2.log` contain the reviewed plan, implementation evidence, and FAIL verdict for post-write terminal ownership.
- Fresh reviewer verification passed the submitted targeted race suite (`ok iop/apps/edge/internal/openai 7.399s`) and common race suite, but a temporary direct-write probe failed because `context.Canceled` produced one `provider_error` terminal instead of `caller_cancel`; the temporary probe was removed after capture.
- The directory predecessors remain satisfied by `agent-task/archive/2026/08/m-iop-hot-path-one-shot-execution/17+14,15,16_endpoint_error_matrix/complete.log` and `agent-task/archive/2026/08/m-iop-hot-path-one-shot-execution/18+17_observation_schema/complete.log`. Roadmap contribution remains `route-observability` under SDD S15.

## Analysis

### Files Read

- `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`
- `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-spec/runtime/stream-evidence-gate.md`
- `agent-spec/input/openai-compatible-surface.md`
- `agent-contract/outer/openai-compatible-api.md`
- `agent-contract/outer/anthropic-compatible-api.md`
- `apps/edge/internal/openai/hot_path_direct.go`
- `apps/edge/internal/openai/hot_path_light.go`
- `apps/edge/internal/openai/hot_path_cleanup.go`
- `apps/edge/internal/openai/hot_path_terminal_control.go`
- `apps/edge/internal/openai/hot_path_observation_test.go`
- `apps/edge/internal/openai/hot_path_light_test.go`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`

### SDD Criteria

- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-hot-path-one-shot-execution/SDD.md` is approved and unlocked. The first-line Milestone Task id is `route-observability`.
- Acceptance S15 requires joined raw-free metrics/logs for direct, Light, and failure requests. Its Evidence Map requires raw-free field-allowlist tests; S13's closed caller-cancel/length meanings constrain the outcome represented by S15 evidence.
- The checklist therefore keeps endpoint behavior unchanged, fixes only the observed winning disposition and ownership order, and requires exact projection plus metric-delta regressions across both protocols.

### Verification Context

- No verification handoff was supplied. Repository-native evidence came from the active SDD/spec/contracts, the direct and Light response exits, the existing cleanup post-write resolver, and lifecycle fixtures.
- Commands run from `/config/workspace/iop-s0`: the fresh targeted race suite passed (`ok iop/apps/edge/internal/openai 7.399s`); the fresh common race suite passed (`streamgate 2.769s`, `config 2.607s`, `openai 32.893s`, `service 9.372s`); `gofmt -d` and `git diff --check` exited 0 without output.
- A temporary deterministic probe used `cancelingHotPathResponseWriter` against `runDirectTurn`; it failed with `Disposition:provider_error`, proving the defect. The probe file was removed and `git diff --check` remained clean.
- Constraint: official verification must not run `iop-agent`. No external runner, credential, device, or network dependency is required. The worktree contains unrelated task changes; modify only the exact files claimed below. Confidence is high because the defect and oracle are deterministic in-process Go paths.

### Test Coverage Gaps

- `TestHotPathObservationLifecycle_CallerWriteFailure` covers only the cleanup-ending Light path, which already resolves after the write.
- No test covers response-write cancellation in direct final or tool-turn responses.
- No test covers response-write cancellation in the five Light provider-length/output-budget terminal branches.
- Existing lifecycle assertions do not compare exact terminal metric deltas for these write-failure variants.

### Symbol References

- No public or internal symbol rename/removal is planned.
- Direct call sites to change: the tool response write at `apps/edge/internal/openai/hot_path_direct.go:96` and final response write at `apps/edge/internal/openai/hot_path_direct.go:111`.
- Light call sites to converge: terminal exits at `apps/edge/internal/openai/hot_path_light.go:867`, `:917`, `:933`, `:943`, and `:975`.

### Split Judgment

- Do not split. The direct and Light edits plus their tests enforce one indivisible invariant: every logical terminal projection is emitted exactly once from the winning disposition selected after the endpoint write.

### Scope Rationale

- Include only direct write-error classification, Light length/output-budget post-write observation, and deterministic lifecycle/metric regressions.
- Exclude cleanup-ending terminals because `writeHotPathTerminal` already follows the required order, schema/logger changes, route/admission/stage observation, TTL/orphan behavior, dashboard work, external provider smoke, and unrelated milestone tasks.

### Final Routing

- `evaluation_mode=isolated-reassessment`; build and review each have `scope_closed=true`, `context_closed=true`, `verification_closed=true`, `evidence_trusted=true`, `ownership_closed=true`, and `decision_closed=true`; no capability gap exists.
- Finalizer: `finalize-task-policy.sh pair`.
- Build scores `1/2/1/2/1` = G07. `large_indivisible_context=false`; matched positive risks are `temporal_state`, `concurrent_consistency`, `boundary_contract`, and `variant_product` (4); `review_rework_count=2`; `evidence_integrity_failure=true`.
- Finalizer output: build `recovery-boundary` -> `PLAN-cloud-G07.md`; review scores `1/2/1/2/1` = G07 and `official-review` -> `CODE_REVIEW-cloud-G07.md` with Codex `gpt-5.6-sol` xhigh.

## Implementation Checklist

- [ ] [REVIEW_REVIEW_API-1] Make direct and Light terminal observation select the winning disposition after the endpoint write while preserving exactly one terminal owner.
- [ ] [REVIEW_REVIEW_API-2] Add both-protocol direct and Light length/output-budget write-cancellation regressions with exact traces and terminal metric deltas.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_REVIEW_API-1] Post-write winning disposition for every affected exit

**Problem:** `apps/edge/internal/openai/hot_path_direct.go:96-100` and `:111-117` classify every response-write failure as `provider_error`, even when `errors.Is(writeErr, context.Canceled)`. `apps/edge/internal/openai/hot_path_light.go:867-872`, `:917-922`, `:933-937`, `:943-948`, and `:975-980` emit the `length` terminal before `writeHotPathStageResponse`, so a caller-canceled write cannot win. This contradicts the already-correct cleanup pattern at `apps/edge/internal/openai/hot_path_cleanup.go:365-408`.

**Solution:** Resolve direct write failures through the existing closed error classifier before the deferred terminal observer runs. Replace the five Light length/output-budget sequences with one helper that writes the endpoint response, resolves the intended `length` against the write result through `resolveHotPathObservedDisposition`, closes preset state, and emits one Light terminal with the winning closed kind. Do not add a second terminal owner or change success/error wire encoding.

Before (`hot_path_direct.go:96-100`, `hot_path_light.go:917-922`):

```go
if err := s.writeDirectResponse(turn, visible); err != nil {
    directTerminal = hotPathTerminalDispositionProviderError
    reachedTerminal = true
    s.terminalPresetRequest(turn.RequestID, turn.OwnerEdgeID)
    return err
}

s.terminalPresetRequest(requestID, s.edgeIDValue())
s.observeHotPathLightLengthTerminal(r.Context(), requestID, dispatch.Preset.ID)
return s.writeHotPathStageResponse(w, r, dispatch, protocol, stream, requestID, output)
```

After:

```go
if err := s.writeDirectResponse(turn, visible); err != nil {
    directTerminal = hotPathTerminalDispositionFromKind(hotPathDispositionForError(err))
    reachedTerminal = true
    s.terminalPresetRequest(turn.RequestID, turn.OwnerEdgeID)
    return err
}

return s.writeHotPathLightLengthTerminal(w, r, dispatch, protocol, stream, requestID, output)
```

The Light helper must call `writeHotPathStageResponse` before `observeHotPathTerminal`, use intended `hotPathDispositionLength`, and preserve the response write error as its return value.

**Modified Files and Checklist:**

- [ ] Modify `apps/edge/internal/openai/hot_path_direct.go` so both response-write error branches classify cancellation/timeout/provider error through the closed error mapper before the deferred exact-once terminal emission.
- [ ] Modify `apps/edge/internal/openai/hot_path_light.go` so every non-cleanup length/output-budget terminal uses one post-write resolver and observer owner.

**Test Strategy:** Regression tests are mandatory under REVIEW_REVIEW_API-2. No production-only test seam or new exported API is allowed.

**Verification:** Run the focused command in Final Verification; direct and Light write cancellation must each produce one `caller_cancel` terminal for OpenAI and Anthropic.

### [REVIEW_REVIEW_API-2] Exact both-protocol regression evidence

**Problem:** `apps/edge/internal/openai/hot_path_observation_test.go:2211-2243` proves post-write cancellation only for cleanup-ending Light requests. It cannot detect the direct hard-coded disposition or the pre-write non-cleanup Light terminal branches, and it does not assert the affected terminal metric label deltas.

**Solution:** Extend the existing lifecycle test harness with table-driven OpenAI/Anthropic rows that drive direct final and tool response writes through `cancelingHotPathResponseWriter`, then drive representative provider-length and output-budget Light terminals through the same failing writer. For every row, compare the exact ordered projection trace, assert exactly one terminal with `caller_cancel`, assert `length` and `provider_error` terminal counters do not increment for that request's unique edge/mode labels, and assert the `caller_cancel` counter increments by exactly one. Ensure the fixture confirms `ResponseWriter.Write` was reached.

Before (`hot_path_observation_test.go:2211-2243`):

```go
func TestHotPathObservationLifecycle_CallerWriteFailure(t *testing.T) {
    // cleanup-ending Light only
}
```

After:

```go
func TestHotPathObservationLifecycle_DirectCallerWriteFailure(t *testing.T) {
    // protocols x final/tool response; exact caller_cancel trace and metric delta
}

func TestHotPathObservationLifecycle_LightLengthCallerWriteFailure(t *testing.T) {
    // protocols x provider-length/output-budget; exact caller_cancel trace and metric delta
}
```

Reuse existing scripted providers, observer capture, `cancelingHotPathResponseWriter`, and `hotPathMetricValue`; do not weaken comparisons to lower bounds.

**Modified Files and Checklist:**

- [ ] Modify `apps/edge/internal/openai/hot_path_observation_test.go` with both regression tests, exact projections, write-exercised assertions, and exact per-label metric deltas.
- [ ] Fill actual implementation notes, deviations, decisions, and raw command output in `agent-task/m-iop-hot-path-one-shot-execution/19+17,18_observation_lifecycle/CODE_REVIEW-cloud-G07.md`.

**Test Strategy:** Add `TestHotPathObservationLifecycle_DirectCallerWriteFailure` and `TestHotPathObservationLifecycle_LightLengthCallerWriteFailure`. Each must cover OpenAI and Anthropic; the direct table must include final and tool response writes, and the Light table must include provider-length and exhausted-output-budget exits.

**Verification:** Run the focused command, then the existing targeted lifecycle command. Both must pass fresh under `-race`, with exact trace and metric assertions.

## Dependencies and Execution Order

1. Directory predecessors 17 and 18 remain satisfied by the `complete.log` paths in Archive Evidence Snapshot.
2. Implement REVIEW_REVIEW_API-1 before REVIEW_REVIEW_API-2 so every new row exercises the final ownership path.

## Modified Files Summary

| File | Item |
|---|---|
| `apps/edge/internal/openai/hot_path_direct.go` | REVIEW_REVIEW_API-1 |
| `apps/edge/internal/openai/hot_path_light.go` | REVIEW_REVIEW_API-1 |
| `apps/edge/internal/openai/hot_path_observation_test.go` | REVIEW_REVIEW_API-2 |
| `agent-task/m-iop-hot-path-one-shot-execution/19+17,18_observation_lifecycle/CODE_REVIEW-cloud-G07.md` | REVIEW_REVIEW_API-2 |

## Final Verification

```bash
go test -race -count=1 ./apps/edge/internal/openai -run '^TestHotPathObservationLifecycle_(DirectCallerWriteFailure|LightLengthCallerWriteFailure)$'
go test -race -count=1 ./apps/edge/internal/openai -run 'TestHotPath(ObservationLifecycle|ObservationRejectsRawValues|MetricLabels|ObserverFailureIsolation|EndpointTerminalMatrix|Cleanup)'
go test -race -count=1 ./packages/go/streamgate ./packages/go/config ./apps/edge/internal/openai ./apps/edge/internal/service
gofmt -d apps/edge/internal/openai/hot_path_direct.go apps/edge/internal/openai/hot_path_light.go apps/edge/internal/openai/hot_path_observation_test.go
git diff --check
```

Expected: every command exits 0; all Go tests are fresh and race-clean; both protocols produce exactly one post-write `caller_cancel` terminal for direct final/tool and Light provider-length/output-budget write failures; the exact `caller_cancel` terminal counter increases by one per isolated row while conflicting `length`/`provider_error` labels do not increase; `gofmt -d` and `git diff --check` print no output. Cached test output is not acceptable because every Go command uses `-count=1`. The commands must not invoke `iop-agent`.

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