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

# Dedicated Edge-Node Workspace Runtime Wire

## For the Implementing Agent

Do not start until packet 08 has `complete.log`. Keep all changes inside the listed boundary, generate protobuf only with `make proto`, run every command, fill the paired review stub, and leave review/finalization artifacts to the official reviewer.

## Background

The admitted binding needs a transport that is distinct from provider `RunRequest`, provider execution, and closed `NodeCommand`. This packet defines and proves that typed boundary, including catalog delivery, open/tool/cancel/cleanup requests, generation fencing, and bounded replies; Node execution remains unsupported until packet 10.

## 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-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/node-smoke.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`
- `proto/iop/runtime.proto`
- `apps/edge/internal/node/mapper.go`
- `apps/edge/internal/node/mapper_test.go`
- `apps/edge/internal/service/service.go`
- `apps/edge/internal/service/node_command.go`
- `apps/edge/internal/node/registry.go`
- `apps/edge/internal/transport/server.go`
- `apps/edge/internal/transport/server_test.go`
- `apps/node/internal/transport/parser.go`
- `apps/node/internal/transport/parser_test.go`
- `apps/node/internal/transport/session.go`
- `apps/node/internal/transport/session_test.go`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-contract/inner/execution-runtime.md`
- `agent-spec/runtime/edge-node-execution.md`

### SDD Criteria

- D08 and S05 require a separate typed workspace request/result boundary, never `RunRequest.metadata`, provider execution, or `NodeCommand` extension.
- Requests/results must cover success/error/timeout/large output and process cancellation with immutable request/stage/tool identity.
- S07 requires cleanup to be an explicit request-owned action; execution semantics follow in packet 13.

### Verification Context

- `make -n proto` resolves to `protoc --go_out=... proto/iop/runtime.proto ...`; `protoc` is installed.
- Baseline Edge/Node transport and service packages passed fresh.
- Packet 08 supplies the exact admitted Node id/generation DTO. Wire tests can use `net.Pipe` and do not require a Mac filesystem.

### State and Concurrency Findings

- Each send must use the admitted Node id and generation; reconnect must fail as stale, not re-resolve.
- Context cancellation sends typed cancel and leaves any request waiter bounded by its timeout.
- Transport error messages/log fields must not include path, content, argv/template, environment values, stdout/stderr, or credentials.

### Test Coverage Gaps

- Parser maps and Session have no workspace message types or optional workspace handler.
- `NodeConfigPayload` cannot carry the approved catalog and Edge has no generation-fenced typed client.

### Symbol References

- Keep `transport.Handler` source-compatible for all existing mocks by adding a separate optional `WorkspaceHandler` interface and type assertion.
- `RunRequest` reserved workspace fields and `NodeCommand` enum remain untouched.
- Generated output for `runtime.proto` is exactly `proto/gen/iop/runtime.pb.go`.

### Split Judgment

- Stable contract: proto generation, parser registration, optional handler behavior, and a net-pipe round trip independently PASS before filesystem effects.
- Executor behavior remains packet 10 to keep wire verification deterministic and host-neutral.

### Scope Rationale

- Include proto/config payload, Edge client, parser/listener registration, tests, contract, and spec.
- Exclude filesystem/process implementation, stage-provider decoding, cleanup effects, and public API output.

### Final Routing

- `evaluation_mode=first-pass`; build closures true, scores 2/1/2/1/2 = G08.
- Finalizer route `risk-boundary`, lane `cloud`, filename `PLAN-cloud-G08.md`; positive risks are all five routing signatures.
- Review scores 2/1/2/2/2 = G09; official review filename `CODE_REVIEW-cloud-G09.md`. No capability gap, rework, or evidence-integrity failure.

## Dependencies and Execution Order

1. Require packet 08 completion.
2. Add source proto messages and generate Go.
3. Map workspace catalog into registration payload, then register parsers/listeners.
4. Add the generation-fenced Edge client and round-trip tests before docs.

## Implementation Checklist

- [ ] Define and generate a dedicated typed workspace config/open/tool/cancel/cleanup protocol with closed operations, identities, statuses, error codes, and bounded result fields.
- [ ] Deliver approved capabilities in `NodeConfigPayload` and register backward-compatible Edge/Node parsers plus an optional Node workspace handler.
- [ ] Implement a generation-fenced service wire client that never reselects a Node and propagates timeout/context cancellation without raw logging.
- [ ] Prove proto generation cleanliness, parser/round-trip/cancel/stale-generation behavior, and synchronize the wire contract/living spec.
- [ ] Run dependency, proto, focused race, package, vet, documentation, and whitespace verification.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

## Implementation Plan

### [API-1] Define the workspace protocol and catalog payload

**Problem**

- `proto/iop/runtime.proto:298` carries only adapters/runtime.
- `proto/iop/runtime.proto:10` explicitly reserves legacy workspace fields on `RunRequest`, and `NodeCommandRequest` is a closed ops surface.

**Solution**

Before (`proto/iop/runtime.proto:298`):

```proto
message NodeConfigPayload {
  repeated AdapterConfig adapters = 1;
  NodeRuntimeConfig runtime = 2;
}
```

After, add a new field and separate top-level protocol messages:

```proto
message NodeConfigPayload {
  repeated AdapterConfig adapters = 1;
  NodeRuntimeConfig runtime = 2;
  repeated WorkspaceConfig workspaces = 3;
}

message WorkspaceOpenRequest { /* execution_id, workspace_ref, limits */ }
message WorkspaceToolRequest { /* execution/stage/tool ids, closed operation, typed input */ }
message WorkspaceCancelRequest { /* execution_id, tool_call_id */ }
message WorkspaceCleanupRequest { /* execution_id */ }
```

`WorkspaceConfig` carries platform/root, closed operations, fixed command definitions, env names, and hard caps from packet 07. Tool input uses a proto `oneof` for relative path, write content, or command id/environment map. Responses echo identities and use closed status/error-code enums with typed bounded content/list/stdout/stderr, exit code, truncation, duration, and cleanup counts. Reserve no caller-selected Node/root/executable/argv field. Preserve existing field numbers and never reuse reservations.

**Modified Files and Checklist**

- [ ] `proto/iop/runtime.proto` — define catalog and four request/response families.
- [ ] `proto/gen/iop/runtime.pb.go` — regenerate with `make proto`; no hand edits.
- [ ] `apps/edge/internal/node/mapper.go` — serialize packet 07 workspace definitions into the private Node payload.
- [ ] `apps/edge/internal/node/mapper_test.go` — assert complete typed mapping and no legacy settings leakage.

**Test Strategy**

- Extend mapper tests and rely on parser round trips in API-2 for every new proto family.

**Verification**

- `make proto`
- `git diff --exit-code -- proto/gen/iop/agent.pb.go proto/gen/iop/control.pb.go proto/gen/iop/job.pb.go proto/gen/iop/node.pb.go`
- `go test ./apps/edge/internal/node -run 'TestBuildConfigPayload.*Workspace' -count=1`
- Expected: only `runtime.pb.go` changes and the payload retains the complete approved catalog.

### [API-2] Register compatible parsers and optional Node handlers

**Problem**

- `apps/node/internal/transport/session.go:17` requires every handler to implement the provider methods, so adding workspace methods there would break all mocks.
- `apps/edge/internal/transport/server.go:35` and the Node parser map do not decode new request/response types.

**Solution**

Before (`apps/node/internal/transport/session.go:17`):

```go
type Handler interface {
	OnRunRequest(context.Context, *Session, *iop.RunRequest) error
	// Existing provider methods.
}
```

After, leave `Handler` unchanged and add:

```go
type WorkspaceHandler interface {
	OnWorkspaceOpen(context.Context, *Session, *iop.WorkspaceOpenRequest) (*iop.WorkspaceOpenResponse, error)
	OnWorkspaceTool(context.Context, *Session, *iop.WorkspaceToolRequest) (*iop.WorkspaceToolResponse, error)
	OnWorkspaceCancel(context.Context, *Session, *iop.WorkspaceCancelRequest) (*iop.WorkspaceCancelResponse, error)
	OnWorkspaceCleanup(context.Context, *Session, *iop.WorkspaceCleanupRequest) (*iop.WorkspaceCleanupResponse, error)
}
```

Register request listeners that type-assert `WorkspaceHandler` and return a typed unsupported/not-ready response when absent or on handler error. Register all request parsers on Node and response parsers on Edge. Do not log raw request/response fields.

**Modified Files and Checklist**

- [ ] `apps/node/internal/transport/parser.go` — parse workspace requests.
- [ ] `apps/node/internal/transport/parser_test.go` — round-trip every request shape.
- [ ] `apps/node/internal/transport/session.go` — add optional handler/listeners and typed failure translation.
- [ ] `apps/node/internal/transport/session_test.go` — net-pipe success, absent handler, error, and identity echo cases.
- [ ] `apps/edge/internal/transport/server.go` — parse workspace responses.
- [ ] `apps/edge/internal/transport/server_test.go` — round-trip every response shape.

**Test Strategy**

- Named tests `TestNodeParserMapWorkspace`, `TestSessionWorkspaceRequest`, and `TestEdgeParserMapWorkspace` cover all message families and preserve existing handler compile compatibility.

**Verification**

- `go test -race ./apps/node/internal/transport ./apps/edge/internal/transport -run 'Test(NodeParserMapWorkspace|SessionWorkspace|EdgeParserMapWorkspace)' -count=1`
- Expected: typed round trips succeed; absent handlers return typed failure without panic or raw leakage.

### [API-3] Dispatch only to the admitted generation

**Problem**

- `apps/edge/internal/service/node_command.go:122` resolves a Node per call and has no request-stable workspace generation.
- Context cancellation has no workspace-specific cancel/cleanup path.

**Solution**

Add `workspace_wire.go` in service with `workspaceOpen`, `workspaceTool`, `workspaceCancel`, and `workspaceCleanup` methods. Each method accepts packet 08's frozen binding, obtains `ReadyOwnerSnapshot(binding.NodeID)`, compares `ConnectionGeneration`, and sends to that exact client only. Tool/open wait uses the lower admitted deadline; a cancelled context sends typed cancel once and all goroutines remain bounded by transport timeout. Translate transport/stale/typed Node errors to stable internal errors without including raw payload.

**Modified Files and Checklist**

- [ ] `apps/edge/internal/service/workspace_wire.go` — implement exact-generation typed request/response dispatch.
- [ ] `apps/edge/internal/service/workspace_wire_test.go` — net-pipe open/tool/cancel/cleanup, timeout, cancellation, stale generation, and no-reselection tests.
- [ ] `agent-contract/inner/edge-node-runtime-wire.md` — define identities, state, errors, limits, privacy, compatibility, and non-reuse rules.
- [ ] `agent-spec/runtime/edge-node-execution.md` — synchronize implemented wire and explicit executor deferral.

**Test Strategy**

- Use a real registry and net-pipe client; reconnect the same Node id and assert the old binding never reaches the new client.

**Verification**

- `go test -race ./apps/edge/internal/service -run 'TestWorkspaceWire' -count=1`
- `rg --sort path -n 'Workspace(Open|Tool|Cancel|Cleanup)|RunRequest|NodeCommand|generation|raw' agent-contract/inner/edge-node-runtime-wire.md agent-spec/runtime/edge-node-execution.md`
- Expected: all sends are typed and generation-fenced; docs preserve the separate boundary.

## Modified Files Summary

| File | Item |
|------|------|
| `proto/iop/runtime.proto` | API-1 |
| `proto/gen/iop/runtime.pb.go` | API-1 |
| `apps/edge/internal/node/mapper.go` | API-1 |
| `apps/edge/internal/node/mapper_test.go` | API-1 |
| `apps/node/internal/transport/parser.go` | API-2 |
| `apps/node/internal/transport/parser_test.go` | API-2 |
| `apps/node/internal/transport/session.go` | API-2 |
| `apps/node/internal/transport/session_test.go` | API-2 |
| `apps/edge/internal/transport/server.go` | API-2 |
| `apps/edge/internal/transport/server_test.go` | API-2 |
| `apps/edge/internal/service/workspace_wire.go` | API-3 |
| `apps/edge/internal/service/workspace_wire_test.go` | API-3 |
| `agent-contract/inner/edge-node-runtime-wire.md` | API-3 |
| `agent-spec/runtime/edge-node-execution.md` | API-3 |
| `agent-task/m-iop-owned-single-request-agent-execution/09+08_workspace_wire/CODE_REVIEW-cloud-G09.md` | API-1, API-2, 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. `make proto`
3. `git diff --exit-code -- proto/gen/iop/agent.pb.go proto/gen/iop/control.pb.go proto/gen/iop/job.pb.go proto/gen/iop/node.pb.go`
4. `go test -race ./apps/edge/internal/node ./apps/edge/internal/service ./apps/edge/internal/transport ./apps/node/internal/transport -run 'Test(BuildConfigPayload.*Workspace|WorkspaceWire|NodeParserMapWorkspace|SessionWorkspace|EdgeParserMapWorkspace)' -count=1`
5. `go test ./apps/edge/internal/node ./apps/edge/internal/service ./apps/edge/internal/transport ./apps/node/internal/transport -count=1`
6. `go vet ./apps/edge/internal/node ./apps/edge/internal/service ./apps/edge/internal/transport ./apps/node/internal/transport`
7. `rg --sort path -n 'Workspace(Open|Tool|Cancel|Cleanup)|RunRequest|NodeCommand|generation|raw' agent-contract/inner/edge-node-runtime-wire.md agent-spec/runtime/edge-node-execution.md`
8. `git diff --check`

Expected: the predecessor is uniquely complete; generation is reproducible and limited to the intended generated file; all typed wire paths pass under race; provider contracts remain separate. Cached tests are not acceptable.

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