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

# Cancel rejected Hot Path dispatches exactly once

## For the Implementing Agent

After implementation, fill the implementation-owned sections of `CODE_REVIEW-cloud-G06.md` with the actual changes and verification output, then stop with both active files in place. If blocked, record only the exact blocker and resume condition; do not archive files, create `complete.log`, or classify the next state.

## Background

The terminal-control implementation correctly fences registered active stages, but several paths reject a provider-pool result before that registration occurs. At that point Edge already owns the returned run, so it must send one exact `CANCEL_RUN` and close every returned handle before reporting the validation failure.

## Archive Evidence Snapshot

- The current loop will archive to `agent-task/m-iop-hot-path-one-shot-execution/16+14,15_terminal_disposition/plan_cloud_G09_2.log` and `agent-task/m-iop-hot-path-one-shot-execution/16+14,15_terminal_disposition/code_review_cloud_G10_2.log` with verdict `FAIL`, 1 Required finding, 0 Suggested findings, and 0 Nits.
- Required finding: selector and downstream validation/unsupported-path rejection after provider-pool dispatch can close or abandon normalized/tunnel handles without one exact `CancelRun(CANCEL_RUN)`, leaving hidden Node work.
- Fresh review evidence passed: focused race tests, the common Go race suite, `go vet ./apps/edge/internal/openai`, formatting inspection, and `git diff --check`; the defect is an uncovered ownership path.
- 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-cloud-G09.md`
- `agent-task/m-iop-hot-path-one-shot-execution/16+14,15_terminal_disposition/CODE_REVIEW-cloud-G10.md`
- `agent-roadmap/milestones/iop-hot-path-one-shot-execution.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-hot-path-one-shot-execution/SDD.md`
- `apps/edge/internal/openai/hot_path_dispatch.go`
- `apps/edge/internal/openai/hot_path_terminal_control.go`
- `apps/edge/internal/openai/hot_path_stage_stream.go`
- `apps/edge/internal/openai/hot_path_terminal_control_test.go`
- `apps/edge/internal/openai/server_test_support_test.go`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`

### SDD Criteria

- The approved and unlocked SDD carries milestone task `error-cancel`. S13 and its evidence map require endpoint failure/cancel/length semantics to terminate without partial success or hidden provider work. This repair closes the pre-registration rejection gap; endpoint byte/status mapping remains child 17 scope.

### Verification Context

- No handoff artifact applies. Review ran from `/config/workspace/iop-s0` on branch `feature/iop-hot-path-one-shot-execution`, commit `f79fe3c7`, with Go `go1.26.2 linux/arm64` in a shared dirty worktree.
- Fresh focused and common race commands passed, as did `go vet ./apps/edge/internal/openai`, formatting inspection, and `git diff --check`. No external credentials or live-provider verification are required for this repair.

### Test Coverage Gaps

- Existing exact-cancel tests begin after an active-stage controller is registered. They do not cover rejected normalized/tunnel results at selector or downstream validation/unsupported-path boundaries, nor assert both exact cancel count and handle close count there.

### Symbol References

- No public symbol is renamed or removed. The implementation may add one internal post-dispatch rejection helper and exercise it through package-local tests.

### Split Judgment

- Compact invariant: once provider-pool dispatch returns ownership to Edge, every local rejection must send one exact cancellation and close every returned handle. The helper and its normalized/tunnel regression matrix are indivisible because the tests are the ownership oracle.

### Scope Rationale

- Included: selector and downstream post-dispatch validation/unsupported-path rejection, exact cancel target/count, close count, and typed validation disposition.
- Excluded: endpoint status/body mapping, cleanup/orphan redesign, successful stage behavior, observation schema, and live-provider smoke.

### Final Routing

- `finalizer=finalize-task-policy.sh`, `finalizer_mode=pair`.
- Build: `build_base_route_basis=local-fit`, `build_route_basis=local-fit`, `build_large_indivisible_context=false`, `build_loop_risk_count=2`, `build_risk_boundary_matched=false`, `build_review_rework_count=1`, `build_evidence_integrity_failure=false`, `build_recovery_boundary_matched=false`, scores `1/2/1/1/1`, `build_lane=local`, `build_grade=G06`, `build_filename=PLAN-local-G06.md`.
- Review: `review_route_basis=official-review`, scores `1/2/1/1/1`, `review_lane=cloud`, `review_grade=G06`, `review_filename=CODE_REVIEW-cloud-G06.md`, `review_adapter=codex`, `review_model=gpt-5.6-sol`, `review_reasoning_effort=xhigh`.
- Risk families: `concurrent_consistency,boundary_contract`; recovery signals: `review_rework_count=1`, `evidence_integrity_failure=false`.

## Implementation Checklist

- [ ] [REVIEW_API-1] Cancel every post-dispatch selector/downstream rejection exactly once and close returned handles while preserving the validation disposition.
- [ ] [REVIEW_API-2] Add normalized/tunnel validation/unsupported-path exact-target/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_API-1] Post-dispatch rejection ownership

**Problem:** `collectPresetSelectorResult` can return from an unsupported path without disposing the returned handle. `runLivePresetSelectorResult` and `submitHotPathStage` close handles after evidence validation fails, and progressive/buffered unsupported-path branches can return without cancellation. Since `SubmitProviderPool` has already transferred ownership, Node may continue the exact run after Edge rejects it.

**Solution:** Add one internal rejection helper in `apps/edge/internal/openai/hot_path_dispatch.go` that builds cancellation from immutable `DispatchInfo`, sends `CancelRun(CANCEL_RUN)` exactly once using a detached context, and closes all non-nil returned normalized/tunnel handles exactly once. Route selector and downstream validation/unsupported-path failures through it, including malformed variants where the returned handle disagrees with `Path`, and preserve `hotPathDispositionValidationError` at the downstream boundary.

**Before:**

```go
if err := validateHotPathStageDispatch(snapshot, route, result.DispatchInfo); err != nil {
    if result.Run != nil {
        result.Run.Close()
    }
    if result.Tunnel != nil {
        result.Tunnel.Close()
    }
    return normalizedStageOutput{}, hotPathStageCorrelation{}, newHotPathDispositionError(
        hotPathDispositionValidationError, "stage_dispatch_validation", snapshot.StageID, err,
    )
}
```

**After:**

```go
if err := validateHotPathStageDispatch(snapshot, route, result.DispatchInfo); err != nil {
    s.rejectHotPathDispatch(result)
    return normalizedStageOutput{}, hotPathStageCorrelation{}, newHotPathDispositionError(
        hotPathDispositionValidationError, "stage_dispatch_validation", snapshot.StageID, err,
    )
}
```

The helper name is illustrative; keep the existing terminal controller as the single exact-once mechanism where practical.

**Modified Files and Checklist:**

- [ ] Modify `apps/edge/internal/openai/hot_path_dispatch.go` at the selector switches around lines 52-59 and 110-133 and the downstream validation/path switches around lines 1209-1240.

**Test Strategy:** Exercise the helper through selector and downstream call paths with fake dispatch ownership, including normalized, tunnel, and mismatched/unsupported path variants.

**Verification:** REVIEW_API-2 focused and common commands exit 0 with no race.

### [REVIEW_API-2] Rejected-dispatch regression evidence

**Problem:** Current terminal-control tests prove exact cancellation only after stage registration and therefore did not detect the pre-registration ownership leak.

**Solution:** Add a table-driven regression that covers selector/downstream validation and unsupported-path rejection for normalized and tunnel handles. Assert the full cancel tuple (`NodeRef`, `RunID`, adapter, target, session), exactly one cancellation, exactly one close for each returned handle, a typed `validation_error` where downstream policy owns disposition, and no duplicate cancellation when rejection cleanup is observed again.

**Modified Files and Checklist:**

- [ ] Extend `apps/edge/internal/openai/hot_path_terminal_control_test.go` with `TestHotPathRejectedDispatch...` rows and exact-target/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-G06.md`.

**Test Strategy:** Table-driven fake results provide both normalized and tunnel ownership and deterministic cancel/close counters; run under the race detector.

**Verification:** Run every Final Verification command and record actual output in the review stub.

## Dependencies and Execution Order

1. Archived child dependencies 14 and 15 already have PASS completion evidence.
2. Implement REVIEW_API-1, then REVIEW_API-2.

## Modified Files Summary

| File | Item |
|---|---|
| `apps/edge/internal/openai/hot_path_dispatch.go` | REVIEW_API-1 |
| `apps/edge/internal/openai/hot_path_terminal_control_test.go` | REVIEW_API-2 |
| `agent-task/m-iop-hot-path-one-shot-execution/16+14,15_terminal_disposition/CODE_REVIEW-cloud-G06.md` | 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
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, each rejected owned run receives one exact `CANCEL_RUN`, every returned handle closes once, validation failures retain typed disposition, and no race is reported.

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