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

# Separate Deadline Observation From Resource Budget Exhaustion

## 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-G07.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

Typed classification now preserves provider, validation, tool, cleanup, budget, and cancellation winners, but production request/stage/tool deadlines still enter the generic internal-tool budget sentinel path. The passing suite checks `context.DeadlineExceeded` only through the classifier helper, so it misses terminal observations that report a real timeout as `internal_tool_budget`. This follow-up separates time expiry from iteration/output exhaustion without changing the caller-visible failure sentinel.

## Archive Evidence Snapshot

- Closed pair: `agent-task/m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing/plan_cloud_G07_3.log` and `agent-task/m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing/code_review_cloud_G07_3.log`.
- Verdict: FAIL with Required R7, zero Suggested findings, and zero Nit findings.
- Affected behavior: request wall-clock, stage timer, and in-flight tool deadline observation versus iteration/output budget observation.
- Fresh reviewer verification: packet 05/12/13 checks passed; focused observation race passed in 1.098s; the service package passed in 6.260s; vet and `git diff --check` passed. A temporary service-lifecycle reproducer was removed after failing 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` | Capture an explicit `timeout` observation class at request wall-clock, stage timer, and in-flight tool deadline winners in `apps/edge/internal/service/single_request.go` and `apps/edge/internal/service/single_request_tool_loop.go`, while retaining `internal_tool_budget` for iteration/output exhaustion; add real lifecycle regressions in `apps/edge/internal/service/single_request_observation_test.go`. | Fresh service tests distinguish elapsed deadlines from non-time resource budget exhaustion before cleanup joins secondary failures. |

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

## Analysis

### Files Read

- `apps/edge/internal/service/service.go`
- `apps/edge/internal/service/single_request.go`
- `apps/edge/internal/service/single_request_observation.go`
- `apps/edge/internal/service/single_request_observation_test.go`
- `apps/edge/internal/service/single_request_tool_loop.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_test.go`
- `agent-spec/runtime/edge-node-execution.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.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_3.log`
- `agent-task/m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing/code_review_cloud_G07_3.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 distinguishes the closed semantic outcome class at each real deadline owner and retains budget classification for iteration/output exhaustion; it does not claim S11 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, the service lifecycle tests, and current source.
- Current host reports `go version go1.26.2 linux/arm64`; the repository target remains Go 1.24-compatible and the fix requires no external service, credential, port, device, or remote runner.
- Packet 05, 12, and 13 dependency checks passed. Fresh reviewer commands passed the focused race suite, service package, vet, and whitespace checks.
- A focused production-lifecycle probe contradicted the active implementation claim: a real request wall-clock expiry emitted terminal `ErrorClass="internal_tool_budget"` instead of `timeout`. The probe file was removed immediately after capture.
- Constraints: preserve caller-visible error sentinels and unrelated dirty work; do not change API/wire/config/schema, adapters, living specs, production observation sinks, or actual Claude/Mac smoke.
- External Verification Preflight: not applicable. This is a deterministic service observation classification repair; S12 external smoke remains a separate Milestone task.
- Confidence: high; the failing wall-clock path was reproduced and the same classification loss is explicit at the stage timer and in-flight tool deadline seams.

### Test Coverage Gaps

- `TestSingleRequestObservationErrorClassMapping` covers `context.DeadlineExceeded` only as a direct classifier input and cannot detect production seams that replace it with `ErrSingleRequestInternalToolBudget`.
- `TestSingleRequestInternalToolLoopStageDeadline` checks the caller-visible budget sentinel but does not attach an observer or assert the semantic timeout class.
- No service-level observation test distinguishes request wall-clock, stage timer, and in-flight tool deadline winners from iteration/output exhaustion.

### Symbol References

- No exported or existing symbol is renamed or removed. Any new classified-failure helper remains package-private and all call sites are confined to `single_request.go`, `single_request_tool_loop.go`, and their service tests.

### Split Judgment

- Keep one plan. Primary error retention, deadline-owner classification, cleanup joining, and tool/stage/terminal observation must share one terminal winner; splitting would permit inconsistent classes between those records.

### Scope Rationale

- Include only coordinator deadline classification, tool-loop deadline classification, and deterministic observation regressions.
- Exclude caller-visible error sentinel changes, API/wire/config/schema changes, production logging/metric adapters, living-spec updates, and external Claude/Mac verification. They are not required to repair R7.

### Final Routing

- `evaluation_mode=isolated-reassessment`; finalizer `finalize-task-policy.sh` in `pair` mode.
- Build closures are all true. Scores `1/2/1/2/1 = G07`; base `local-fit`, final basis `recovery-boundary`, lane `cloud`, catalog `worker/cloud/G07`, filename `PLAN-cloud-G07.md`.
- Review closures are all true. Scores `1/2/1/2/1 = G07`; basis `official-review`, lane `cloud`, catalog `review/cloud/G07`, filename `CODE_REVIEW-cloud-G07.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=3`, `evidence_integrity_failure=true`; capability gap: none.

## Implementation Checklist

- [ ] Preserve caller-visible budget failures while recording `timeout` for request wall-clock, stage timer, and in-flight tool deadline winners; retain `internal_tool_budget` for iteration/output exhaustion.
- [ ] Add deterministic service-lifecycle observation regressions for all three deadline owners and at least one non-time budget control, including cleanup-error joining where applicable.
- [ ] 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 in terminal observation

**Problem**

- `apps/edge/internal/service/single_request.go:223` and `:632` replace request deadlines with `ErrSingleRequestInternalToolBudget` before `failLocked` captures the primary observation class.
- `apps/edge/internal/service/single_request_tool_loop.go:232`, `:250`, and `:315` do the same for in-flight tool and stage timer deadlines.
- `apps/edge/internal/service/single_request_observation_test.go:1074` tests the classifier helper rather than those lifecycle owners.

**Solution**

Replace unqualified deadline failure capture:

```go
// single_request.go:223-224, before
} else if errors.Is(execCtx.Err(), context.DeadlineExceeded) {
    h.failLocked(ErrSingleRequestInternalToolBudget)
}
```

with a package-private classified failure path that preserves the existing error but records the deadline winner explicitly:

```go
} else if errors.Is(execCtx.Err(), context.DeadlineExceeded) {
    h.failLockedWithErrorClass(ErrSingleRequestInternalToolBudget, singleRequestErrorClassTimeout)
}
```

Make ordinary `failLocked` delegate with an empty override so typed sentinel classification remains the default. Apply the explicit timeout override at request wall-clock, executor deadline, stage timer, and tool deadline seams. Return `singleRequestErrorClassTimeout` from the tool outcome mapper on `context.DeadlineExceeded`; keep iteration and output limit failures on the unqualified budget path. The first primary class must continue to survive cleanup error joining.

**Modified Files and Checklist**

- [ ] `apps/edge/internal/service/single_request.go` — add the internal classified failure seam and use it at request deadline winners.
- [ ] `apps/edge/internal/service/single_request_tool_loop.go` — use timeout class for actual stage/tool deadlines and preserve budget class for non-time limits.
- [ ] `apps/edge/internal/service/single_request_observation_test.go` — assert lifecycle terminal/stage/tool classes for wall-clock, stage, tool deadline, cleanup join, and a resource-budget control.

**Test Strategy**

- Add `TestSingleRequestObservationDeadlineClassifications` in `apps/edge/internal/service/single_request_observation_test.go`.
- Exercise a request wall-clock expiry before any stage timer, a planning-stage timer expiry, and a blocked Node tool whose stage deadline wins. Assert terminal `timeout`; for the tool case also assert tool-before-stage ordering, `ToolCount=1`, and timeout class preservation across cleanup failure.
- Exercise one iteration or output exhaustion through the real service and assert terminal `internal_tool_budget` so the fix cannot relabel all budgets as timeouts.

**Verification**

- `go test -race ./apps/edge/internal/service -run 'TestSingleRequestObservation' -count=1`
- Expected: real deadline variants report `timeout`, resource exhaustion reports `internal_tool_budget`, cleanup cannot overwrite the primary class, and 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-G07.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 'TestSingleRequestObservation' -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; request, stage, and tool deadlines record `timeout`; iteration/output exhaustion remains `internal_tool_budget`; primary classes survive cleanup joining; 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`.**
