<!-- task=m-iop-hot-path-one-shot-execution/12+10,11_outer_turn_core plan=3 tag=REVIEW_API milestone-task=terminal-control -->

# Harden Hot Path stage lifecycle, framing, and output-cap boundaries

## For the Implementing Agent

Implement the checklist, run every verification command, and fill the implementation-owned sections of `CODE_REVIEW-cloud-G08.md` with actual notes and stdout/stderr. Keep the active files in place and report ready for review. If blocked, record only the exact blocker, attempted command/output, and resume condition in implementation-owned evidence. Do not ask the user, call user-input tools, create stop-state files, classify the next state, archive logs, or write `complete.log`; finalization belongs to code review.

## Background

The protocol-neutral outer-turn core passes its happy-path tests but does not yet preserve stage transport ownership or fail closed on malformed tunnel lifecycle. Its turn-wide output cap also applies only to text, allowing reasoning and tool arguments to bypass the S10 public-output boundary. This follow-up repairs those three contract gaps without wiring the core into direct/light handlers or adding endpoint codecs.

## Archive Evidence Snapshot

- The current pair will archive as `plan_cloud_G08_2.log` and `code_review_cloud_G09_2.log`; the review verdict is FAIL with 3 Required, 0 Suggested, and 0 Nit findings.
- Required findings: inject idempotent abort/graceful-close ownership instead of the no-op stage controller; reject tunnel `BODY`/`END` before `RESPONSE_START` and channel close before explicit completion; apply the one turn-wide cap to text, reasoning, and tool arguments with deterministic crossing-fragment behavior.
- Reviewer verification passed: targeted race test `ok iop/apps/edge/internal/openai 1.384s`; common race packages passed (`streamgate 2.171s`, `config 1.944s`, `openai 11.271s`, `service 7.183s`); `git diff --check` exited 0. These commands did not cover the required boundary variants.
- Split predecessors remain satisfied by `agent-task/archive/2026/08/m-iop-hot-path-one-shot-execution/10+07,09_light_flow/complete.log` and `agent-task/archive/2026/08/m-iop-hot-path-one-shot-execution/11+09,10_cleanup/complete.log`.

## Analysis

### Files Read

- `agent-task/m-iop-hot-path-one-shot-execution/12+10,11_outer_turn_core/PLAN-cloud-G08.md`
- `agent-task/m-iop-hot-path-one-shot-execution/12+10,11_outer_turn_core/CODE_REVIEW-cloud-G09.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/outer/anthropic-compatible-api.md`
- `agent-spec/runtime/stream-evidence-gate.md`
- `apps/edge/internal/openai/hot_path_stage_stream.go`
- `apps/edge/internal/openai/hot_path_terminal_control.go`
- `apps/edge/internal/openai/hot_path_terminal_control_test.go`
- `apps/edge/internal/openai/hot_path_dispatch.go`
- `apps/edge/internal/openai/stream_gate_dispatcher.go`
- `apps/edge/internal/openai/stream_gate_runtime.go`
- `apps/edge/internal/service/run_types.go`
- `apps/edge/internal/service/provider_tunnel.go`
- `packages/go/streamgate/runtime.go`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`

### SDD Criteria

- Approved SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-hot-path-one-shot-execution/SDD.md`; status `[승인됨]`, lock released.
- Metadata scope remains `milestone-task=terminal-control`; targeted scenario is S10.
- S10 and its Evidence Map require terminal-only stage handling, normalized ordering, one outer envelope, turn-scoped id/usage/output-cap aggregation, and exactly-once HTTP/logical terminal evidence. The checklist repairs resource ownership, strict framing, and full public-output cap coverage before the same fresh race/common commands can serve as S10 contribution evidence.

### Verification Context

- No separate verification handoff was supplied. Repository-native fallback came from `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, the active plan, S10, and related tests.
- Workdir is `/config/workspace/iop-s0`; `/config/.local/bin/go` reports `go1.26.2 linux/arm64`. The shared checkout is dirty only with the active task implementation/artifacts shown by `git status --short`.
- Required evidence is deterministic local Go verification with fresh `-race -count=1`; no credential, provider, remote runner, or external runtime is required. Cached output is not accepted.
- Confidence is high: the malformed-frame and cap paths are direct state-machine branches, and handle ownership is represented by idempotent `Close` plus the existing abort/graceful controller pattern.

### Test Coverage Gaps

- Existing stage tests cover valid response-start/body/end fragments but not `BODY` or `END` before `RESPONSE_START`, nor channel close before `END`.
- Existing runtime tests use a no-op controller and cannot prove success, error, or cancellation releases transport ownership exactly once.
- Existing cap test covers text only; it does not cover reasoning, tool arguments, crossing-fragment handling, or stage changes.

### Symbol References

- Changing `newHotPathStageRuntime` affects only `runHotPathStage` in `hot_path_terminal_control.go`.
- Changing `runHotPathStage` affects `TestHotPathStageRuntime` and both protocol rows in `TestHotPathStageProtocolFragments` in `hot_path_terminal_control_test.go`.
- No exported/public symbol is renamed or removed.

### Split Judgment

- Keep one corrective packet: source lifecycle, attempt ownership, and cap/terminal evidence are one stage-runtime invariant and must PASS together.
- Directory dependencies `10` and `11` are satisfied by the two exact archived predecessor `complete.log` paths in `Archive Evidence Snapshot`.

### Scope Rationale

- Exclude `hot_path_dispatch.go`, direct/light lifecycle integration, caller endpoint codecs, observability, external smoke, and roadmap/spec edits. Child 13 and protocol children own integration; S16 owns live-provider evidence.
- Do not change `packages/go/streamgate`; inject its existing `AttemptController` contract and reuse `CloseRequestResources` semantics.

### Final Routing

- `evaluation_mode=isolated-reassessment`; finalizer=`finalize-task-policy.sh pair`.
- Build closures are all true; scores are `2/2/1/1/1` (G07), base `local-fit`. Positive risks are `temporal_state`, `concurrent_consistency`, `boundary_contract`, and `structured_interpretation` (4); `large_indivisible_context=false`; recovery signals are `review_rework_count=1`, `evidence_integrity_failure=false`. Risk boundary routes build to `PLAN-cloud-G07.md`.
- Review closures are all true; scores are `2/2/1/2/1` (G08), official review routes to `CODE_REVIEW-cloud-G08.md`. No capability gap exists.

## Implementation Checklist

- [ ] [REVIEW_API-1] Make stage attempt ownership idempotently abort/close real transports and fail closed on incomplete or out-of-order tunnel framing, with deterministic lifecycle regressions.
- [ ] [REVIEW_API-2] Enforce the one outer-turn output cap across text, reasoning, and tool arguments and prove cap/terminal behavior across fragments and stages.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_API-1] Stage transport ownership and strict tunnel framing

**Problem:** `apps/edge/internal/openai/hot_path_terminal_control.go:453` hardcodes a no-op `AttemptController`, so `CloseRequestResources` cannot release a stage handle. `apps/edge/internal/openai/hot_path_stage_stream.go:157`, `:225`, and `:273` convert close-before-END or BODY/END-before-RESPONSE_START into a successful stage.

**Solution:** Require an injected idempotent attempt controller that implements abort plus graceful close and pass it to `streamgate.NewAttemptBinding`; remove the no-op fallback. Make the tunnel source track explicit response start and completion, convert malformed ordering/early close into a sanitized provider-error terminal, and preserve valid fragmented OpenAI/Anthropic decoding.

Before (`hot_path_terminal_control.go:453`):

```go
type hotPathStageController struct{}

func (hotPathStageController) AbortAttempt(context.Context) error { return nil }
```

After:

```go
type hotPathStageAttemptController interface {
	streamgate.AttemptController
	CloseAttempt(context.Context) error
}

func newHotPathStageRuntime(..., controller hotPathStageAttemptController) (...) {
	// The binding owns one real stage transport and CloseRequestResources closes it once.
}
```

**Modified Files and Checklist:**

- [ ] Modify `apps/edge/internal/openai/hot_path_terminal_control.go` to require and use an idempotent abort/graceful-close controller.
- [ ] Modify `apps/edge/internal/openai/hot_path_stage_stream.go` to reject BODY/END-before-start and close-before-END with sanitized provider-error evidence.
- [ ] Modify `apps/edge/internal/openai/hot_path_terminal_control_test.go` with `TestHotPathStageTransportOwnership` and table-driven `TestHotPathStageTunnelFraming` success/error/cancel cases.

**Test Strategy:** Use counting fake controllers/closers and bounded frame channels. Assert graceful success closes once without abort, cancellation/error aborts once, duplicate cleanup is a no-op, valid fragments still pass, and each malformed lifecycle yields one provider-error terminal and no public success.

**Verification:** The focused race command in Final Verification exits 0 and includes every lifecycle row.

### [REVIEW_API-2] Full public-output cap

**Problem:** `apps/edge/internal/openai/hot_path_terminal_control.go:171` applies the cap only to text; reasoning at `:182` and tool arguments at `:192` do not consume the shared budget.

**Solution:** Centralize remaining-rune accounting for every caller-visible delta. Preserve Unicode boundaries, mark cap exhaustion exactly once, use deterministic atomic handling for a tool fragment that would cross the remaining budget, reject later emission, and force the single public terminal reason to `length` across stage changes.

Before (`hot_path_terminal_control.go:170`):

```go
switch ev.Kind() {
case streamgate.EventKindTextDelta:
	text = t.applyOutputCapLocked(text)
case streamgate.EventKindReasoningDelta:
	t.reasoning.WriteString(text)
case streamgate.EventKindToolCallFragment:
	tool.args.WriteString(call.Arguments)
}
```

After:

```go
switch ev.Kind() {
case streamgate.EventKindTextDelta, streamgate.EventKindReasoningDelta:
	visible := t.consumeOutputBudgetLocked(delta)
case streamgate.EventKindToolCallFragment:
	visibleArgs := t.consumeAtomicToolFragmentLocked(call.Arguments)
}
```

**Modified Files and Checklist:**

- [ ] Modify `apps/edge/internal/openai/hot_path_terminal_control.go` with one shared public-output budget for all release kinds.
- [ ] Extend `apps/edge/internal/openai/hot_path_terminal_control_test.go` so `TestHotPathOuterTurnOutputCap` covers Unicode text, reasoning, tool fragments, crossing-fragment behavior, stage changes, `length`, and post-terminal rejection.

**Test Strategy:** Use exact ordered release assertions and a small rune cap. Verify the total visible payload never exceeds the cap, no locally truncated tool fragment is published, cap exhaustion survives stage replacement, and only one length terminal wins under race.

**Verification:** Focused and common fresh race commands exit 0; formatting, vet, and diff checks are empty.

## Dependencies and Execution Order

1. `10+07,09_light_flow` is satisfied by its archived PASS `complete.log`.
2. `11+09,10_cleanup` is satisfied by its archived PASS `complete.log`.
3. Implement REVIEW_API-1 before REVIEW_API-2, then run the full verification set.

## Modified Files Summary

| File | Item |
|---|---|
| `apps/edge/internal/openai/hot_path_stage_stream.go` | REVIEW_API-1 |
| `apps/edge/internal/openai/hot_path_terminal_control.go` | REVIEW_API-1, REVIEW_API-2 |
| `apps/edge/internal/openai/hot_path_terminal_control_test.go` | REVIEW_API-1, REVIEW_API-2 |
| `agent-task/m-iop-hot-path-one-shot-execution/12+10,11_outer_turn_core/CODE_REVIEW-cloud-G08.md` | Review evidence |

## Final Verification

```bash
go test -race -count=1 ./apps/edge/internal/openai -run 'TestHotPath(StageTransportOwnership|StageTunnelFraming|StageRuntime|StageProtocolFragments|OuterTurnOutputCap|OuterTurnTerminalRace)'
go test -race -count=1 ./packages/go/streamgate ./packages/go/config ./apps/edge/internal/openai ./apps/edge/internal/service
go vet ./apps/edge/internal/openai
gofmt -d apps/edge/internal/openai/hot_path_stage_stream.go apps/edge/internal/openai/hot_path_terminal_control.go apps/edge/internal/openai/hot_path_terminal_control_test.go
git diff --check
```

Expected: all commands exit 0; malformed tunnel lifecycles fail closed; attempt ownership closes/cancels exactly once; the complete public payload respects one turn-wide cap and terminates once with `length`; no race, vet, formatting, or diff error. Fresh `-count=1` output is mandatory. Repository Edge-Node diagnostics, supplemental E2E smoke, full-cycle execution, and credentialed provider smoke are not run because this corrective core remains deterministic and pre-integration; S16 owns live Hot Path smoke.

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