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

# Make late tool admission request-authoritative

## For the Implementing Agent

Fill every implementation-owned section in `CODE_REVIEW-cloud-G08.md` after coding. Run the verification commands exactly, paste actual output, keep both active files in place, and report ready for review; finalization belongs only 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 shared child-operation classifier now makes already-running tool and artifact operations request-authoritative. Tool-call admission still returns `timeout` directly when its stage deadline is expired, so an admission racing the request monitor can freeze the wrong terminal after the earlier request deadline. Close that remaining branch without changing genuinely earlier stage timeouts or any public vocabulary.

## Archive Evidence Snapshot

- `agent-task/m-iop-owned-single-request-agent-execution/23+22_error_cancel/code_review_cloud_G10_4.log` ended in `FAIL` with Required R1: `prepareInternalWorkspaceToolLocked` still checks the stage deadline without first consulting request wall-clock ownership.
- A fresh focused reviewer reproducer set an expired `requestDeadline` one millisecond before an expired `stageDeadline`; `prepareInternalWorkspaceToolLocked` returned `error class = "timeout", want "internal_tool_budget"`.
- The submitted repeated real tool/artifact ownership tests, earlier-stage controls, compatibility matrix, full Edge tests, SDD race suite, protobuf reproducibility, deterministic searches, and diff hygiene all passed freshly. They do not cover late tool-call admission after the request deadline.
- `agent-task/archive/2026/08/m-iop-owned-single-request-agent-execution/22+21_executor_activation/complete.log` satisfies predecessor subtask 22. S12 external Claude qualification remains the separate `claude-smoke` task.

## Finding Resolution Map

| Finding | Mode | Fix and changed precondition |
|---------|------|------------------------------|
| Required R1 | direct-fix | Update `apps/edge/internal/service/single_request_tool_loop.go` so expired tool-call admission uses the existing parent-first request/child classifier, and add request-first versus stage-first admission coverage in `apps/edge/internal/service/single_request_tool_loop_test.go`. The precondition changes from “the direct stage-deadline branch can beat the request monitor” to “the earlier authoritative deadline determines the class before terminal freeze.” |

`ownership_closed=true`: Required R1 is repository-fixable in this packet and has no unordered external dependency.

## Analysis

### Files Read

Production files:

- `apps/edge/internal/service/single_request.go`
- `apps/edge/internal/service/single_request_tool_loop.go`
- `apps/edge/internal/service/single_request_artifact.go`

Test files:

- `apps/edge/internal/service/single_request_tool_loop_test.go`
- `apps/edge/internal/service/single_request_artifact_test.go`
- `apps/edge/internal/service/single_request_observation_test.go`

### SDD Criteria

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md`; status `[승인됨]`, lock released, no user review.
- Milestone metadata: `milestone-task=error-cancel`.
- Target: Acceptance Scenario S11 and its Evidence Map row requiring the budget/error/cancel/length/repetition terminal matrix with bounded, no-partial, no-second-request evidence.
- S11 makes the earlier request wall-clock authoritative over a later stage deadline. It drives the admission-order regression, preserved earlier-stage timeout control, repeated request-budget races, and common race suite below.

### Verification Context

- No separate verification handoff was supplied. Repository-native evidence came from `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, the active Milestone/SDD, the Anthropic contract, matching living specs, source/tests above, and fresh review commands.
- Reviewer preflight: `/config/workspace/iop-s0`, `go1.26.2 linux/arm64`, current dirty worktree preserved, no remote runner or credential required.
- Fresh passes: repeated real child-path ownership and earlier-stage controls; S11 service/OpenAI compatibility; `go vet`; all Edge tests; the approved common race suite; protobuf hash reproducibility; deterministic contract/symbol searches; `git diff --check`.
- Fresh contradiction: a temporary package-local test, removed after execution, called the production admission classifier with `requestDeadline < stageDeadline < now` and received `timeout` instead of `internal_tool_budget`.
- Constraints: no public terminal, metric label, contract/spec, protobuf, Node wire, retry/fallback, ingress, or S12 smoke change. Fresh Go runs must use `-count=1`, `-count=10`, or `-count=20` as specified.
- Gap: no committed test covers request-first expiry at tool-call admission. Confidence is high because the direct branch and deterministic reproducer agree.

### Test Coverage Gaps

- Existing real tool/artifact request-wall-clock tests cover operations already in flight, not a new tool call admitted after both deadlines.
- Existing `expired stage deadline at tool admission is observed as timeout` covers the opposite ordering (`stageDeadline < requestDeadline`) and must remain unchanged.
- Add one deterministic table test for both orderings, then retain the real race tests as integrated terminal/observation controls.

### Symbol References

No symbol is renamed or removed. `classifyChildOperationContext` remains the shared classifier; only the direct expired-admission branch becomes an additional consumer.

### Split Judgment

Keep one packet: the source condition and its two-order regression form one compact deadline-precedence invariant. Subtask predecessor 22 is satisfied by `agent-task/archive/2026/08/m-iop-owned-single-request-agent-execution/22+21_executor_activation/complete.log`.

### Scope Rationale

- Do not change `single_request.go`; its shared classifier already has the required caller → request → child ordering.
- Do not change artifact or OpenAI code; fresh integrated evidence shows those consumers are correct.
- Do not change contracts, specs, protobuf, Node wire, terminal vocabulary, retry/fallback, or S12 smoke.

### Final Routing

- `status=routed`; `evaluation_mode=isolated-reassessment`; `finalizer=finalize-task-policy.sh`; `finalizer_mode=pair`.
- Build closures are all true with no capability gap. Scores: `scope_coupling=1`, `state_concurrency=2`, `blast_irreversibility=1`, `evidence_diagnosis=2`, `verification_complexity=2`; grade `G08`; base basis `local-fit`; final basis `recovery-boundary`; route `cloud`; filename `PLAN-cloud-G08.md`.
- Review closures are all true with no capability gap. The same scores produce `G08`; basis `official-review`; route `cloud`; filename `CODE_REVIEW-cloud-G08.md`.
- `large_indivisible_context=false`; positive loop risks: `temporal_state`, `concurrent_consistency`, `boundary_contract`, `variant_product`; `loop_risk_count=4`; `risk_boundary_matched=true`.
- Recovery: `review_rework_count=3`; `evidence_integrity_failure=true`; `recovery_boundary_matched=true`.
- Catalog routes: `worker/cloud/G08` and `review/cloud/G08`.

## Dependencies and Execution Order

- Predecessor subtask 22 is complete at `agent-task/archive/2026/08/m-iop-owned-single-request-agent-execution/22+21_executor_activation/complete.log`; no additional dependency remains.

## Implementation Checklist

- [ ] Make expired tool-call admission consult request-owned deadline classification before preserving a genuinely earlier stage timeout.
- [ ] Add deterministic request-first and stage-first tool-admission regression coverage, then retain the real child-path budget ownership controls.
- [ ] Run focused race, compatibility, full Edge/SDD, protobuf reproducibility, deterministic symbol, and diff-hygiene verification freshly.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_API-1] Classify expired tool admission by deadline ownership

**Problem**

`apps/edge/internal/service/single_request_tool_loop.go:77-82` returns timeout whenever the stored stage deadline is expired:

```go
77 if usage.iterations >= h.binding.Limits.MaxToolIterations || h.toolLoop.stageDeadline.IsZero() {
78     return nil, ErrSingleRequestInternalToolBudget, ""
79 }
80 if !time.Now().Before(h.toolLoop.stageDeadline) {
81     return nil, ErrSingleRequestInternalToolBudget, singleRequestErrorClassTimeout
82 }
```

When the stage deadline is not earlier than the immutable request deadline, the stage timer is stopped and the request monitor owns termination. The direct admission branch can still acquire the handle lock first after both deadlines and freeze timeout.

**Solution**

Use the existing parent-first classifier at the expired-stage admission branch. Preserve timeout as the fallback only while the caller and request remain live:

```go
if !time.Now().Before(h.toolLoop.stageDeadline) {
    _, errorClass := h.classifyChildOperationContext(nil, singleRequestErrorClassTimeout)
    return nil, ErrSingleRequestInternalToolBudget, errorClass
}
```

Do not change iteration/output budget classification or the stage timer. The classifier must return `internal_tool_budget` when the immutable request deadline is already reached and `timeout` when only the genuinely earlier stage deadline is reached.

**Modified Files and Checklist**

- [ ] Update the expired admission branch in `apps/edge/internal/service/single_request_tool_loop.go`.
- [ ] Add `TestPrepareInternalWorkspaceToolDeadlineOwnership` to `apps/edge/internal/service/single_request_tool_loop_test.go` with request-first and stage-first cases.

**Test Strategy**

Use a valid package-local handle, binding, continuation, runtime, and read call. Set explicit past/future immutable deadlines without starting timers: request-first (`requestDeadline < stageDeadline < now`) must return `ErrSingleRequestInternalToolBudget` with `internal_tool_budget`; stage-first (`stageDeadline < now < requestDeadline`) must retain `timeout`. Existing repeated real tool/artifact tests continue to prove one terminal, raw-free observation, and no timeout after request expiry.

**Verification**

```sh
go test -race ./apps/edge/internal/service -run '^TestPrepareInternalWorkspaceToolDeadlineOwnership$' -count=20
```

Expected: both deadline orderings pass freshly across all repetitions.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/edge/internal/service/single_request_tool_loop.go` | REVIEW_API-1 |
| `apps/edge/internal/service/single_request_tool_loop_test.go` | REVIEW_API-1 |
| `agent-task/m-iop-owned-single-request-agent-execution/23+22_error_cancel/CODE_REVIEW-cloud-G08.md` | REVIEW_API-1 evidence |

## Final Verification

1. Confirm the failed review and predecessor evidence:

```sh
test -f agent-task/m-iop-owned-single-request-agent-execution/23+22_error_cancel/code_review_cloud_G10_4.log && test -f agent-task/archive/2026/08/m-iop-owned-single-request-agent-execution/22+21_executor_activation/complete.log && rg -n 'Overall Verdict: FAIL|Required R1|review_rework_count=3|evidence_integrity_failure=true' agent-task/m-iop-owned-single-request-agent-execution/23+22_error_cancel/code_review_cloud_G10_4.log
```

Expected: both files exist and the archived review prints the FAIL, Required R1, and routing signals.

2. Run the new deadline-ownership regression repeatedly:

```sh
go test -race ./apps/edge/internal/service -run '^TestPrepareInternalWorkspaceToolDeadlineOwnership$' -count=20
```

Expected: request-first admission is budget and stage-first admission is timeout on every run.

3. Rerun the real request-budget ownership and deadline controls:

```sh
go test -race ./apps/edge/internal/service -run '^(TestSingleRequest(InternalTool|Artifact)RequestWallClockBudgetOwnership|TestSingleRequestObservationDeadlineClassifications)$' -count=10
```

Expected: all request-expiry paths are budget, the genuinely earlier stage paths remain timeout, observations stay raw-free, and each request emits one terminal.

4. Rerun the S11 and prior ownership compatibility matrix:

```sh
go test -race ./apps/edge/internal/service ./apps/edge/internal/openai -run 'Test(SingleRequestTerminalDisposition|SingleRequestQualityGate|SingleRequestExecutor(Cancellation|StageFailures|TerminalWaiterCleanup|ParentContextOwnership|RequestBudgetOwnership)|AnthropicSingleRequestErrorCancelMatrix|SingleRequestAnthropicStreamTerminalDisposition|AnthropicSingleRequestLiveContextProviderCancellationBuffered|SingleRequestAnthropicStreamLiveContextProviderCancellation)' -count=1
```

Expected: both packages pass without cached output.

5. Run Edge static and package regressions:

```sh
go vet ./apps/edge/internal/service ./apps/edge/internal/openai && go test ./apps/edge/... -count=1
```

Expected: vet and every Edge package pass.

6. Run the approved SDD common race suite:

```sh
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
```

Expected: every package passes freshly under `-race`.

7. Prove protobuf output remains reproducible:

```sh
b_before=$(sha256sum proto/gen/iop/runtime.pb.go) && make proto && b_after=$(sha256sum proto/gen/iop/runtime.pb.go) && test "$b_before" = "$b_after" && printf '%s\n' "$b_after"
```

Expected: generation succeeds and prints the unchanged hash.

8. Confirm the contract and both deadline-order consumers deterministically:

```sh
rg --sort path -n 'caller disconnect|request wall-clock|error-cancel|S11' agent-contract/outer/anthropic-compatible-api.md agent-spec/runtime/edge-node-execution.md agent-spec/input/openai-compatible-surface.md && rg --sort path -n 'prepareInternalWorkspaceToolLocked|classifyChildOperationContext|requestDeadline|singleRequestErrorClassInternalToolBudget|singleRequestErrorClassTimeout' apps/edge/internal/service --glob '*.go'
```

Expected: searches show S11, the shared classifier, the corrected admission consumer, prior tool/artifact consumers, and both regression orderings.

9. Check diff hygiene:

```sh
git diff --check
```

Expected: no output and exit 0.

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