<!-- task=m-iop-owned-single-request-agent-execution/09+08_workspace_wire plan=1 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, regenerate both Go and Dart bindings only with the repository targets, 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.

## Archive Evidence Snapshot

- The first-pass pair is preserved at `agent-task/m-iop-owned-single-request-agent-execution/09+08_workspace_wire/plan_cloud_G08_0.log` and `agent-task/m-iop-owned-single-request-agent-execution/09+08_workspace_wire/code_review_cloud_G09_0.log`; it contains no implementation evidence or review verdict.
- Self-review found that the wire identity must use the immutable coordinator `request_id`, and that changing `runtime.proto` requires tracked Go and Dart regeneration plus client verification. Plan 1 is the isolated reassessment that corrects both omissions.

## 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/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`
- `Makefile`
- `apps/client/lib/gen/proto/iop/runtime.pb.dart`
- `apps/client/lib/gen/proto/iop/runtime.pbenum.dart`
- `apps/client/lib/gen/proto/iop/runtime.pbjson.dart`
- `apps/client/lib/gen/proto/iop/runtime.pbserver.dart`
- `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 coordinator request/stage/tool identity. The wire `request_id` is the identity later used for `.iop/job/<request_id>`; it is not a Node-local execution id.
- S07 requires cleanup to be an explicit request-owned action; execution semantics follow in packet 13.

### Verification Context

- `make -n proto` and `make -n proto-dart` resolve to repository-owned Go and Dart generation; `protoc`, `protoc-gen-dart`, Flutter, and Dart are 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.
- Tracked generated output for `runtime.proto` is `proto/gen/iop/runtime.pb.go` plus the four `apps/client/lib/gen/proto/iop/runtime.*.dart` bindings.

### 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=isolated-reassessment`; `review_rework_count=0`; `evidence_integrity_failure=false`; 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 regenerate tracked Go and Dart bindings.
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, immutable `request_id`/stage/tool 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 Go and Dart generation cleanliness, parser/round-trip/cancel/stale-generation behavior, and synchronize the wire contract/living spec.
- [ ] Run dependency, Go/Dart generation, focused race, package, vet, client test/build, 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 { /* request_id, workspace_ref, limits */ }
message WorkspaceToolRequest { /* request/stage/tool ids, closed operation, typed input */ }
message WorkspaceCancelRequest { /* request_id, tool_call_id */ }
message WorkspaceCleanupRequest { /* request_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. The immutable coordinator `request_id` is copied unchanged through open/tool/cancel/cleanup and later names the exact internal namespace `.iop/job/<request_id>`; do not introduce a second `execution_id` alias. Responses echo request/stage/tool 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/client/lib/gen/proto/iop/runtime.pb.dart` — regenerate with `make proto-dart`; no hand edits.
- [ ] `apps/client/lib/gen/proto/iop/runtime.pbenum.dart` — regenerate with `make proto-dart`; no hand edits.
- [ ] `apps/client/lib/gen/proto/iop/runtime.pbjson.dart` — regenerate with `make proto-dart`; no hand edits.
- [ ] `apps/client/lib/gen/proto/iop/runtime.pbserver.dart` — regenerate with `make proto-dart`; 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`
- `make proto-dart`
- `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 apps/client/lib/gen/proto/iop/{control,job,node}.{pb,pbenum,pbjson,pbserver}.dart`
- `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/client/lib/gen/proto/iop/runtime.pb.dart` | API-1 |
| `apps/client/lib/gen/proto/iop/runtime.pbenum.dart` | API-1 |
| `apps/client/lib/gen/proto/iop/runtime.pbjson.dart` | API-1 |
| `apps/client/lib/gen/proto/iop/runtime.pbserver.dart` | 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. `make proto-dart`
4. `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 apps/client/lib/gen/proto/iop/{control,job,node}.{pb,pbenum,pbjson,pbserver}.dart`
5. `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`
6. `go test ./apps/edge/internal/node ./apps/edge/internal/service ./apps/edge/internal/transport ./apps/node/internal/transport -count=1`
7. `go vet ./apps/edge/internal/node ./apps/edge/internal/service ./apps/edge/internal/transport ./apps/node/internal/transport`
8. `make client-test`
9. `make client-build-web`
10. `rg --sort path -n 'Workspace(Open|Tool|Cancel|Cleanup)|request_id|RunRequest|NodeCommand|generation|raw' agent-contract/inner/edge-node-runtime-wire.md agent-spec/runtime/edge-node-execution.md`
11. `git diff --check`

Expected: the predecessor is uniquely complete; Go and Dart generation is reproducible and limited to the intended runtime bindings; client consumers still compile; all typed wire paths pass under race; immutable request identity and provider-contract separation remain explicit. Cached tests are not acceptable.

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