<!-- task=m-iop-owned-single-request-agent-execution/09+08_workspace_wire plan=2 tag=REVIEW_API milestone-task=tool-executor -->

# Repair Workspace Wire Cancellation and Validation

## For the Implementing Agent

This is the direct-fix follow-up for Required R1-R3. Preserve the implemented workspace proto, parser, catalog, contract, and spec changes; modify only the four source/test files listed below. Make the cancellation proof deterministic, run every verification command uncached, fill the paired review stub, and leave review finalization to the official reviewer.

## Background

The dedicated workspace wire is generated, registered, and generation-fenced, but review found three blocking defects in its Edge/Node lifecycle. A blocked Node tool handler serializes the request receive loop, Edge tries to send cancellation behind the mutex held by the original request wait, open accepts a workspace reference other than the admitted reference, and Edge treats typed failure or identity-mismatched responses as successful calls. The intended contract and living spec already describe the correct behavior, so this follow-up repairs implementation and tests without changing the wire schema or documentation.

## Archive Evidence Snapshot

- Plan 1 and its failed review are preserved at `agent-task/m-iop-owned-single-request-agent-execution/09+08_workspace_wire/plan_cloud_G08_1.log` and `agent-task/m-iop-owned-single-request-agent-execution/09+08_workspace_wire/code_review_cloud_G09_1.log`. The review verdict is `FAIL` with Required R1-R3: in-flight cancellation is serialized, open can override the admitted workspace reference, and typed response outcomes/identities are not validated.
- Fresh reviewer execution reran every planned verification successfully, then `go test ./apps/edge/internal/service -run '^TestReviewProbeWorkspace' -count=1 -timeout=5s` failed both the blocked-tool cancellation and admitted-reference probes. The temporary probe file was removed after recording that evidence.

## Analysis

### Files Read

- `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/client/rules.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/client-smoke.md`
- `agent-roadmap/current.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-contract/inner/edge-node-runtime-wire.md`
- `agent-spec/runtime/edge-node-execution.md`
- `apps/edge/internal/service/workspace_wire.go`
- `apps/edge/internal/service/workspace_wire_test.go`
- `apps/edge/internal/node/registry.go`
- `apps/node/internal/transport/session.go`
- `apps/node/internal/transport/session_test.go`
- `/config/workspace/proto-socket/go/communicator.go`
- `/config/workspace/proto-socket/go/packets/message_common.pb.go`
- `agent-task/m-iop-owned-single-request-agent-execution/09+08_workspace_wire/plan_cloud_G08_1.log`
- `agent-task/m-iop-owned-single-request-agent-execution/09+08_workspace_wire/code_review_cloud_G09_1.log`

### SDD Criteria

- S04 freezes the exact admitted workspace reference, Node id, dispatch-ready connection generation, closed capabilities, and effective limits before execution. Later requests cannot substitute another workspace capability.
- S05 requires the typed open/tool/cancel/cleanup lifecycle to preserve immutable coordinator request/stage/tool identities, remain separate from provider execution, and prove cancellation and bounded failure behavior.
- The Evidence Map assigns the `tool-executor` task key to S05; this follow-up remains that task and must restore conformance before completion.

### Verification Context

- The predecessor admission packet remains uniquely complete in the active/archive dependency check.
- All original generation, package, vet, client, documentation, and whitespace commands passed independently. No proto, generated binding, client, contract, or spec defect was found.
- The reviewer probe exercised the production Edge request path with a real registry and net pipe. It deterministically showed that cancel did not reach a handler that waited for cancel, and that an unapproved request reference reached Node.

### State and Concurrency Findings

- `Registry.WithCurrentDispatchOwner` holds the registry mutex around the complete `SendRequestTyped` wait. Calling `workspaceCancel` through the same helper therefore waits behind the in-flight tool request instead of interrupting it.
- The proto-socket communicator has one receive coordinator, and `AddRequestListenerTyped` invokes the request callback synchronously. A blocked workspace tool callback prevents the queued workspace cancel callback from starting.
- Cancellation must be emitted exactly once to the client captured for the admitted Node id/generation. It must never resolve a replacement client, but it also must not wait for the original dispatch-owner mutex.
- Concurrent workspace callbacks must retain the original request nonce when queuing each response and use the Session lifetime context so disconnect still cancels handler work.

### Test Coverage Gaps

- `TestWorkspaceWireContextCancellationSendsCancel` sleeps and returns from the tool handler before it observes cancel, so it proves only delayed delivery after the work has completed.
- There is no negative test for `request.workspace_ref != binding.Ref` or for proving rejection before any transport send.
- There is no response validator coverage for non-success status/error-code pairs, nil responses, identity mismatches, or exclusion of a raw Node `Error` sentinel.
- Session tests do not prove that cancel is handled while tool work remains blocked or that handler errors still produce generic typed responses through the concurrent listener path.

### Symbol References

- Keep the existing `transport.Handler` and optional `WorkspaceHandler` interfaces source-compatible.
- Replace only workspace request registrations with a repository-local asynchronous request-response helper built from public communicator registration/queue primitives. Do not modify the sibling `proto-socket` project.
- Keep the admitted `NodeEntry.Client` captured for one tool call. A cancellation may use a fire-and-forget typed send to that captured communicator because the caller has already returned on context cancellation; it must not call selection or wait for a cancel response.
- Keep closed status and error-code values as safe typed evidence. Never include `Workspace*Response.Error` in returned errors, logs, or assertions other than a sentinel non-leak check.

### Split Judgment

- R1-R3 share one request lifecycle and the same Edge tests. Splitting cancellation from binding/response validation would leave the reviewed workspace API partially unsafe and duplicate the same net-pipe harness.
- The four-file packet is independently verifiable without executor filesystem/process behavior, proto regeneration, client work, or documentation changes.

### Scope Rationale

- Include Node workspace listener concurrency, Edge exact-client cancellation, admitted-reference validation, typed response validation, and deterministic regressions.
- Exclude proto/generated files, catalog/parser mapping, registry ownership semantics, public APIs, executor filesystem/process behavior, contract/spec text, roadmap state, and unrelated dirty-worktree changes.

### Finding Resolution Map

| Finding | Disposition | Direct-fix targets | Changed precondition and proof |
|---|---|---|---|
| Required R1 | direct-fix | `apps/node/internal/transport/session.go`, `apps/node/internal/transport/session_test.go`, `apps/edge/internal/service/workspace_wire.go`, `apps/edge/internal/service/workspace_wire_test.go` | Workspace request callbacks no longer serialize the receive coordinator, and context cancellation sends once to the captured admitted client without acquiring the mutex held by the tool request. A blocked handler must observe cancel before it is released. |
| Required R2 | direct-fix | `apps/edge/internal/service/workspace_wire.go`, `apps/edge/internal/service/workspace_wire_test.go` | Open requires the request reference to equal the admitted binding and rejects mismatches before any Node send. |
| Required R3 | direct-fix | `apps/edge/internal/service/workspace_wire.go`, `apps/edge/internal/service/workspace_wire_test.go` | Every response family validates non-nil identity and allowed terminal status, translates failures to stable raw-free internal errors, and rejects mismatched echoes. |

### Final Routing

- `evaluation_mode=isolated-reassessment`; all build/review scope, context, verification, evidence, ownership, and decision closures are true; no capability gap is present.
- Build scores are 2/2/1/2/1 = G08. `large_indivisible_context=false`; matched loop-risk signatures are `temporal_state`, `concurrent_consistency`, and `boundary_contract`, so `loop_risk_count=3` and the risk boundary is false.
- `review_rework_count=1` and `evidence_integrity_failure=true`, so the recovery boundary promotes the local-fit build to `recovery-boundary`, lane `cloud`, filename `PLAN-cloud-G08.md`.
- Review scores are 2/2/2/2/1 = G09; official review is `cloud`, filename `CODE_REVIEW-cloud-G09.md`.

## Dependencies and Execution Order

1. Require the packet 08 admission dependency to remain uniquely complete.
2. Make Node workspace request handling concurrent and prove a cancel can enter while tool work remains blocked.
3. Send Edge cancellation to the captured admitted client without waiting behind the original request, then prove exactly-once/no-reselection behavior.
4. Enforce the admitted reference and validate typed response identity/status before returning any response.
5. Run focused race, package, vet, and whitespace verification, then fill the paired review stub.

## Implementation Checklist

- [ ] Make only Node workspace request-response callbacks concurrent while preserving request nonces, generic handler failures, session lifetime cancellation, and the optional handler contract.
- [ ] Make context cancellation send exactly one typed cancel to the captured admitted client/generation before a blocked tool handler returns, without reselection or registry-lock waiting.
- [ ] Reject an open request whose workspace reference differs from the admitted binding before any transport dispatch.
- [ ] Validate every workspace response identity and allowed success/cancel terminal status; translate nil, typed failure, and mismatch outcomes to stable raw-free internal errors.
- [ ] Add deterministic blocked-tool, mismatch/no-send, handler-error, invalid-response, timeout-bound, and raw-sentinel regressions.
- [ ] Run every final verification command uncached and fill all implementation-owned sections in `CODE_REVIEW-cloud-G09.md`.

## Implementation Plan

### [REVIEW_API-1] Restore in-flight workspace cancellation

**Problem**

- `apps/node/internal/transport/session.go:170` runs the tool request callback on the communicator's only receive coordinator, so the cancel callback at line 182 cannot start until tool work returns.
- `apps/edge/internal/service/workspace_wire.go:54` holds `Registry.WithCurrentDispatchOwner` for the complete typed tool request wait. The cancellation path at line 70 calls back through the same locked helper.

**Solution**

Register the four workspace request types through a local Session helper that performs type validation on receipt, starts handler/response work in a goroutine, and queues the typed response with the original request nonce. Preserve generic unsupported/failed responses, use `s.Context()`, and stop response delivery cleanly on disconnect. Leave all non-workspace listener behavior unchanged and do not modify proto-socket.

Capture the admitted Node entry/client once for `workspaceTool`. Keep the original tool dispatch behind the exact owner/generation fence, but when caller context wins, issue one fire-and-forget `WorkspaceCancelRequest` to that captured communicator instead of invoking the response-waiting `workspaceCancel` helper. Preserve request/stage/tool identities, never look up a replacement Node, and keep the original request waiter bounded.

**Modified Files and Checklist**

- [ ] `apps/node/internal/transport/session.go` — add concurrent workspace-only request/response dispatch.
- [ ] `apps/node/internal/transport/session_test.go` — prove cancel enters before blocked tool completion and generic handler failure still responds.
- [ ] `apps/edge/internal/service/workspace_wire.go` — capture the admitted client and send cancellation once without registry-lock waiting or reselection.
- [ ] `apps/edge/internal/service/workspace_wire_test.go` — replace delayed cancellation coverage with a blocked-tool handshake and assert exact identities/one send.

**Test Strategy**

- `TestSessionWorkspaceConcurrentCancel` blocks the tool handler until its cancel handler runs, then verifies both typed responses and identity echoes.
- `TestWorkspaceWireCancelReachesBlockedTool` waits until Node has entered the tool callback, cancels the caller context, and fails on a short timeout unless cancel arrives before the tool is released.

### [REVIEW_API-2] Enforce the admitted workspace reference

**Problem**

- `apps/edge/internal/service/workspace_wire.go:23` accepts any non-empty `WorkspaceOpenRequest.workspace_ref`, even when it differs from `SingleRequestWorkspaceBinding.Ref`.

**Solution**

Validate the binding before computing timeouts or dispatching. Require the request reference to equal the admitted reference and return a stable validation error for nil/empty/mismatched input. Do not normalize an attacker-selected reference into another capability after dispatch begins.

**Modified Files and Checklist**

- [ ] `apps/edge/internal/service/workspace_wire.go` — require exact admitted-reference equality before send.
- [ ] `apps/edge/internal/service/workspace_wire_test.go` — assert mismatch failure and prove the Node listener was never reached.

**Test Strategy**

- `TestWorkspaceWireRejectsBindingMismatchBeforeSend` uses distinct sentinel references and a Node-side counter/channel to prove fail-closed behavior before transport.

### [REVIEW_API-3] Validate typed workspace responses

**Problem**

- `apps/edge/internal/service/workspace_wire.go:33`, `:63`, `:86`, and `:103` translate only transport errors. Nil, non-success, and mismatched typed responses are returned with nil error, and raw Node error text remains reachable by callers.

**Solution**

Add focused validators for open, tool, cancel, and cleanup responses. Require exact request identity echoes (plus workspace/stage/tool identity where present), accept only each operation's defined successful terminal (`SUCCESS` for open/tool/cleanup and `CANCELLED` for cancel), and classify every other closed status/error-code pair as a stable internal failure. Do not include the response `Error` string or other raw fields in error text or logs. Retain transport/stale errors as their existing stable sentinels.

**Modified Files and Checklist**

- [ ] `apps/edge/internal/service/workspace_wire.go` — validate every response family and translate typed outcomes to raw-free internal errors.
- [ ] `apps/edge/internal/service/workspace_wire_test.go` — table-test nil, identity mismatch, status/error-code failure, timeout/cancelled/unsupported, and raw sentinel exclusion.

**Test Strategy**

- `TestWorkspaceWireRejectsInvalidResponse` covers all four response families and asserts that a unique raw `Error` sentinel is absent from every returned error.
- Keep the existing success, stale-generation, and no-reselection cases to prove validation does not widen dispatch.

## Modified Files Summary

| File | Item |
|---|---|
| `apps/node/internal/transport/session.go` | REVIEW_API-1 |
| `apps/node/internal/transport/session_test.go` | REVIEW_API-1 |
| `apps/edge/internal/service/workspace_wire.go` | REVIEW_API-1, REVIEW_API-2, REVIEW_API-3 |
| `apps/edge/internal/service/workspace_wire_test.go` | REVIEW_API-1, REVIEW_API-2, REVIEW_API-3 |
| `agent-task/m-iop-owned-single-request-agent-execution/09+08_workspace_wire/CODE_REVIEW-cloud-G09.md` | REVIEW_API-1, REVIEW_API-2, REVIEW_API-3 |

## Final Verification

1. `test -f agent-task/m-iop-owned-single-request-agent-execution/08+03,07_workspace_admission/complete.log || test "$(compgen -G 'agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/08+03,07_workspace_admission/complete.log' | wc -l)" -eq 1`
2. `go test -race ./apps/edge/internal/service ./apps/node/internal/transport -run 'Test(WorkspaceWire|SessionWorkspace)' -count=1`
3. `go test ./apps/edge/internal/node ./apps/edge/internal/service ./apps/edge/internal/transport ./apps/node/internal/transport -count=1`
4. `go vet ./apps/edge/internal/node ./apps/edge/internal/service ./apps/edge/internal/transport ./apps/node/internal/transport`
5. `git diff --check`

Expected: the predecessor remains uniquely complete; cancellation reaches a genuinely blocked tool on the exact admitted client before work is released; mismatched admitted references never reach Node; every typed response is identity/status validated with raw-free stable errors; all affected packages pass under race and regression checks. Cached tests are not acceptable.

**After completing all code changes, fill every implementation-owned section in `CODE_REVIEW-cloud-G09.md`.**
