<!-- task=m-iop-owned-single-request-agent-execution/23+22_error_cancel plan=2 tag=API milestone-task=error-cancel -->

# Closed single-request error, cancel, and length terminals

## For the Implementing Agent

Filling the implementation-owned sections of `CODE_REVIEW-cloud-G10.md` is the mandatory last implementation step. Run every verification command, paste actual output, keep both active files in place, and report ready for review; only the official code-review skill may append a verdict, archive files, write `complete.log`, or classify the next state. If blocked, record the exact blocker, attempted commands/output, and resume condition only in the review evidence fields; do not ask the user, call a user-input tool, create a control-plane stop file, or change the owner or scope.

## Background

The activated single-request executor will preserve one controller and one outer Anthropic request, but the current service and projector expose only a generic failure/cancel distinction and always finalize success as `end_turn`. SDD S11 requires every provider/tool timeout, budget exhaustion, repetition/no-progress, malformed call, context/output limit, and disconnect to converge through one closed terminal policy without fallback, partial success, or a second Claude request.

## Archive Evidence Snapshot

- Immediate prior plan: `agent-task/m-iop-owned-single-request-agent-execution/23+22_error_cancel/plan_cloud_G10_1.log`; immediate prior pristine review stub: `agent-task/m-iop-owned-single-request-agent-execution/23+22_error_cancel/code_review_cloud_G10_1.log`. The earlier semantic snapshot remains in the matching `_0.log` files.
- No implementation evidence or official verdict existed. Fresh source review found that the prior plan added a disposition only to `SingleRequestResult`, while current failed/cancelled progress carries no result; that design could not deliver the classified failure/cancel reason to either Anthropic projector.
- This replan preserves the corrected owner/write boundary and S11 matrix, and explicitly carries one validated, raw-free terminal candidate through envelope/result/progress so finalizing, failed, and cancelled paths are all projectable.

## Analysis

### Files Read

- Plan 2 freshly reread the milestone/SDD, immediate prior pair/log, current service coordinator and terminal observation code, buffered/SSE projectors and their full tests, current contract/spec owners, and predecessor/consumer plans. The unchanged entries below preserve the analyzed base recorded by the prior pair; no prior test output is promoted as fresh verification.
- `AGENTS.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/common/rules-agent-spec.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/refine-plans/SKILL.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/finalize-task-routing/SKILL.md`
- `agent-ops/skills/common/sync-milestone-workstate/SKILL.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/node-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-test/local/testing-smoke.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-owned-single-request-agent-execution.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md`
- `agent-spec/index.md`
- `agent-spec/runtime/edge-node-execution.md`
- `agent-spec/runtime/stream-evidence-gate.md`
- `agent-spec/input/openai-compatible-surface.md`
- `agent-contract/index.md`
- `agent-contract/outer/anthropic-compatible-api.md`
- `agent-contract/inner/execution-runtime.md`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `apps/edge/internal/service/single_request.go`
- `apps/edge/internal/service/single_request_types.go`
- `apps/edge/internal/service/single_request_tool_loop.go`
- `apps/edge/internal/service/single_request_tool_types.go`
- `apps/edge/internal/service/single_request_observation.go`
- `apps/edge/internal/service/single_request_metrics.go`
- `apps/edge/internal/service/single_request_test.go`
- `apps/edge/internal/service/single_request_tool_loop_test.go`
- `apps/edge/internal/service/single_request_cleanup_test.go`
- `apps/edge/internal/service/single_request_observation_test.go`
- `apps/edge/internal/service/single_request_metrics_test.go`
- `apps/edge/internal/openai/anthropic_handler.go`
- `apps/edge/internal/openai/single_request_anthropic_stream.go`
- `apps/edge/internal/openai/single_request_handler_test.go`
- `apps/edge/internal/openai/single_request_anthropic_stream_test.go`
- `agent-task/m-iop-owned-single-request-agent-execution/18+17_plan_stage/PLAN-local-G06.md`
- `agent-task/m-iop-owned-single-request-agent-execution/19+18_work_stage/PLAN-cloud-G08.md`
- `agent-task/m-iop-owned-single-request-agent-execution/20+19_review_repair/PLAN-cloud-G07.md`
- `agent-task/m-iop-owned-single-request-agent-execution/21+20_single_request_executor/PLAN-local-G08.md`
- `agent-task/m-iop-owned-single-request-agent-execution/22+21_executor_activation/PLAN-local-G07.md`
- `agent-task/m-iop-owned-single-request-agent-execution/24+22_claude_smoke_harness/PLAN-cloud-G07.md`
- `agent-task/m-iop-owned-single-request-agent-execution/25+23,24_claude_smoke_qualification/plan_cloud_G08_0.log`

### SDD Criteria

- Approved, unlocked SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md`; `milestone-task=error-cancel` maps to S11.
- S11 requires a matrix covering stage/request exhaustion, repetition/no-progress, malformed calls, provider/tool timeout, output/context limits, and disconnect. Every row must prove no retry/fallback/partial-success/second ingress and exactly one standard error, cancel, or length outcome.
- Evidence Map row S11 requires the budget/error/cancel/length/repetition terminal matrix. That row drives API-1 through API-4 and the focused race matrix in Final Verification.

### Verification Context

- No handoff was supplied. Repository-native evidence is the local test rules, service coordinator/tool-loop/cleanup/observation tests, Anthropic buffered/SSE tests, approved SDD, outer contract, and two matching current specs.
- Preparation checkpoint is branch `feature/iop-owned-single-request-agent-execution`, HEAD `b31163396d0e6556b5eaa759f9c7f1149fbc81c9`. Go tests use `-count=1`; race tests, protobuf reproducibility, deterministic searches, and `git diff --check` are mandatory. S11 itself uses deterministic stage/provider/tool fixtures; task 25 is only the later S12 Claude qualification.
- Task 22 is active and has no `complete.log`. Implementation must wait for it, resolve exactly one completion path, read that exact log, then reread completed task-18 through task-22 sources before editing. A completed-symbol mismatch is a recorded blocker/deviation, never a guessed owner expansion.
- The common SDD suite includes config, streamgate, Edge OpenAI/service, Node runtime/transport/workspace, `make proto`, and diff checks. Fresh output is required.

### Test Coverage Gaps

- Existing service tests cover budgets, cancellation, cleanup, races, and generic observation classes, but no caller-safe terminal disposition exists on `SingleRequestEnvelope`/`SingleRequestProgress`; failed and cancelled terminal progress therefore loses all closed classification before either Anthropic projector.
- Existing stage plans do not prove repetition/no-progress or one closed cross-stage failure policy.
- Existing buffered/SSE tests do not cover the full timeout, budget, malformed, context, output-length, cancellation, and no-second-ingress matrix.

### Symbol References

- `SingleRequestEnvelope`, `SingleRequestResult`, and `SingleRequestProgress` are validated, stored, cloned, and emitted in `apps/edge/internal/service/single_request.go`. Extend the terminal-bearing DTOs compatibly and update every repository composite literal found by the final search.
- `singleRequestAnthropicTerminalKind`, `singleRequestAnthropicError`, `writeAnthropicSingleRequestTerminal`, and `pumpSingleRequestAnthropicStream` are the current projection sites. No public request field or Edge-Node wire symbol is renamed.
- Future task-18 through task-22 stage/composite symbols are explicit call sites and must be reread after task 22 completes.

### Split Judgment

- This is the indivisible S11 boundary: stage classification, service terminal ownership, and buffered/SSE projection must agree atomically.
- `23+22_error_cancel` depends on sibling 22. `agent-task/m-iop-owned-single-request-agent-execution/22+21_executor_activation/` is active and its `complete.log` is missing; no archive candidate was read.
- Task 25 consumes this packet and task 24 for actual Claude qualification.
- The replacement was evaluated once with `refine-plans`. A service-only child would not independently close S11, while a projector child must consume that exact contract; inserting such a child between fixed index 23 and log-anchored consumer 25 would also collide with task 24. Keep this packet unchanged.

### Scope Rationale

Include only the closed terminal vocabulary, request-local stage failure/no-progress classification, one controller winner, Anthropic buffered/SSE mapping, S11 tests, outer contract, and matching current specs. Exclude retry/reselection, dynamic modes, Edge-Node protobuf changes, new metric labels, actual Claude execution, deployment, roadmap mutation, the generic StreamGate lifecycle, and `agent-contract/inner/execution-runtime.md`; that inner contract owns host-neutral provider execution rather than Edge `/v1/messages` projection.

### Final Routing

- `evaluation_mode=isolated-reassessment`; build and review closures (`scope`, `context`, `verification`, `evidence`, `ownership`, `decision`) are all true; capability gap is absent.
- Finalizer `finalize-task-policy.sh`, mode `pair`. Build scores `2/2/2/2/2` => G10, base/final `grade-boundary`, `worker/cloud/G10`, `PLAN-cloud-G10.md`. Review scores `2/2/2/2/2` => G10, `official-review`, `review/cloud/G10`, `CODE_REVIEW-cloud-G10.md`.
- `large_indivisible_context=false`; positive risks are `temporal_state`, `concurrent_consistency`, `boundary_contract`, and `structured_interpretation` (4); `review_rework_count=0`; `evidence_integrity_failure=false`; recovery boundary is false.

## Dependencies and Execution Order

1. Resolve exactly one sibling-22 `complete.log` with Final Verification command 1, read only it, then reread completed provider-stage, Plan/Work/Review, composite executor, activation, service, and projector files.
2. Define and test the closed service terminal contract, then thread stage outcomes through the composite.
3. Project the same disposition through buffered/SSE Anthropic responses, synchronize the outer contract and both current specs, and run the full SDD suite.

## Implementation Checklist

- [ ] Add a closed, copy-safe single-request terminal disposition on envelope/result/progress that distinguishes `end_turn`, `length`, sanitized error classes, and caller cancellation while preserving exactly-once cleanup/acknowledgement.
- [ ] Classify provider/tool timeout, stage/request budgets, repetition/no-progress, malformed calls, context/output limits, and disconnect in the completed stage/composite path without retry, fallback, partial-success, generic StreamGate admission, or retained waiters.
- [ ] Project the closed disposition consistently through buffered and SSE Anthropic responses and add the complete S11 one-ingress/one-terminal race matrix.
- [ ] Synchronize the Anthropic outer contract and both matching current implementation specs with the implemented error/cancel/length policy.
- [ ] Run dependency, focused race, compatibility, full SDD, proto, deterministic symbol/document, and diff verification freshly.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [API-1] Carry one closed terminal disposition

**Problem**

`apps/edge/internal/service/single_request.go:46-48` carries only output, while failed/cancelled progress at lines 304-317 loses the safe terminal reason before the endpoint sees it.

**Solution**

Add a closed exported terminal DTO with validated kind and safe error class. Carry a candidate on executor envelopes/results and copy the frozen value onto every `finalizing`, `failed`, or `cancelled` progress item. Legacy final results normalize to `end_turn`; plain executor errors normalize to a safe provider class; typed stage failures retain only their closed disposition. Cleanup may replace a success/length candidate with one cleanup failure before terminal progress is emitted, after which the disposition is immutable. Endpoint acknowledgement failure changes only the internal completion outcome and can never emit a second public terminal.

Before (`apps/edge/internal/service/single_request.go:46-48`):

```go
type SingleRequestResult struct {
	Output string
}
```

After:

```go
type SingleRequestTerminalDisposition struct {
	Kind       SingleRequestTerminalKind
	ErrorClass SingleRequestTerminalErrorClass
}

type SingleRequestResult struct {
	Output   string
	Terminal SingleRequestTerminalDisposition
}

type SingleRequestProgress struct {
	// Existing request, stage, message, result, and error fields remain.
	Terminal *SingleRequestTerminalDisposition
}
```

`SingleRequestEnvelope` gains the same optional terminal candidate for classified failure/cancel handoff. Zero-value legacy result literals normalize to `end_turn` only during validation; raw error text is never stored or projected.

**Modified Files and Checklist**

- [ ] Extend envelope/result/progress validation, cloning, terminal progress, cleanup conversion, and acknowledgement in `apps/edge/internal/service/single_request.go`.
- [ ] Add normal, invalid, length, error, cancel, cleanup-race, and terminal-winner coverage in `apps/edge/internal/service/single_request_test.go`.

**Test Strategy**

Write `TestSingleRequestTerminalDisposition*` in `apps/edge/internal/service/single_request_test.go`; assert finalizing/failed/cancelled propagation, copy safety, legacy compatibility, cleanup-before-freeze, post-freeze winner stability, and raw-free values.

**Verification**

Run `go test -race ./apps/edge/internal/service -run 'TestSingleRequestTerminalDisposition' -count=1`; all disposition/race rows pass.

### [API-2] Classify every S11 stage outcome without fallback

**Problem**

The completed provider codec and stage loops will return heterogeneous provider finishes, decoder failures, repeated actions/results, context/output exhaustion, and context errors. Generic propagation would collapse length into failure and permit deterministic no-progress to consume later budgets.

**Solution**

Add a request-local quality guard that reuses existing bounded canonicalization/fingerprint helpers where applicable, rejects the first proven action/result no-progress cycle, and returns only closed service dispositions. It must not enter the generic StreamGate admission/recovery lifecycle, duplicate request lifecycle/budget ownership, retry, or dispatch a later stage/model after terminal classification.

Before (`agent-task/m-iop-owned-single-request-agent-execution/21+20_single_request_executor/PLAN-local-G08.md:84`):

```go
// The predecessor composite preserves cancellation and generic errors.
```

After:

```go
// The composite classifies each stage outcome once, submits no later provider
// call after a terminal, and returns one closed service disposition.
```

**Modified Files and Checklist**

- [ ] Add closed outcome normalization and bounded fingerprints in `apps/edge/internal/openai/single_request_quality_gate.go`.
- [ ] Thread it through `apps/edge/internal/openai/single_request_provider_stage.go`, `apps/edge/internal/openai/single_request_work_stage.go`, `apps/edge/internal/openai/single_request_review_stage.go`, and `apps/edge/internal/openai/single_request_executor.go` after task 22.
- [ ] Add the complete deterministic matrix in `apps/edge/internal/openai/single_request_quality_gate_test.go`.

**Test Strategy**

Write `TestSingleRequestQualityGate*` under `-race`; assert dispatch/tool/envelope/terminal/waiter counts and safe output for timeout, budgets, repeat/no-progress, malformed, context/output, and cancel rows.

**Verification**

Run `go test -race ./apps/edge/internal/openai -run 'TestSingleRequestQualityGate' -count=1`; every row stops on its first terminal with no extra dispatch.

### [API-3] Emit one standard Anthropic terminal

**Problem**

`apps/edge/internal/openai/anthropic_handler.go:280-303` maps every failure to generic `api_error`, while lines 316-330 always emit `stop_reason=end_turn`. `apps/edge/internal/openai/single_request_anthropic_stream.go:368-397` likewise lacks length/error-class projection.

**Solution**

Use one policy shared by buffered and SSE projectors: success=`end_turn`, output limit=`max_tokens`, validation/context=`invalid_request_error`, provider/timeout/budget/repetition/malformed=`api_error`, caller disconnect=silent internal cancel. Length never exposes private partial stage content, and no error writes a later success terminal.

Before (`apps/edge/internal/openai/anthropic_handler.go:316-325`):

```go
stopReason := "end_turn"
response := anthropicMessageResponse{StopReason: &stopReason}
```

After:

```go
policy := anthropicSingleRequestPolicy(result.Terminal)
response := anthropicMessageResponse{StopReason: &policy.stopReason}
```

**Modified Files and Checklist**

- [ ] Centralize safe status/error/stop-reason mapping in `apps/edge/internal/openai/anthropic_handler.go`.
- [ ] Apply the same serialized policy in `apps/edge/internal/openai/single_request_anthropic_stream.go`.
- [ ] Add buffered matrix coverage in `apps/edge/internal/openai/single_request_handler_test.go`.
- [ ] Add SSE ordering/race/length/error/disconnect coverage in `apps/edge/internal/openai/single_request_anthropic_stream_test.go`.

**Test Strategy**

Write `TestAnthropicSingleRequestErrorCancelMatrix` and `TestSingleRequestAnthropicStreamTerminalDisposition*`; assert HTTP/SSE shape, terminal count one, ingress delta one, no private values, no success after error, and silence after disconnect.

**Verification**

Run `go test -race ./apps/edge/internal/openai -run 'Test(AnthropicSingleRequestErrorCancelMatrix|SingleRequestAnthropicStreamTerminalDisposition)' -count=1`; all rows pass.

### [API-4] Synchronize the Edge terminal contract and current specs

**Problem**

`agent-contract/outer/anthropic-compatible-api.md:131-134` describes generic marked-request failure/cancel behavior, while both matching living specs defer S11. The host-neutral `agent-contract/inner/execution-runtime.md` owns provider `RunRequest` lifecycle rather than Edge HTTP projection and must remain unchanged.

**Solution**

Document the exact endpoint mapping, one-ingress/one-terminal/no-fallback invariant, silent disconnect, length privacy, unchanged Edge-Node wire, and deterministic S11 evidence in the outer Anthropic contract plus the runtime and input-surface specs.

Before (`agent-contract/outer/anthropic-compatible-api.md:131-134`):

```text
A coordinator failure or non-disconnect cancellation writes one sanitized
error event. Caller disconnect cancels execution and suppresses further output.
```

After:

```text
The marked projector applies one closed error/cancel/length policy to buffered
and SSE responses; disconnect is silent and cannot produce later ingress/output.
```

**Modified Files and Checklist**

- [ ] Update `agent-contract/outer/anthropic-compatible-api.md` with the endpoint mapping.
- [ ] Update `agent-spec/runtime/edge-node-execution.md` with S11 implementation/evidence, leaving S12 pending.
- [ ] Update `agent-spec/input/openai-compatible-surface.md` with the same current `/v1/messages` terminal behavior and evidence.

**Test Strategy**

No document-only test is added; API-1 through API-3 fixtures back the statements and deterministic search checks both specs.

**Verification**

Run Final Verification command 7; every terminal kind and S11/no-second-request statement appears in the outer contract and both specs.

## Modified Files Summary

| File | Items |
|------|-------|
| `apps/edge/internal/service/single_request.go` | API-1 |
| `apps/edge/internal/service/single_request_test.go` | API-1 |
| `apps/edge/internal/openai/single_request_quality_gate.go` | API-2 |
| `apps/edge/internal/openai/single_request_quality_gate_test.go` | API-2 |
| `apps/edge/internal/openai/single_request_provider_stage.go` | API-2 |
| `apps/edge/internal/openai/single_request_work_stage.go` | API-2 |
| `apps/edge/internal/openai/single_request_review_stage.go` | API-2 |
| `apps/edge/internal/openai/single_request_executor.go` | API-2 |
| `apps/edge/internal/openai/anthropic_handler.go` | API-3 |
| `apps/edge/internal/openai/single_request_anthropic_stream.go` | API-3 |
| `apps/edge/internal/openai/single_request_handler_test.go` | API-3 |
| `apps/edge/internal/openai/single_request_anthropic_stream_test.go` | API-3 |
| `agent-contract/outer/anthropic-compatible-api.md` | API-4 |
| `agent-spec/runtime/edge-node-execution.md` | API-4 |
| `agent-spec/input/openai-compatible-surface.md` | API-4 |
| `agent-task/m-iop-owned-single-request-agent-execution/23+22_error_cancel/CODE_REVIEW-cloud-G10.md` | API-1, API-2, API-3, API-4 |

## Final Verification

Fresh output is required; cached Go test output is not acceptable.

1. `bash -c 'set -euo pipefail; shopt -s nullglob; candidates=(agent-task/m-iop-owned-single-request-agent-execution/22_*/complete.log agent-task/m-iop-owned-single-request-agent-execution/22+*/complete.log agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/22_*/complete.log agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/22+*/complete.log); printf "%s\n" "${candidates[@]}"; ((${#candidates[@]} == 1))'` — exactly one task-22 completion path exists before implementation/review.
2. `go test -race ./apps/edge/internal/service ./apps/edge/internal/openai -run 'Test(SingleRequestTerminalDisposition|SingleRequestQualityGate|AnthropicSingleRequestErrorCancelMatrix|SingleRequestAnthropicStreamTerminalDisposition)' -count=1` — the S11 terminal matrix passes without races.
3. `go test -race ./apps/edge/internal/service -run 'TestSingleRequest(InternalToolLoop|Cleanup|Observation|EnvelopeOrdering|StageBudget)' -count=1` — coordinator budgets, cleanup, observation, and ordering remain compatible.
4. `go vet ./apps/edge/internal/service ./apps/edge/internal/openai && go test ./apps/edge/... -count=1` — changed Edge packages vet and regress cleanly.
5. `go test -race -count=1 ./packages/go/config ./packages/go/streamgate ./apps/edge/internal/openai ./apps/edge/internal/service ./apps/node/internal/node ./apps/node/internal/transport ./apps/node/internal/workspace` — approved SDD common suite passes freshly.
6. `make proto && git diff --exit-code -- proto/gen/iop` — protobuf generation is reproducible with no wire delta.
7. `rg --sort path -n 'end_turn|max_tokens|invalid_request_error|api_error|disconnect|no second|second request|S11|error-cancel' agent-contract/outer/anthropic-compatible-api.md agent-spec/runtime/edge-node-execution.md agent-spec/input/openai-compatible-surface.md` — outer contract and both current specs contain the closed policy/evidence.
8. `rg --sort path -n 'SingleRequestTerminal|SingleRequestResult|singleRequestAnthropic.*Policy' apps/edge/internal/service apps/edge/internal/openai --glob '*.go'` — every construction/projection call site is visible.
9. `git diff --check` — no whitespace errors.

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