<!-- task=m-iop-hot-path-one-shot-execution/16+14,15_terminal_disposition plan=4 tag=REVIEW_REVIEW_API milestone-task=error-cancel -->

# Make rejected Hot Path dispatch disposal idempotent and exhaustive

## For the Implementing Agent

Implement every checklist item, run the exact verification commands, and fill the implementation-owned sections of `CODE_REVIEW-cloud-G07.md` with actual notes and stdout/stderr. Keep both active files in place and report ready for review; finalization belongs to the code-review skill. If blocked, record only the exact blocker, attempted commands/output, and resume condition 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 prior repair added post-dispatch cancellation, but three unsupported-path branches still return without disposing the owned provider result. Its helper also sends another `CANCEL_RUN` and closes the same handles whenever cleanup observes the same rejection twice. The passing focused suite does not cover those branches and explicitly accepts duplicate disposal, so the exact-once ownership contract remains open.

## Archive Evidence Snapshot

- The current loop will archive to `agent-task/m-iop-hot-path-one-shot-execution/16+14,15_terminal_disposition/plan_local_G06_3.log` and `agent-task/m-iop-hot-path-one-shot-execution/16+14,15_terminal_disposition/code_review_cloud_G06_3.log` with verdict `FAIL`, 1 Required finding, 0 Suggested findings, and 0 Nits.
- Required finding: buffered selector and progressive/buffered downstream unsupported-path branches omit rejection disposal, while repeated helper use duplicates cancellation and handle closure; the claimed normalized/tunnel matrix is incomplete.
- Fresh review evidence passed: focused race `ok iop/apps/edge/internal/openai 2.740s`, common race `ok` for streamgate/config/openai/service, formatting inspection, and `git diff --check`. These passes do not exercise the missing paths.
- Roadmap carryover remains `milestone-task=error-cancel`; SDD S13 requires terminal failure without partial success or hidden provider work. This follow-up does not assert milestone completion.

## Analysis

### Files Read

- `agent-task/m-iop-hot-path-one-shot-execution/16+14,15_terminal_disposition/PLAN-local-G06.md`
- `agent-task/m-iop-hot-path-one-shot-execution/16+14,15_terminal_disposition/CODE_REVIEW-cloud-G06.md`
- `agent-task/m-iop-hot-path-one-shot-execution/16+14,15_terminal_disposition/plan_cloud_G09_2.log`
- `agent-task/m-iop-hot-path-one-shot-execution/16+14,15_terminal_disposition/code_review_cloud_G10_2.log`
- `agent-task/archive/2026/08/m-iop-hot-path-one-shot-execution/14+13_anthropic_gate/complete.log`
- `agent-task/archive/2026/08/m-iop-hot-path-one-shot-execution/15+13_chat_gate/complete.log`
- `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-contract/outer/openai-compatible-api.md`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `apps/edge/internal/openai/hot_path_dispatch.go`
- `apps/edge/internal/openai/hot_path_terminal_control_test.go`
- `apps/edge/internal/openai/server.go`
- `apps/edge/internal/openai/hot_path_stage_stream.go`
- `apps/edge/internal/openai/hot_path_selector.go`
- `apps/edge/internal/openai/normalized_sse.go`
- `apps/edge/internal/openai/anthropic_stream.go`
- `apps/edge/internal/service/provider_pool.go`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`

### SDD Criteria

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-hot-path-one-shot-execution/SDD.md`, status `[승인됨]`, lock released.
- Milestone contribution: `milestone-task=error-cancel`.
- Target scenario: S13. Its Evidence Map requires endpoint error/cancel/length regressions proving no custom partial-success state; the post-dispatch ownership portion also must leave no hidden provider work.
- The checklist therefore requires one idempotent disposal owner shared by every local rejection branch and a variant matrix that proves the immutable Edge-Node cancel tuple plus exact cancel/close counts.

### Verification Context

- No verification handoff was supplied. Repository-native inputs are `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, the active PLAN commands, the Go module, and the focused tests.
- Local preflight: `/config/workspace/iop-s0`, branch `feature/iop-hot-path-one-shot-execution`, HEAD `f79fe3c7`, shared dirty worktree, `/config/.local/bin/go`, `go1.26.2 linux/arm64`.
- Fresh reviewer commands passed: focused and common `go test -race -count=1`, `gofmt -d`, and `git diff --check`.
- No external runner, credential, provider, device, port, or long-running runtime is required. Credentialed provider smoke and live Claude/Pi execution remain the separate S16 `hot-smoke` scope.
- Confidence: high. The uncovered returns and duplicate helper behavior are directly visible and deterministic.

### Test Coverage Gaps

- `collectPresetSelectorResult` unsupported path: uncovered; it returns at `hot_path_dispatch.go:58` without cancellation or close.
- `submitHotPathStage` progressive unsupported path: uncovered; it returns at `hot_path_dispatch.go:1226` without cancellation or close.
- `submitHotPathStage` buffered unsupported path: uncovered; it returns at `hot_path_dispatch.go:1237` without cancellation or close.
- Repeated rejection observation: covered with the wrong oracle; `TestHotPathRejectedDispatchHelperIdempotentCancelCount` expects two cancels.
- Tunnel selector rejection: the test checks only that an error exists and does not assert target/count/close.
- Downstream rejection: only normalized dispatch validation is covered; tunnel validation and unsupported normalized/tunnel/malformed handle variants are absent.

### Symbol References

- No public symbol is renamed or removed.
- The internal `rejectHotPathDispatch` call sites are limited to `apps/edge/internal/openai/hot_path_dispatch.go` and direct package tests; replace them consistently with one result-scoped owner.

### Split Judgment

- Keep one plan. The indivisible invariant is: one provider-pool result transfers ownership once, and every selector/downstream local rejection must cause exactly one immutable-target cancel and exactly one close per returned handle, even if rejection cleanup is observed repeatedly.
- Split predecessors are satisfied: index 14 by `agent-task/archive/2026/08/m-iop-hot-path-one-shot-execution/14+13_anthropic_gate/complete.log`, and index 15 by `agent-task/archive/2026/08/m-iop-hot-path-one-shot-execution/15+13_chat_gate/complete.log`.

### Scope Rationale

- Included: rejection ownership in `hot_path_dispatch.go` and deterministic package-local regressions in `hot_path_terminal_control_test.go`.
- Excluded: endpoint status/body mapping, successful stage streaming, cleanup/orphan redesign, service/provider-pool schema, observation fields, roadmap mutation, and credentialed/live-provider smoke.

### Final Routing

- `evaluation_mode=isolated-reassessment`; finalizer `finalize-task-policy.sh pair`; build/review closures are all true (`scope`, `context`, `verification`, `evidence`, `ownership`, `decision`).
- Build scores `1/2/1/2/1` produce `G07`; base basis `local-fit`, final basis `recovery-boundary`, `large_indivisible_context=false`, risks `concurrent_consistency,boundary_contract,variant_product` (3), `review_rework_count=2`, `evidence_integrity_failure=true`, route `cloud`, filename `PLAN-cloud-G07.md`.
- Review scores `1/2/1/2/1` produce `G07`; basis `official-review`, route `cloud`, filename `CODE_REVIEW-cloud-G07.md`, adapter `codex`, model `gpt-5.6-sol`, reasoning `xhigh`.

## Implementation Checklist

- [ ] [REVIEW_REVIEW_API-1] Replace the rejection helper with one result-scoped idempotent disposal owner and route every selector/downstream rejection through it while preserving typed validation disposition.
- [ ] [REVIEW_REVIEW_API-2] Add table-driven buffered/live, selector/downstream, normalized/tunnel/unsupported/repeated-observation exact tuple/count/close regressions and run fresh focused/common verification.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_REVIEW_API-1] Result-scoped exact-once rejection ownership

**Problem:** `apps/edge/internal/openai/hot_path_dispatch.go:58`, `:1226`, and `:1237` return after provider-pool ownership transfer without cancellation or handle close. At `:1259`, each helper invocation independently calls `sendCancelRun` and `Close`, so a repeated observation duplicates both actions.

**Solution:** Replace the stateless helper with a result-scoped rejection owner backed by the existing `hotPathStageTransportController` exact-once claim. Construct that owner exactly once after each non-nil provider-pool result is received, make its close callback close every non-nil returned handle, and call `AbortAttempt(context.Background())` from all selector and downstream validation/unsupported-path branches. Repeated aborts on the same owner must be no-ops. Wrap downstream unsupported paths in the existing `validation_error` disposition with stable source/stage ownership.

Before (`apps/edge/internal/openai/hot_path_dispatch.go:1220`):

```go
switch result.Path {
case edgeservice.ProviderPoolPathNormalized:
    return s.runHotPathLiveNormalizedStage(ctx, snapshot, outer, result.Run, result.DispatchInfo)
case edgeservice.ProviderPoolPathTunnel:
    return s.runHotPathLiveTunnelStage(ctx, snapshot, outer, result.Tunnel, result.DispatchInfo)
default:
    return normalizedStageOutput{}, hotPathStageCorrelation{}, fmt.Errorf("hot path stage returned unsupported execution path %q", result.Path)
}
```

After:

```go
rejection := s.newHotPathRejectedDispatchOwner(result)
// Every local rejection uses the same owner instance.
if err := rejection.AbortAttempt(context.Background()); err != nil {
    s.logger.Warn("hot path rejected dispatch cancellation failed", zap.Error(err))
}
return normalizedStageOutput{}, hotPathStageCorrelation{}, newHotPathDispositionError(
    hotPathDispositionValidationError, "stage_dispatch_path", snapshot.StageID,
    fmt.Errorf("hot path stage returned unsupported execution path %q", result.Path),
)
```

**Modified Files and Checklist:**

- [ ] Modify `apps/edge/internal/openai/hot_path_dispatch.go` to create one rejection owner per result and use it at buffered selector, live selector, downstream validation, and progressive/buffered unsupported-path exits.

**Test Strategy:** Covered by REVIEW_REVIEW_API-2; do not add a new package or public API.

**Verification:** The focused race command exits 0 and every rejection row observes one cancel and one close per owned handle.

### [REVIEW_REVIEW_API-2] Exhaustive rejection ownership matrix

**Problem:** `apps/edge/internal/openai/hot_path_terminal_control_test.go:943` expects duplicate cancellation, `:1034` omits tunnel target/count/close assertions, and `:1084` covers only one normalized downstream validation case. The passing regex therefore cannot detect the three uncovered production branches.

**Solution:** Replace helper-only fragments with table-driven tests that invoke the real buffered/live selector and downstream entry points. Cover normalized, tunnel, unknown path, malformed both-handle ownership, validation mismatch, progressive/buffered selection, and repeated rejection observation. Every row must assert `NodeRef`, `RunID`, adapter, target, session, `CANCEL_RUN`, total cancel count `1`, and close count `1` for each non-nil handle; downstream validation/unsupported rows must assert the typed `validation_error` disposition.

Before (`apps/edge/internal/openai/hot_path_terminal_control_test.go:955`):

```go
srv.rejectHotPathDispatch(result)
srv.rejectHotPathDispatch(result)
if len(svc.cancelCallsSnapshot()) != 2 {
    t.Fatal("expected duplicate cancellation")
}
```

After:

```go
owner := srv.newHotPathRejectedDispatchOwner(result)
_ = owner.AbortAttempt(context.Background())
_ = owner.AbortAttempt(context.Background())
assertExactRejectedDispatch(t, svc.cancelCallsSnapshot(), result.DispatchInfo, 1)
assertHandleCloseCounts(t, result, 1)
```

**Modified Files and Checklist:**

- [ ] Modify `apps/edge/internal/openai/hot_path_terminal_control_test.go` with table-driven entry-point regressions and shared exact tuple/count/close assertions.
- [ ] Record actual implementation and verification evidence in `agent-task/m-iop-hot-path-one-shot-execution/16+14,15_terminal_disposition/CODE_REVIEW-cloud-G07.md`.

**Test Strategy:** Add `TestHotPathRejectedDispatchExactOnceMatrix` and retain focused race coverage through the `TestHotPathRejectedDispatch` prefix. Use only deterministic fake run/tunnel handles and `rejectPoolService`; no network/provider process.

**Verification:** Run every Final Verification command; all exit 0 with no race, formatting, vet, or diff error.

## Dependencies and Execution Order

1. Archived predecessor 14 is satisfied by `agent-task/archive/2026/08/m-iop-hot-path-one-shot-execution/14+13_anthropic_gate/complete.log`.
2. Archived predecessor 15 is satisfied by `agent-task/archive/2026/08/m-iop-hot-path-one-shot-execution/15+13_chat_gate/complete.log`.
3. Implement REVIEW_REVIEW_API-1, then REVIEW_REVIEW_API-2.

## Modified Files Summary

| File | Item |
|---|---|
| `apps/edge/internal/openai/hot_path_dispatch.go` | REVIEW_REVIEW_API-1 |
| `apps/edge/internal/openai/hot_path_terminal_control_test.go` | REVIEW_REVIEW_API-2 |
| `agent-task/m-iop-hot-path-one-shot-execution/16+14,15_terminal_disposition/CODE_REVIEW-cloud-G07.md` | REVIEW_REVIEW_API-2 |

## Final Verification

```bash
go test -race -count=1 ./apps/edge/internal/openai -run 'TestHotPath(RejectedDispatch|TerminalDisposition|ActiveStageCancel|CancelCompleteRace|Cleanup)'
go test -race -count=1 ./packages/go/streamgate ./packages/go/config ./apps/edge/internal/openai ./apps/edge/internal/service
go test -count=1 ./apps/edge/...
go vet ./apps/edge/...
gofmt -d apps/edge/internal/openai/hot_path_dispatch.go apps/edge/internal/openai/hot_path_terminal_control_test.go
git diff --check
```

Expected: every command exits 0; all selector/downstream rejection variants send one exact `CANCEL_RUN`, close each returned handle once, keep typed validation disposition, and remain idempotent under repeated observation.

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