<!-- task=m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing plan=5 tag=REVIEW_API milestone-task=cleanup-observation -->

# Classify Expired Tool Admission as Timeout

## For the Implementing Agent

Implement R7 exactly within the listed write boundary, run every verification command, and fill the implementation-owned sections of `CODE_REVIEW-cloud-G06.md` with actual notes and stdout/stderr. Keep the active pair in place and report ready for review. 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`; finalization belongs to the code-review skill.

## Background

The normal request, stage-timer, and in-flight tool deadline paths now record `timeout`, but tool admission still combines an already elapsed stage deadline with iteration and invalid-budget rejection. When an internal-tool envelope is queued before the delayed timer callback acquires the coordinator mutex, that admission path wins and records `internal_tool_budget`. The fix must retain the caller-visible budget sentinel while preserving elapsed-deadline ownership in observation.

## Archive Evidence Snapshot

- Closed pair: `agent-task/m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing/plan_cloud_G07_4.log` and `agent-task/m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing/code_review_cloud_G07_4.log`.
- Verdict: FAIL with Required R7, zero Suggested findings, and zero Nit findings.
- Affected behavior: stage-deadline ownership when tool admission races the stage timer callback.
- Fresh reviewer verification: packet 05/12/13 checks passed; the focused observation race passed in 1.239s; the service package passed in 6.411s; vet and `git diff --check` passed. A temporary mutex-ordering lifecycle reproducer was removed after failing in 0.08s with `terminal error class="internal_tool_budget", want timeout`.
- Roadmap carryover: `milestone-task=cleanup-observation`; approved SDD scenario S07 requires linked raw-free timing and outcome evidence.

## Finding Resolution Map

| Finding | Mode | Exact fix | Changed precondition |
|---------|------|-----------|----------------------|
| R7 | `direct-fix` | Separate elapsed `stageDeadline` admission from iteration/invalid budget rejection in `apps/edge/internal/service/single_request_tool_loop.go`, pass the explicit class through `apps/edge/internal/service/single_request.go`, and add the mutex-ordering lifecycle regression in `apps/edge/internal/service/single_request_observation_test.go`. | Tool admission queued ahead of a delayed stage timer records `timeout` while iteration exhaustion remains `internal_tool_budget`. |

`ownership_closed=true`: R7 is a repository-local direct fix with deterministic race coverage and requires no user decision, external authorization, or unordered dependency.

## Analysis

### Files Read

- `apps/edge/internal/service/single_request.go`
- `apps/edge/internal/service/single_request_tool_loop.go`
- `apps/edge/internal/service/single_request_observation_test.go`
- `apps/edge/internal/service/single_request_tool_loop_test.go`
- `apps/edge/internal/service/single_request_test.go`
- `agent-spec/runtime/edge-node-execution.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-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-task/m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing/plan_cloud_G07_4.log`
- `agent-task/m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing/code_review_cloud_G07_4.log`

### SDD Criteria

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md`; status approved, lock released, and no `USER_REVIEW.md`.
- Header scope: `milestone-task=cleanup-observation`.
- Target scenario: S07 requires success/error/cancel cleanup plus linked raw-free stage/tool/total timing and outcomes.
- Evidence Map S07 requires scoped lifecycle/timing evidence. The checklist therefore closes the remaining timer/admission ordering variant and retains a non-time budget control; it does not claim S11, S12, or Milestone completion.

### Verification Context

- No separate verification handoff was supplied. Repository-native sources are the archived review evidence, approved SDD S07, Edge domain rules, local test rules, current coordinator/tool-loop source, and service lifecycle tests.
- Current host: `go version go1.26.2 linux/arm64`; the repository target remains Go 1.24-compatible.
- Packet 05, 12, and 13 dependency checks passed. Fresh reviewer commands passed the focused observation race, service package, vet, and whitespace checks.
- Fresh reviewer evidence contradicted the completion claim: with `h.mu` held past the stage deadline, a tool envelope queued before the timer callback won terminal ownership and emitted `internal_tool_budget` instead of `timeout`. The temporary test file was removed.
- Constraints: preserve caller-visible sentinels and unrelated dirty work; do not change API, wire, config, schema, adapters, living specs, production sinks, or external smoke.
- External Verification Preflight: not applicable. The remaining defect is deterministic service-local timer/mutex classification; S12 actual Claude/Mac smoke is a separate Milestone task.
- Confidence: high; the failing branch is explicit and the production mutex ordering was reproduced.

### Test Coverage Gaps

- Existing deadline lifecycle tests cover the wall-clock monitor, an uncontended stage timer, and an already in-flight Node tool, but not expired stage deadline admission that beats a blocked timer callback.
- Existing iteration-exhaustion coverage proves the resource-budget control and must remain unchanged.
- No test currently holds the coordinator mutex across the stage deadline, queues tool admission first, and asserts the resulting terminal class.

### Symbol References

- `prepareInternalWorkspaceToolLocked` is package-private and has one production call in `apps/edge/internal/service/single_request.go`; update that call if its result gains an explicit error-class value.
- No exported symbol, interface, wire type, or external call site is renamed or removed.

### Split Judgment

- Keep one plan. The admission result, terminal winner, timer callback, and observation class share one lock-ordered invariant and one deterministic race regression.

### Scope Rationale

- Include only tool-admission deadline classification, its coordinator handoff, and the lifecycle regression.
- Exclude caller-visible sentinel changes, other budget semantics, API/wire/config/schema changes, observation sinks, living-spec updates, and external Claude/Mac verification because they are not required to resolve R7.

### Final Routing

- `evaluation_mode=isolated-reassessment`; finalizer `finalize-task-policy.sh` in `pair` mode.
- Build closures are all true. Scores `1/2/0/2/1 = G06`; base `local-fit`, final basis `recovery-boundary`, lane `cloud`, catalog `worker/cloud/G06`, filename `PLAN-cloud-G06.md`.
- Review closures are all true. Scores `1/2/0/2/1 = G06`; basis `official-review`, lane `cloud`, catalog `review/cloud/G06`, filename `CODE_REVIEW-cloud-G06.md`.
- `large_indivisible_context=false`; positive loop risks are `temporal_state`, `concurrent_consistency`, `boundary_contract`, and `variant_product` (`loop_risk_count=4`).
- Recovery signals: `review_rework_count=4`, `evidence_integrity_failure=true`; capability gap: none.

## Implementation Checklist

- [ ] Preserve `ErrSingleRequestInternalToolBudget` for callers while classifying an elapsed stage deadline at tool admission as `timeout`; retain `internal_tool_budget` for iteration and output exhaustion.
- [ ] Add a deterministic mutex-ordering lifecycle regression that queues tool admission before releasing the coordinator lock after the stage deadline, and retain the existing non-time budget control.
- [ ] Run dependency, focused race, package, vet, and whitespace verification with cache bypass where supported.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

## Implementation Plan

### [REVIEW_API-1] Preserve deadline ownership at tool admission

**Problem**

- `apps/edge/internal/service/single_request_tool_loop.go:74` combines iteration exhaustion, a missing stage deadline, and an elapsed stage deadline into the same unclassified `ErrSingleRequestInternalToolBudget` result.
- `apps/edge/internal/service/single_request.go:334` passes every admission error to `failLocked`, so an elapsed deadline that wins before the stage timer callback records `internal_tool_budget`.

**Solution**

Replace the combined admission result:

```go
// single_request_tool_loop.go:74-77, before
if usage.iterations >= h.binding.Limits.MaxToolIterations || h.toolLoop.stageDeadline.IsZero() ||
    time.Until(h.toolLoop.stageDeadline) <= 0 {
    return nil, ErrSingleRequestInternalToolBudget
}
```

with distinct resource and elapsed-time branches. Return an explicit `singleRequestErrorClassTimeout` alongside the preserved budget sentinel only for the elapsed-deadline branch. Update the sole caller to use `failLockedWithErrorClass(err, errorClass)`; ordinary admission failures return an empty override and retain typed default classification.

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

Update every return from the package-private helper for the new result shape. Do not change the exported sentinel, general classifier order, stage timer, or iteration/output budget paths.

**Modified Files and Checklist**

- [ ] `apps/edge/internal/service/single_request_tool_loop.go` — separate elapsed-deadline classification from resource admission budgets.
- [ ] `apps/edge/internal/service/single_request.go` — pass the helper's explicit class to the existing classified terminal seam.
- [ ] `apps/edge/internal/service/single_request_observation_test.go` — add the real mutex/timer admission ordering regression and assert the preserved sentinel plus terminal `timeout`.

**Test Strategy**

- Extend `TestSingleRequestObservationDeadlineClassifications` in `apps/edge/internal/service/single_request_observation_test.go`.
- Use a continuation-capable executor with planning-ready, tool-release, and submitting signals. Hold `singleRequestHandle.mu`, queue the tool envelope, keep the lock held beyond the immutable stage deadline so the earlier waiter owns unlock, then assert `Wait` still matches `ErrSingleRequestInternalToolBudget` while the terminal DTO reports `timeout`.
- Keep the existing iteration exhaustion case and assert it still reports `internal_tool_budget`.

**Verification**

- `go test -race ./apps/edge/internal/service -run '^TestSingleRequestObservationDeadlineClassifications$' -count=1`
- Expected: timer, in-flight tool, and expired-admission deadline variants report `timeout`; resource exhaustion reports `internal_tool_budget`; the suite is race-free.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/edge/internal/service/single_request.go` | REVIEW_API-1 |
| `apps/edge/internal/service/single_request_tool_loop.go` | REVIEW_API-1 |
| `apps/edge/internal/service/single_request_observation_test.go` | REVIEW_API-1 |
| `agent-task/m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing/CODE_REVIEW-cloud-G06.md` | REVIEW_API-1 |

## Final Verification

1. `test -f agent-task/m-iop-owned-single-request-agent-execution/05+03_single_ingress/complete.log || test "$(compgen -G 'agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/05+03_single_ingress/complete.log' | wc -l)" -eq 1`
2. `test -f agent-task/m-iop-owned-single-request-agent-execution/12+05,06,08,11_internal_tool_loop/complete.log || test "$(compgen -G 'agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/12+05,06,08,11_internal_tool_loop/complete.log' | wc -l)" -eq 1`
3. `test -f agent-task/m-iop-owned-single-request-agent-execution/13+12_workspace_cleanup/complete.log || test "$(compgen -G 'agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/13+12_workspace_cleanup/complete.log' | wc -l)" -eq 1`
4. `go test -race ./apps/edge/internal/service -run '^TestSingleRequestObservationDeadlineClassifications$' -count=1`
5. `go test ./apps/edge/internal/service -count=1`
6. `go vet ./apps/edge/internal/service`
7. `git diff --check`

Expected: predecessor evidence remains unique; expired tool admission and all other real deadlines record `timeout`; iteration/output exhaustion remains `internal_tool_budget`; the service package remains clean. Cached test output is not acceptable.

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