<!-- task=m-iop-owned-single-request-agent-execution/15+14_observation_adapters plan=2 tag=REVIEW_API milestone-task=cleanup-observation -->

# Make the Cleanup/Tool Correlation Regression Deterministic

## For the Implementing Agent

Resolve only Required finding R2 by repairing the ordering test. Do not change production runtime, cleanup, observation, API, wire, config, or spec behavior. Run every verification command, fill the implementation-owned sections in `CODE_REVIEW-cloud-G04.md` with actual notes and output, keep the active files in place, and report ready for review. If blocked, record only the exact blocker, attempted commands/output, and resume condition in those implementation-owned 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 prior follow-up repaired the Anthropic cleanup fake and changed all Node tool observers to carry immutable request correlation. Its cleanup-overlap test passes, but it closes the pause channel before WRITE begins, so it does not force cleanup between correlation capture and deferred tool observation. The remaining work is a deterministic test-only synchronization repair.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-iop-owned-single-request-agent-execution/15+14_observation_adapters/plan_local_G06_1.log`.
- Prior review: `agent-task/m-iop-owned-single-request-agent-execution/15+14_observation_adapters/code_review_cloud_G06_1.log`.
- Prior verdict: `FAIL`; Required findings: R2; Suggested findings: none; Nit findings: none.
- R1 is closed: the canonical cleanup request/response fake, exact-one cleanup assertion, targeted Anthropic test, and affected-package regression pass.
- R2 production propagation is present for READ, LIST, WRITE, DELETE, and COMMAND, and the focused race suite passes. The remaining gap is that `TestWorkspaceObservationCorrelationSurvivesCleanupOverlap` does not block WRITE before cleanup or wait for cleanup before release.
- Predecessor evidence: `agent-task/archive/2026/08/m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing/complete.log`.

## Finding Resolution Map

| Finding | Mode | Exact fix and changed precondition |
|---------|------|------------------------------------|
| R2 | direct-fix | In `apps/node/internal/workspace/observation_test.go`, start WRITE asynchronously, block it after correlation capture at `beforeRename`, complete cleanup while WRITE is held, then release WRITE and assert exactly one tool/cleanup observation pair shares the original non-empty correlation. The changed precondition is a proven cleanup-between-capture-and-observation ordering rather than another run against the unchanged scheduler race. |

`ownership_closed=true`: R2 has one repository-local test owner and requires no external runner or user decision.

## Analysis

### Files Read

- `agent-task/m-iop-owned-single-request-agent-execution/15+14_observation_adapters/plan_local_G06_1.log`
- `agent-task/m-iop-owned-single-request-agent-execution/15+14_observation_adapters/code_review_cloud_G06_1.log`
- `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`
- `apps/node/internal/workspace/observation.go`
- `apps/node/internal/workspace/file_executor.go`
- `apps/node/internal/workspace/cleanup.go`
- `apps/node/internal/workspace/observation_test.go`

### SDD Criteria

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md`, status `[승인됨]`, lock released.
- Scope: `milestone-task=cleanup-observation`, Acceptance Scenario S07.
- Evidence Map: S07 requires cleanup-race, user-result-preservation, and raw-free timing/log/metric allowlist evidence.
- This packet closes only the cleanup-race ordering proof. Its checklist forces cleanup to finish between immutable correlation capture and tool observation, while final verification preserves the existing raw-free observation and package evidence.

### Verification Context

- No separate verification-context handoff was supplied. Repository-native evidence came from the current review output, the local test rules, the Edge/Node testing profiles, the approved SDD, and direct reviewer commands.
- Current host: Go `1.26.2`, `linux/arm64`; repository target remains Go 1.24-compatible. No external service, credential, device, or remote runner is needed for this test-only fix.
- The packet-14 predecessor resolves uniquely to the archived `complete.log` listed above.
- Fresh reviewer evidence: the targeted Anthropic test, focused Node observation race suite, 100 repeated current overlap-test runs, affected-package regression, vet, formatting, and `git diff --check` passed. The repeated overlap runs do not close R2 because the test lacks the required ordering barriers.
- External Claude/full-cycle qualification remains the separate `claude-smoke` packet and is not evidence claimed by this task.
- Confidence: high; the missing barrier is explicit in the test body and the production propagation is already independently inspectable.

### Test Coverage Gaps

- Covered: cleanup and tool events retain a non-empty correlation in a normally scheduled run.
- Missing: WRITE is not proven to have captured correlation before cleanup removes request authority, because `pause` is closed before WRITE reaches `beforeRename` and cleanup completion is not ordered before WRITE resumes.

### Symbol References

- No production symbol is renamed or removed.
- The test-only `catalogEntry.beforeRename` seam is invoked by WRITE at `apps/node/internal/workspace/file_executor.go:235`.

### Split Judgment

- Keep one packet. One test function and one ordering invariant produce one independent PASS result.
- Runtime predecessor `14+05,12,13_observation_timing` is satisfied by the archived `complete.log` in the Archive Evidence Snapshot.

### Scope Rationale

- Include only deterministic synchronization and assertions in `apps/node/internal/workspace/observation_test.go` plus review evidence.
- Exclude production observer propagation, cleanup ownership, the already-closed Anthropic fake, API/wire/config/spec changes, and external/full-cycle smoke.

### Final Routing

- `evaluation_mode=isolated-reassessment`; all build and review closures are true and there is no capability gap.
- Build scores are `0/2/0/1/1` (G04). `large_indivisible_context=false`; positive loop risk is `concurrent_consistency` (`loop_risk_count=1`).
- `review_rework_count=2` and `evidence_integrity_failure=true` select the `recovery-boundary`; build route is `cloud/G04`, `PLAN-cloud-G04.md`.
- Review scores are `0/2/0/1/1` (G04); official review route is `cloud/G04`, `CODE_REVIEW-cloud-G04.md`.
- Finalizer: `finalize-task-policy.sh pair local-fit false 1 2 true 0 2 0 1 1 official-review 0 2 0 1 1`.

## Implementation Checklist

- [x] Force WRITE to reach and block at `beforeRename`, complete cleanup while it is blocked, then release it and assert exactly one cleanup/tool pair shares the original non-empty correlation.
- [x] Run predecessor, repeated ordering race, focused observation race, Anthropic regression, affected-package, formatting, vet, and whitespace verification with uncached Go tests.
- [x] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_API-1] Enforce cleanup between correlation capture and observation

**Problem**

The current test does not establish its claimed order:

```go
// apps/node/internal/workspace/observation_test.go:128
pause := make(chan struct{})
request.entry.beforeRename = func() error {
    <-pause
    return nil
}
cleanupDone := make(chan struct{})
go func() {
    defer close(cleanupDone)
    _ = runtime.Cleanup(t.Context(), "request-cleanup-overlap")
}()
close(pause)
if result := runtime.Write("request-cleanup-overlap", "result.txt", []byte("overlap")); result.Status != iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS {
    t.Fatalf("write after cleanup overlap: %+v", result)
}
```

`pause` is already closed when WRITE reaches the hook, so cleanup is not forced between capture and deferred observation.

**Solution**

Use separate arrival and release barriers. Start WRITE in a goroutine, wait until `beforeRename` reports arrival, call and complete cleanup synchronously while WRITE remains blocked, release WRITE, and then inspect its result. Count observations and require exactly one cleanup event and one WRITE tool event, both carrying the original correlation.

```go
entered := make(chan struct{})
release := make(chan struct{})
request.entry.beforeRename = func() error {
    close(entered)
    <-release
    return nil
}
writeDone := make(chan Result, 1)
go func() {
    writeDone <- runtime.Write("request-cleanup-overlap", "result.txt", []byte("overlap"))
}()
<-entered
cleanup := runtime.Cleanup(t.Context(), "request-cleanup-overlap")
close(release)
writeResult := <-writeDone
```

Do not change the production seam or observation schema.

**Modified Files and Checklist**

- [x] `apps/node/internal/workspace/observation_test.go` — add the arrival/release ordering barriers and exact event-count/shared-correlation assertions.

**Test Strategy**

- Update `TestWorkspaceObservationCorrelationSurvivesCleanupOverlap` only.
- Run it 50 times under the race detector to prove the explicit ordering is scheduler-independent.
- Run the full observation subset and affected packages to preserve raw-free allowlists, failure isolation, cleanup behavior, and R1 regression closure.

**Verification**

- `go test -race ./apps/node/internal/workspace -run '^TestWorkspaceObservationCorrelationSurvivesCleanupOverlap$' -count=50`
- Expected: every run observes successful cleanup and WRITE with exactly one shared non-empty original correlation and no race.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/node/internal/workspace/observation_test.go` | REVIEW_API-1 |
| `agent-task/m-iop-owned-single-request-agent-execution/15+14_observation_adapters/CODE_REVIEW-cloud-G04.md` | REVIEW_API-1 |

## Final Verification

1. `test -f agent-task/m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing/complete.log || test "$(compgen -G 'agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/14+05,12,13_observation_timing/complete.log' | wc -l)" -eq 1`
2. `go test -race ./apps/node/internal/workspace -run '^TestWorkspaceObservationCorrelationSurvivesCleanupOverlap$' -count=50`
3. `go test -race ./apps/node/internal/workspace -run 'TestWorkspaceObservation' -count=1`
4. `go test ./apps/edge/internal/openai -run '^TestAnthropicSingleRequestInternalToolsStayPrivate$' -count=3`
5. `go test ./apps/edge/internal/service ./apps/edge/internal/bootstrap ./apps/edge/internal/openai ./apps/node/internal/workspace -count=1`
6. `gofmt -d apps/node/internal/workspace/observation_test.go`
7. `go vet ./apps/edge/internal/service ./apps/edge/internal/bootstrap ./apps/edge/internal/openai ./apps/node/internal/workspace`
8. `git diff --check`

Expected: the packet-14 predecessor remains uniquely complete; the ordering test passes 50 uncached race runs with explicit barriers; observation, Anthropic, and affected-package regressions pass; formatting produces no output; vet and whitespace checks are clean. Cached Go test results are not acceptable.

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