<!-- task=m-iop-owned-single-request-agent-execution/17_internal_artifact_wire plan=0 tag=API milestone-task=plan-stage,work-stage,review-stage -->

# Code Review Reference - API

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `Implementation Checklist`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> Execute the plan's selected root cause, scope, files, and dependency decisions as written. Do not choose another owner, narrow/expand the write boundary, or replace a fix with another verification attempt.
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.

## Overview

date=2026-08-07
task=m-iop-owned-single-request-agent-execution/17_internal_artifact_wire, plan=0, tag=API

## For the Review Agent

> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.

Compare implementation of each item against source files and verify that output in `Verification Results` matches code.
Review completion means the following steps are finished:

1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
2. Archive `CODE_REVIEW-cloud-G09.md` → `code_review_cloud_G09_0.log` and `PLAN-cloud-G09.md` → `plan_cloud_G09_0.log`.
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-iop-owned-single-request-agent-execution/17_internal_artifact_wire/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
4. If PASS, preserve `milestone-task=plan-stage,work-stage,review-stage` in `complete.log` and report it for runtime aggregation. Roadmap state evaluation belongs to `sync-milestone-workstate`.
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.

---

## Implementation Item Completion

| Item | Status |
|------|---------|
| API-1 Define the closed artifact protocol and canonical terminals | [x] |
| API-2 Implement Node-owned artifact access | [x] |
| API-3 Make artifacts part of coordinator lifecycle ownership | [x] |
| API-4 Synchronize the implemented contract and spec | [x] |

## Implementation Checklist

- [x] Add and regenerate the closed request-owned PLAN/REVIEW artifact protobuf family, including Go and Dart generated bindings.
- [x] Implement bounded Node internal artifact read/write handling and typed transport dispatch without exposing `.iop` to model workspace tools.
- [x] Integrate artifact access into the Edge wire and `SingleRequestController`, preserving one workspace open, exact admitted Node generation, terminal cleanup, cancellation, bounds, and raw-error redaction.
- [x] Update the inner runtime contract and current implementation spec, then run focused, race, broader Edge/Node/shared, generation, client, vet, and diff checks.
- [x] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

## Review-Only Checklist

> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
> Implementing agents must not modify or check this section.

- [x] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
- [x] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
- [x] Archive active `CODE_REVIEW-*-G??.md` to `code_review_cloud_G09_0.log`.
- [x] Archive active `PLAN-*-G??.md` to `plan_cloud_G09_0.log`.
- [x] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
- [x] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
- [x] If PASS, move active task directory `agent-task/m-iop-owned-single-request-agent-execution/17_internal_artifact_wire/` to `agent-task/archive/YYYY/MM/m-iop-owned-single-request-agent-execution/17_internal_artifact_wire/` and update this checklist at the final archive path.
- [x] If PASS, preserve and report `milestone-task=plan-stage,work-stage,review-stage` for runtime aggregation, without modifying roadmap or directly calling `update-roadmap`.
- [x] If PASS for split work, remove empty active parent `agent-task/m-iop-owned-single-request-agent-execution/` or verify it was kept due to remaining siblings/files.
- [ ] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.

## Deviations from Plan

None.

## Key Design Decisions

- Kept artifact authority closed at every layer: the wire carries enum-selected PLAN/REVIEW and READ/WRITE values, while Node alone maps them to `plan.md` and `review.md`. Public `WorkspaceOperation` and model-supplied relative-path authority remain unchanged.
- Reused the request-owned artifact inventory for reads. The Unix implementation opens descriptor-relatively with no-follow semantics, compares the recorded parent and file device/inode/type before reading, enforces the fixed one-MiB cap, and verifies identity and size again after the bounded read.
- Added one shared lazy-open coordinator primitive for artifact and model-tool callers. It serializes and caches the single open attempt, records artifact work in the existing in-flight wait group, and preserves the existing one-cleanup terminal gate for success, failure, and cancellation.
- Fenced artifact dispatch to the immutable admitted Node generation, enforced the admitted workspace effective output limit in both directions, and validated response identity, operation, canonical terminal, content shape, and raw-error exclusion before accepting a response.
- Kept provider-specific Plan/Work/Review drivers and actual Claude/Mac qualification deferred to SDD S12 and `claude-smoke`; this packet implements only the internal artifact and lifecycle foundation.

## Reviewer Checkpoints

- Confirm the wire accepts only enum-selected `PLAN`/`REVIEW` artifacts and never extends public `WorkspaceToolRequest` path authority.
- Confirm Node reads compare the inventoried device/inode/type through descriptor-relative no-follow operations and both directions enforce size caps.
- Confirm artifact-first, tool-after-artifact, cancel, terminal, stale-generation, and malformed-response paths preserve one open and one cleanup without raw error/path leakage.
- Confirm protobuf bindings are generator output and contract/spec text does not claim Plan/Work/Review provider drivers or actual Claude qualification.

## Verification Results

Paste actual stdout/stderr for every command. If a command changes, record the replacement and reason in `Deviations from Plan` before pasting its output.

### 1. Protobuf generation

`make proto && make proto-dart`

Expected: both generators exit zero and tracked Go/Dart bindings reflect the source schema.

```text
protoc \
	--go_out=. \
	--go_opt=module=iop \
	--proto_path=. \
	proto/iop/runtime.proto \
	proto/iop/node.proto \
	proto/iop/control.proto \
	proto/iop/job.proto
mkdir -p apps/client/lib/gen
protoc \
	--plugin=protoc-gen-dart=/config/.local/bin/protoc-gen-dart \
	--dart_out=apps/client/lib/gen \
	--proto_path=. \
	--proto_path=/config/.local/include \
	proto/iop/runtime.proto \
	proto/iop/node.proto \
	proto/iop/control.proto \
	proto/iop/job.proto
```

### 2. Focused cross-boundary tests

`go test ./packages/go/workspaceprotocol ./apps/node/internal/workspace ./apps/node/internal/node ./apps/node/internal/transport ./apps/edge/internal/transport ./apps/edge/internal/service -count=1`

Expected: all focused packages pass freshly.

```text
ok  	iop/packages/go/workspaceprotocol	0.027s
ok  	iop/apps/node/internal/workspace	0.717s
ok  	iop/apps/node/internal/node	1.022s
ok  	iop/apps/node/internal/transport	5.582s
ok  	iop/apps/edge/internal/transport	4.779s
ok  	iop/apps/edge/internal/service	6.478s
```

### 3. Race verification

`go test -race ./apps/edge/internal/service ./apps/node/internal/transport -run 'Test.*(WorkspaceArtifact|SingleRequestArtifact)' -count=1`

Expected: artifact lifecycle/correlation tests pass with no race report.

```text
ok  	iop/apps/edge/internal/service	1.115s
ok  	iop/apps/node/internal/transport	1.049s
```

### 4. Vet

`go vet ./packages/go/... && go vet ./apps/node/... && go vet ./apps/edge/internal/service`

Expected: relevant shared, Node, and Edge packages vet cleanly.

```text
```

### 5. Broader regressions

`go test ./packages/go/... ./apps/node/... ./apps/edge/... -count=1`

Expected: all shared and consumer packages pass freshly.

```text
ok  	iop/packages/go/audit	0.013s
ok  	iop/packages/go/auth	10.036s
ok  	iop/packages/go/config	0.206s
ok  	iop/packages/go/credentiallease	0.097s
?   	iop/packages/go/events	[no test files]
ok  	iop/packages/go/execution	0.015s
ok  	iop/packages/go/hostsetup	0.023s
?   	iop/packages/go/jobs	[no test files]
?   	iop/packages/go/metadata	[no test files]
ok  	iop/packages/go/observability	0.059s
?   	iop/packages/go/policy	[no test files]
ok  	iop/packages/go/streamgate	0.912s
?   	iop/packages/go/version	[no test files]
ok  	iop/packages/go/workspaceprotocol	0.030s
ok  	iop/apps/node/cmd/node	0.094s
ok  	iop/apps/node/internal/adapters	0.062s
?   	iop/apps/node/internal/adapters/mock	[no test files]
ok  	iop/apps/node/internal/adapters/ollama	0.030s
ok  	iop/apps/node/internal/adapters/openai_compat	0.159s
ok  	iop/apps/node/internal/adapters/vllm	0.142s
ok  	iop/apps/node/internal/bootstrap	1.440s
ok  	iop/apps/node/internal/node	1.106s
ok  	iop/apps/node/internal/router	0.515s
ok  	iop/apps/node/internal/store	0.024s
ok  	iop/apps/node/internal/transport	5.596s
ok  	iop/apps/node/internal/workspace	0.800s
ok  	iop/apps/edge/cmd/edge	0.222s
ok  	iop/apps/edge/internal/authprojection	0.065s
ok  	iop/apps/edge/internal/bootstrap	0.578s
ok  	iop/apps/edge/internal/configrefresh	0.123s
ok  	iop/apps/edge/internal/controlplane	6.608s
ok  	iop/apps/edge/internal/edgecmd	0.123s
ok  	iop/apps/edge/internal/edgevalidate	0.059s
ok  	iop/apps/edge/internal/events	0.049s
ok  	iop/apps/edge/internal/input	0.130s
ok  	iop/apps/edge/internal/input/a2a	0.124s
ok  	iop/apps/edge/internal/node	0.065s
ok  	iop/apps/edge/internal/openai	8.023s
ok  	iop/apps/edge/internal/opsconsole	0.045s
ok  	iop/apps/edge/internal/service	6.500s
ok  	iop/apps/edge/internal/transport	4.797s
```

### 6. Client generated-binding check

`make client-test`

Expected: generated Dart bindings compile and all Flutter tests pass.

```text
cd apps/client && flutter test
Resolving dependencies...
Downloading packages...
  _flutterfire_internals 1.3.59 (1.3.76 available)
  firebase_core 3.15.2 (4.13.0 available)
  firebase_core_platform_interface 6.0.3 (8.1.0 available)
  firebase_core_web 2.24.1 (3.10.0 available)
  firebase_messaging 15.2.10 (16.5.0 available)
  firebase_messaging_platform_interface 4.6.10 (4.9.3 available)
  firebase_messaging_web 3.10.10 (4.2.4 available)
  matcher 0.12.19 (0.12.20 available)
  meta 1.17.0 (1.19.0 available)
  test_api 0.7.10 (0.7.13 available)
  url_launcher_android 6.3.30 (6.3.32 available)
  vector_math 2.2.0 (2.4.2 available)
Got dependencies!
12 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.
00:00 +0: loading /config/workspace/iop-s0/apps/client/test/app_shell_test.dart
00:00 +0: /config/workspace/iop-s0/apps/client/test/app_shell_test.dart: Client App basic rendering and success handshake test
00:00 +1: /config/workspace/iop-s0/apps/client/test/app_shell_test.dart: Client App basic rendering and success handshake test
00:00 +2: /config/workspace/iop-s0/apps/client/test/edge_nodes_panels_test.dart: Client App opens Edges panel and displays Edge details
00:00 +3: /config/workspace/iop-s0/apps/client/test/edge_nodes_panels_test.dart: Client App opens Edges panel and displays Edge details
00:00 +4: /config/workspace/iop-s0/apps/client/test/edge_nodes_panels_test.dart: Client App opens Edges panel and displays Edge details
00:00 +5: /config/workspace/iop-s0/apps/client/test/edge_nodes_panels_test.dart: Client App opens Edges panel and displays Edge details
00:00 +6: /config/workspace/iop-s0/apps/client/test/edge_nodes_panels_test.dart: Client App opens Edges panel and displays Edge details
00:00 +7: /config/workspace/iop-s0/apps/client/test/edge_nodes_panels_test.dart: Client App opens Edges panel and displays Edge details
00:00 +8: /config/workspace/iop-s0/apps/client/test/edge_nodes_panels_test.dart: Client App opens Edges panel and displays Edge details
00:00 +9: /config/workspace/iop-s0/apps/client/test/edge_nodes_panels_test.dart: Client App opens Edges panel and displays Edge details
00:00 +10: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +11: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +12: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +13: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +14: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +15: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +16: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +17: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +18: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +19: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +20: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +21: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +22: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +23: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +24: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +25: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +26: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +27: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +28: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +29: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App opens Operations panel and verifies history and provider commands
00:01 +30: /config/workspace/iop-s0/apps/client/test/provider_status_test.dart: EdgeStatusResponseView parses health=available/status=available and health=unavailable/status=backlog
00:01 +31: /config/workspace/iop-s0/apps/client/test/edge_nodes_panels_test.dart: EdgesPanel preserves loading error and empty states
00:01 +32: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App handles unsupported or error command responses and shows error banner
00:01 +33: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App handles unsupported or error command responses and shows error banner
00:02 +34: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App handles unsupported or error command responses and shows error banner
00:02 +35: /config/workspace/iop-s0/apps/client/test/iop_wire/generated_proto_import_test.dart: Generated proto compile guard and field verification
00:02 +36: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App gates node.status and provider.command without required inputs
00:02 +37: /config/workspace/iop-s0/apps/client/test/notification_integration_test.dart: notification stream from NexoNotificationHostIntegration connects to UI snackbar
00:03 +38: /config/workspace/iop-s0/apps/client/test/notification_integration_test.dart: notification stream from NexoNotificationHostIntegration connects to UI snackbar
00:03 +39: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App requires Ollama API path
00:03 +40: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App requires Ollama API path
00:03 +41: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: Client App requires Ollama API path
00:03 +42: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: RuntimePanel keeps loaded empty history visible while a command is pending
00:03 +43: /config/workspace/iop-s0/apps/client/test/runtime_panel_test.dart: RuntimePanel renders operations empty and fetch error states
00:03 +44: All tests passed!
```

### 7. Boundary search

`rg --sort path -n 'WorkspaceArtifact|plan\.md|review\.md' proto/iop/runtime.proto apps/edge apps/node packages/go/workspaceprotocol agent-contract/inner/edge-node-runtime-wire.md agent-spec/runtime/edge-node-execution.md`

Expected: results are confined to the private artifact/runtime boundary and its tests/docs.

```text
proto/iop/runtime.proto:443:// WorkspaceArtifactKind is a closed coordinator-only artifact selector. Node
proto/iop/runtime.proto:446:enum WorkspaceArtifactKind {
proto/iop/runtime.proto:452:enum WorkspaceArtifactOperation {
proto/iop/runtime.proto:458:message WorkspaceArtifactRequest {
proto/iop/runtime.proto:460:  WorkspaceArtifactKind kind = 2;
proto/iop/runtime.proto:461:  WorkspaceArtifactOperation operation = 3;
proto/iop/runtime.proto:465:message WorkspaceArtifactResponse {
proto/iop/runtime.proto:467:  WorkspaceArtifactKind kind = 2;
proto/iop/runtime.proto:468:  WorkspaceArtifactOperation operation = 3;
apps/edge/internal/openai/artifact_pair_test.go:149:				{name: "traversal path", planPath: ".iop/job/../escape/plan.md"},
apps/edge/internal/openai/artifact_pair_test.go:150:				{name: "alternate request path", planPath: ".iop/job/other-request/plan.md"},
apps/edge/internal/openai/hot_path_direct_test.go:599:		providerBody := `{"id":"chatcmpl-provider-bad","created":1777000606,"choices":[{"message":{"role":"assistant","content":"","tool_calls":[{"id":"call_bad_control","type":"function","function":{"name":"shell","arguments":"{\"path\":\".iop/job/not-issued/plan.md\"}"}}]},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":1,"completion_tokens":1,"total_tokens":2}}`
apps/edge/internal/openai/hot_path_selector.go:38:	PlanPath   string // e.g. ".iop/job/<request_id>/plan.md"
apps/edge/internal/openai/hot_path_selector.go:39:	ReviewPath string // e.g. ".iop/job/<request_id>/review.md"
apps/edge/internal/openai/hot_path_selector.go:48:		PlanPath:   jobDir + "/plan.md",
apps/edge/internal/openai/hot_path_selector.go:49:		ReviewPath: jobDir + "/review.md",
apps/edge/internal/openai/hot_path_selector_test.go:41:				{ID: "call_plan", Name: "write_file", RawArgs: `{"path":".iop\/job\/req_test_123\/plan.md"}`},
apps/edge/internal/openai/hot_path_selector_test.go:66:			output: normalizedStageOutput{ToolCalls: []normalizedToolCall{{ID: "call_wrong", Name: "write_file", Arguments: map[string]any{"path": ".iop/job/another/plan.md"}}}},
apps/edge/internal/openai/hot_path_selector_test.go:86:			output: normalizedStageOutput{ToolCalls: []normalizedToolCall{{ID: "call_raw_conflict", Name: "write_file", Arguments: map[string]any{"path": issued.PlanPath}, RawArgs: `{"path":".iop/job/req_test_123/review.md"}`}}},
apps/edge/internal/openai/hot_path_selector_test.go:109:			output: normalizedStageOutput{Content: "I would choose light and mention .iop/job/req_test_123/plan.md in prose."},
apps/edge/internal/openai/workspace_tool_binding_test.go:150:		payload, err := encodeWorkspaceCall(binding, opKindWrite, writeCall(".iop/job/request-1/plan.md"))
apps/edge/internal/openai/workspace_tool_binding_test.go:161:		payload, err := encodeWorkspaceCall(binding, opKindWrite, writeCall(".iop/job/request-2/plan.md"))
apps/edge/internal/openai/workspace_tool_binding_test.go:201:			if err := os.Symlink(filepath.Join(outside, "target.md"), filepath.Join(root, ".iop", "job", "request-3", "plan.md")); err != nil {
apps/edge/internal/openai/workspace_tool_binding_test.go:217:			payload, err := encodeWorkspaceCall(binding, opKindWrite, writeCall(".iop/job/request-3/plan.md"))
apps/edge/internal/openai/workspace_tool_binding_test.go:245:			Arguments: map[string]any{"path": ".iop/job/r1/plan.md", "content": content, "ignored": "must not pass"},
apps/edge/internal/openai/workspace_tool_binding_test.go:263:		call := normalizedToolCall{ID: "public-2", Name: "run_workspace", Arguments: map[string]any{"path": ".iop/job/r2/review.md", "content": "hello 'world'"}}
apps/edge/internal/openai/workspace_tool_binding_test.go:272:		wantArgv := []string{"write", ".iop/job/r2/review.md", "hello 'world'"}
apps/edge/internal/openai/workspace_tool_binding_test.go:288:		payload, err := encodeWorkspaceCall(binding, opKindWrite, normalizedToolCall{ID: "public-4", Name: "write_file", Arguments: map[string]any{"path": ".iop/job/r4/plan.md", "content": "x"}})
apps/edge/internal/openai/workspace_tool_binding_test.go:297:		if !strings.Contains(payload.containmentGuard, `IOP_WS_CANDIDATE="$IOP_WS_ROOT/.iop/job/r4/plan.md"`) {
apps/edge/internal/openai/workspace_tool_binding_test.go:307:		Arguments: map[string]any{"path": ".iop/job/r5/plan.md", "content": "plan"},
apps/edge/internal/openai/workspace_tool_binding_test.go:341:			"path":      func(p *workspaceEncodedPayload) { p.safePath = ".iop/job/r5/review.md" },
apps/edge/internal/service/single_request_artifact.go:28:type singleRequestWorkspaceArtifactRuntime interface {
apps/edge/internal/service/single_request_artifact.go:29:	workspaceArtifact(context.Context, *SingleRequestWorkspaceBinding, *iop.WorkspaceArtifactRequest, int) (*iop.WorkspaceArtifactResponse, error)
apps/edge/internal/service/single_request_artifact.go:35:	runtime     singleRequestWorkspaceArtifactRuntime
apps/edge/internal/service/single_request_artifact.go:38:	kind        iop.WorkspaceArtifactKind
apps/edge/internal/service/single_request_artifact.go:61:	response, err := operation.runtime.workspaceArtifact(operation.ctx, operation.binding, &iop.WorkspaceArtifactRequest{
apps/edge/internal/service/single_request_artifact.go:64:		Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ,
apps/edge/internal/service/single_request_artifact.go:93:	response, err := operation.runtime.workspaceArtifact(operation.ctx, operation.binding, &iop.WorkspaceArtifactRequest{
apps/edge/internal/service/single_request_artifact.go:96:		Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE,
apps/edge/internal/service/single_request_artifact.go:130:	runtime, ok := openRuntime.(singleRequestWorkspaceArtifactRuntime)
apps/edge/internal/service/single_request_artifact.go:163:func singleRequestArtifactProtoKind(kind SingleRequestArtifactKind) (iop.WorkspaceArtifactKind, bool) {
apps/edge/internal/service/single_request_artifact.go:166:		return iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN, true
apps/edge/internal/service/single_request_artifact.go:168:		return iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_REVIEW, true
apps/edge/internal/service/single_request_artifact.go:170:		return iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_UNSPECIFIED, false
apps/edge/internal/service/single_request_artifact_test.go:23:	artifacts     map[iop.WorkspaceArtifactKind][]byte
apps/edge/internal/service/single_request_artifact_test.go:27:	runtime := &artifactLifecycleRuntime{artifactStart: make(chan struct{}), artifacts: make(map[iop.WorkspaceArtifactKind][]byte)}
apps/edge/internal/service/single_request_artifact_test.go:44:func (r *artifactLifecycleRuntime) workspaceArtifact(_ context.Context, _ *SingleRequestWorkspaceBinding, req *iop.WorkspaceArtifactRequest, maximum int) (*iop.WorkspaceArtifactResponse, error) {
apps/edge/internal/service/single_request_artifact_test.go:50:	response := &iop.WorkspaceArtifactResponse{RequestId: req.GetRequestId(), Kind: req.GetKind(), Operation: req.GetOperation(), Status: iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS}
apps/edge/internal/service/single_request_artifact_test.go:54:	case iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE:
apps/edge/internal/service/single_request_artifact_test.go:59:	case iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ:
apps/edge/internal/service/single_request_tool_types_test.go:67:		"private runtime path":   internalToolCall(InternalWorkspaceToolDelete, `{"relative_path":".iop/job/request-1/plan.md"}`),
apps/edge/internal/service/workspace_wire.go:151:func (s *Service) workspaceArtifact(ctx context.Context, binding *SingleRequestWorkspaceBinding, req *iop.WorkspaceArtifactRequest, maxBytes int) (*iop.WorkspaceArtifactResponse, error) {
apps/edge/internal/service/workspace_wire.go:152:	if req == nil || req.GetRequestId() == "" || binding == nil || maxBytes < 1 || !validWorkspaceArtifactKind(req.GetKind()) || !validWorkspaceArtifactOperation(req.GetOperation()) {
apps/edge/internal/service/workspace_wire.go:156:	if (req.GetOperation() == iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ && len(req.GetContent()) != 0) || len(req.GetContent()) > limit {
apps/edge/internal/service/workspace_wire.go:159:	outbound := &iop.WorkspaceArtifactRequest{
apps/edge/internal/service/workspace_wire.go:164:	var response *iop.WorkspaceArtifactResponse
apps/edge/internal/service/workspace_wire.go:167:		response, requestErr = toki.SendRequestTyped[*iop.WorkspaceArtifactRequest, *iop.WorkspaceArtifactResponse](&entry.Client.Communicator, outbound, wait)
apps/edge/internal/service/workspace_wire.go:173:	return validateWorkspaceArtifactResponse(outbound, response, limit)
apps/edge/internal/service/workspace_wire.go:271:func validateWorkspaceArtifactResponse(req *iop.WorkspaceArtifactRequest, resp *iop.WorkspaceArtifactResponse, limit int) (*iop.WorkspaceArtifactResponse, error) {
apps/edge/internal/service/workspace_wire.go:280:		(resp.GetOperation() == iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE && len(resp.GetContent()) != 0) {
apps/edge/internal/service/workspace_wire.go:286:func validWorkspaceArtifactKind(kind iop.WorkspaceArtifactKind) bool {
apps/edge/internal/service/workspace_wire.go:287:	return kind == iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN || kind == iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_REVIEW
apps/edge/internal/service/workspace_wire.go:290:func validWorkspaceArtifactOperation(operation iop.WorkspaceArtifactOperation) bool {
apps/edge/internal/service/workspace_wire.go:291:	return operation == iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ || operation == iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE
apps/edge/internal/service/workspace_wire_test.go:115:		toki.TypeNameOf(&iop.WorkspaceArtifactRequest{}): parseWorkspaceMessage[*iop.WorkspaceArtifactRequest],
apps/edge/internal/service/workspace_wire_test.go:125:		toki.TypeNameOf(&iop.WorkspaceArtifactResponse{}): parseWorkspaceMessage[*iop.WorkspaceArtifactResponse],
apps/edge/internal/service/workspace_wire_test.go:144:	case *iop.WorkspaceArtifactRequest:
apps/edge/internal/service/workspace_wire_test.go:145:		return any(&iop.WorkspaceArtifactRequest{}).(T)
apps/edge/internal/service/workspace_wire_test.go:154:	case *iop.WorkspaceArtifactResponse:
apps/edge/internal/service/workspace_wire_test.go:155:		return any(&iop.WorkspaceArtifactResponse{}).(T)
apps/edge/internal/service/workspace_wire_test.go:165:func TestWorkspaceArtifactWire(t *testing.T) {
apps/edge/internal/service/workspace_wire_test.go:169:		seen := make(chan *iop.WorkspaceArtifactRequest, 2)
apps/edge/internal/service/workspace_wire_test.go:170:		toki.AddRequestListenerTyped[*iop.WorkspaceArtifactRequest, *iop.WorkspaceArtifactResponse](&node.Communicator, func(req *iop.WorkspaceArtifactRequest) (*iop.WorkspaceArtifactResponse, error) {
apps/edge/internal/service/workspace_wire_test.go:172:			seen <- proto.Clone(req).(*iop.WorkspaceArtifactRequest)
apps/edge/internal/service/workspace_wire_test.go:173:			response := &iop.WorkspaceArtifactResponse{RequestId: req.GetRequestId(), Kind: req.GetKind(), Operation: req.GetOperation(), Status: iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS}
apps/edge/internal/service/workspace_wire_test.go:174:			if req.GetOperation() == iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ {
apps/edge/internal/service/workspace_wire_test.go:180:		write := &iop.WorkspaceArtifactRequest{RequestId: "request-1", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN, Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE, Content: []byte("bounded plan")}
apps/edge/internal/service/workspace_wire_test.go:188:		read := &iop.WorkspaceArtifactRequest{RequestId: "request-1", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN, Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ}
apps/edge/internal/service/workspace_wire_test.go:194:		oversized := &iop.WorkspaceArtifactRequest{RequestId: "request-1", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_REVIEW, Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE, Content: []byte(strings.Repeat("x", binding.Limits.MaxOutputBytes+1))}
apps/edge/internal/service/workspace_wire_test.go:203:	for name, response := range map[string]*iop.WorkspaceArtifactResponse{
apps/edge/internal/service/workspace_wire_test.go:205:			RequestId: "request-1", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_REVIEW,
apps/edge/internal/service/workspace_wire_test.go:206:			Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ,
apps/edge/internal/service/workspace_wire_test.go:210:			RequestId: "request-1", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN,
apps/edge/internal/service/workspace_wire_test.go:211:			Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ,
apps/edge/internal/service/workspace_wire_test.go:215:			RequestId: "request-1", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN,
apps/edge/internal/service/workspace_wire_test.go:216:			Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ,
apps/edge/internal/service/workspace_wire_test.go:223:			toki.AddRequestListenerTyped[*iop.WorkspaceArtifactRequest, *iop.WorkspaceArtifactResponse](&node.Communicator, func(*iop.WorkspaceArtifactRequest) (*iop.WorkspaceArtifactResponse, error) {
apps/edge/internal/service/workspace_wire_test.go:224:				return proto.Clone(response).(*iop.WorkspaceArtifactResponse), nil
apps/edge/internal/service/workspace_wire_test.go:226:			request := &iop.WorkspaceArtifactRequest{RequestId: "request-1", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN, Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ}
apps/edge/internal/service/workspace_wire_test.go:243:		toki.AddRequestListenerTyped[*iop.WorkspaceArtifactRequest, *iop.WorkspaceArtifactResponse](&newNode.Communicator, func(req *iop.WorkspaceArtifactRequest) (*iop.WorkspaceArtifactResponse, error) {
apps/edge/internal/service/workspace_wire_test.go:245:			return &iop.WorkspaceArtifactResponse{RequestId: req.GetRequestId(), Kind: req.GetKind(), Operation: req.GetOperation(), Status: iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS}, nil
apps/edge/internal/service/workspace_wire_test.go:249:		request := &iop.WorkspaceArtifactRequest{RequestId: "request-1", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN, Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ}
apps/edge/internal/transport/server.go:72:		toki.TypeNameOf(&iop.WorkspaceArtifactResponse{}): func(b []byte) (proto.Message, error) {
apps/edge/internal/transport/server.go:73:			m := &iop.WorkspaceArtifactResponse{}
apps/edge/internal/transport/server_test.go:57:		&iop.WorkspaceArtifactResponse{RequestId: "request-1", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN, Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ, Status: iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS, Content: []byte("plan")},
apps/node/internal/node/workspace_handler.go:109:// OnWorkspaceArtifact serves only the closed PLAN/REVIEW artifact family. Node
apps/node/internal/node/workspace_handler.go:112:func (n *Node) OnWorkspaceArtifact(_ context.Context, _ *transport.Session, req *iop.WorkspaceArtifactRequest) (*iop.WorkspaceArtifactResponse, error) {
apps/node/internal/node/workspace_handler.go:116:		return &iop.WorkspaceArtifactResponse{Status: status, ErrorCode: code, Error: msg}, nil
apps/node/internal/node/workspace_handler.go:118:	response := &iop.WorkspaceArtifactResponse{
apps/node/internal/node/workspace_handler.go:122:	operationOK := req.GetOperation() == iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ ||
apps/node/internal/node/workspace_handler.go:123:		req.GetOperation() == iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE
apps/node/internal/node/workspace_handler.go:125:		(req.GetOperation() == iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ && len(req.GetContent()) != 0) {
apps/node/internal/node/workspace_handler.go:135:	case iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ:
apps/node/internal/node/workspace_handler.go:142:	case iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE:
apps/node/internal/node/workspace_handler.go:245:func workspaceArtifactName(kind iop.WorkspaceArtifactKind) (string, bool) {
apps/node/internal/node/workspace_handler.go:247:	case iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN:
apps/node/internal/node/workspace_handler.go:248:		return "plan.md", true
apps/node/internal/node/workspace_handler.go:249:	case iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_REVIEW:
apps/node/internal/node/workspace_handler.go:250:		return "review.md", true
apps/node/internal/node/workspace_handler.go:256:func applyArtifactFailure(response *iop.WorkspaceArtifactResponse, err error) {
apps/node/internal/node/workspace_handler_test.go:137:func TestNodeWorkspaceArtifactMapping(t *testing.T) {
apps/node/internal/node/workspace_handler_test.go:145:	readPlan := &iop.WorkspaceArtifactRequest{
apps/node/internal/node/workspace_handler_test.go:146:		RequestId: "request-artifact", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN,
apps/node/internal/node/workspace_handler_test.go:147:		Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ,
apps/node/internal/node/workspace_handler_test.go:149:	missing, err := n.OnWorkspaceArtifact(context.Background(), nil, readPlan)
apps/node/internal/node/workspace_handler_test.go:153:	writePlan := &iop.WorkspaceArtifactRequest{
apps/node/internal/node/workspace_handler_test.go:154:		RequestId: "request-artifact", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN,
apps/node/internal/node/workspace_handler_test.go:155:		Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE, Content: []byte("bounded plan"),
apps/node/internal/node/workspace_handler_test.go:157:	written, err := n.OnWorkspaceArtifact(context.Background(), nil, writePlan)
apps/node/internal/node/workspace_handler_test.go:161:	read, err := n.OnWorkspaceArtifact(context.Background(), nil, readPlan)
apps/node/internal/node/workspace_handler_test.go:165:	if data, err := os.ReadFile(filepath.Join(root, ".iop", "job", "request-artifact", "plan.md")); err != nil || string(data) != "bounded plan" {
apps/node/internal/node/workspace_handler_test.go:169:	for name, request := range map[string]*iop.WorkspaceArtifactRequest{
apps/node/internal/node/workspace_handler_test.go:171:			RequestId: "request-artifact", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_UNSPECIFIED,
apps/node/internal/node/workspace_handler_test.go:172:			Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ,
apps/node/internal/node/workspace_handler_test.go:175:			RequestId: "request-artifact", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_REVIEW,
apps/node/internal/node/workspace_handler_test.go:176:			Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_UNSPECIFIED,
apps/node/internal/node/workspace_handler_test.go:179:			RequestId: "request-artifact", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_REVIEW,
apps/node/internal/node/workspace_handler_test.go:180:			Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ, Content: []byte("must not be accepted"),
apps/node/internal/node/workspace_handler_test.go:183:			RequestId: "request-artifact", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_REVIEW,
apps/node/internal/node/workspace_handler_test.go:184:			Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE, Content: []byte(strings.Repeat("x", 1<<20+1)),
apps/node/internal/node/workspace_handler_test.go:188:			response, callErr := n.OnWorkspaceArtifact(context.Background(), nil, request)
apps/node/internal/node/workspace_handler_test.go:194:	if _, err := os.Stat(filepath.Join(root, ".iop", "job", "request-artifact", "review.md")); !errors.Is(err, os.ErrNotExist) {
apps/node/internal/node/workspace_handler_test.go:195:		t.Fatalf("malformed artifact request created review.md: %v", err)
apps/node/internal/node/workspace_handler_test.go:199:func TestNodeWorkspaceArtifactStableFailures(t *testing.T) {
apps/node/internal/node/workspace_handler_test.go:201:	request := &iop.WorkspaceArtifactRequest{
apps/node/internal/node/workspace_handler_test.go:202:		RequestId: "request-artifact", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN,
apps/node/internal/node/workspace_handler_test.go:203:		Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ,
apps/node/internal/node/workspace_handler_test.go:205:	missingRuntime, err := n.OnWorkspaceArtifact(context.Background(), nil, request)
apps/node/internal/node/workspace_handler_test.go:209:	invalid, err := n.OnWorkspaceArtifact(context.Background(), nil, nil)
apps/node/internal/node/workspace_handler_test.go:219:	write := proto.Clone(request).(*iop.WorkspaceArtifactRequest)
apps/node/internal/node/workspace_handler_test.go:220:	write.Operation = iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE
apps/node/internal/node/workspace_handler_test.go:222:	if response, err := n.OnWorkspaceArtifact(context.Background(), nil, write); err != nil || response.GetStatus() != iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS {
apps/node/internal/node/workspace_handler_test.go:225:	target := filepath.Join(root, ".iop", "job", "request-artifact", "plan.md")
apps/node/internal/node/workspace_handler_test.go:232:	replaced, err := n.OnWorkspaceArtifact(context.Background(), nil, request)
apps/node/internal/node/workspace_handler_test.go:279:	if err := runtime.WriteInternalArtifact("request-cleanup", "plan.md", []byte("plan")); err != nil {
apps/node/internal/transport/parser.go:52:		toki.TypeNameOf(&iop.WorkspaceArtifactRequest{}): func(b []byte) (proto.Message, error) {
apps/node/internal/transport/parser.go:53:			m := &iop.WorkspaceArtifactRequest{}
apps/node/internal/transport/parser_test.go:47:func TestNodeParserMapWorkspaceArtifact(t *testing.T) {
apps/node/internal/transport/parser_test.go:60:		&iop.WorkspaceArtifactRequest{RequestId: "request-1", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN, Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE, Content: []byte("plan")},
apps/node/internal/transport/parser_test.go:91:	artifactFields := (&iop.WorkspaceArtifactRequest{}).ProtoReflect().Descriptor().Fields()
apps/node/internal/transport/parser_test.go:95:			t.Fatalf("WorkspaceArtifactRequest.%s number = %v, want %d", name, field, number)
apps/node/internal/transport/parser_test.go:98:	if iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN != 1 || iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_REVIEW != 2 ||
apps/node/internal/transport/parser_test.go:99:		iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ != 1 || iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE != 2 {
apps/node/internal/transport/session.go:34:	OnWorkspaceArtifact(ctx context.Context, sess *Session, req *iop.WorkspaceArtifactRequest) (*iop.WorkspaceArtifactResponse, error)
apps/node/internal/transport/session.go:204:	addWorkspaceRequestListener(s, &iop.WorkspaceArtifactRequest{}, func(req *iop.WorkspaceArtifactRequest) proto.Message {
apps/node/internal/transport/session.go:209:		resp, err := workspace.OnWorkspaceArtifact(s.Context(), s, req)
apps/node/internal/transport/session.go:311:func workspaceArtifactUnsupported(req *iop.WorkspaceArtifactRequest) *iop.WorkspaceArtifactResponse {
apps/node/internal/transport/session.go:312:	return &iop.WorkspaceArtifactResponse{RequestId: req.GetRequestId(), Kind: req.GetKind(), Operation: req.GetOperation(), Status: iop.WorkspaceStatus_WORKSPACE_STATUS_UNSUPPORTED, ErrorCode: iop.WorkspaceErrorCode_WORKSPACE_ERROR_CODE_NOT_READY, Error: "workspace runtime not ready"}
apps/node/internal/transport/session.go:315:func workspaceArtifactFailed(req *iop.WorkspaceArtifactRequest) *iop.WorkspaceArtifactResponse {
apps/node/internal/transport/session.go:316:	return &iop.WorkspaceArtifactResponse{RequestId: req.GetRequestId(), Kind: req.GetKind(), Operation: req.GetOperation(), Status: iop.WorkspaceStatus_WORKSPACE_STATUS_ERROR, ErrorCode: iop.WorkspaceErrorCode_WORKSPACE_ERROR_CODE_INTERNAL, Error: "workspace artifact operation failed"}
apps/node/internal/transport/session_test.go:51:func (h *workspaceHandler) OnWorkspaceArtifact(_ context.Context, _ *transport.Session, req *iop.WorkspaceArtifactRequest) (*iop.WorkspaceArtifactResponse, error) {
apps/node/internal/transport/session_test.go:52:	return &iop.WorkspaceArtifactResponse{RequestId: req.GetRequestId(), Kind: req.GetKind(), Operation: req.GetOperation(), Status: iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS, Content: append([]byte(nil), req.GetContent()...)}, nil
apps/node/internal/transport/session_test.go:78:func TestSessionWorkspaceArtifactRequest(t *testing.T) {
apps/node/internal/transport/session_test.go:91:	artifact, err := toki.SendRequestTyped[*iop.WorkspaceArtifactRequest, *iop.WorkspaceArtifactResponse](&edgeSide.Communicator, &iop.WorkspaceArtifactRequest{RequestId: "request-1", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN, Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_WRITE, Content: []byte("plan")}, 2*time.Second)
apps/node/internal/transport/session_test.go:92:	if err != nil || artifact.GetStatus() != iop.WorkspaceStatus_WORKSPACE_STATUS_SUCCESS || artifact.GetKind() != iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_PLAN || string(artifact.GetContent()) != "plan" {
apps/node/internal/transport/session_test.go:119:func TestSessionWorkspaceArtifactRequestWithoutOptionalHandler(t *testing.T) {
apps/node/internal/transport/session_test.go:124:	response, err := toki.SendRequestTyped[*iop.WorkspaceArtifactRequest, *iop.WorkspaceArtifactResponse](&edgeSide.Communicator, &iop.WorkspaceArtifactRequest{RequestId: "request-1", Kind: iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_REVIEW, Operation: iop.WorkspaceArtifactOperation_WORKSPACE_ARTIFACT_OPERATION_READ}, 2*time.Second)
apps/node/internal/transport/session_test.go:128:	if response.GetStatus() != iop.WorkspaceStatus_WORKSPACE_STATUS_UNSUPPORTED || response.GetErrorCode() != iop.WorkspaceErrorCode_WORKSPACE_ERROR_CODE_NOT_READY || response.GetRequestId() != "request-1" || response.GetKind() != iop.WorkspaceArtifactKind_WORKSPACE_ARTIFACT_KIND_REVIEW {
apps/node/internal/transport/session_test.go:335:		toki.TypeNameOf(&iop.WorkspaceArtifactResponse{}): func(b []byte) (proto.Message, error) {
apps/node/internal/transport/session_test.go:336:			m := &iop.WorkspaceArtifactResponse{}
apps/node/internal/transport/session_test.go:369:		toki.TypeNameOf(&iop.WorkspaceArtifactRequest{}): func(b []byte) (proto.Message, error) {
apps/node/internal/transport/session_test.go:370:			m := &iop.WorkspaceArtifactRequest{}
apps/node/internal/workspace/cleanup_test.go:25:	if err := runtime.WriteInternalArtifact("request-1", "plan.md", []byte("request one plan")); err != nil {
apps/node/internal/workspace/cleanup_test.go:28:	if err := runtime.WriteInternalArtifact("request-1", "review.md", []byte("request one review")); err != nil {
apps/node/internal/workspace/cleanup_test.go:31:	if err := runtime.WriteInternalArtifact("request-2", "plan.md", []byte("request two plan")); err != nil {
apps/node/internal/workspace/cleanup_test.go:34:	for name, want := range map[string]string{"plan.md": "request one plan", "review.md": "request one review"} {
apps/node/internal/workspace/cleanup_test.go:40:	if got, err := runtime.ReadInternalArtifact("request-2", "plan.md"); err != nil || string(got) != "request two plan" {
apps/node/internal/workspace/cleanup_test.go:43:	if _, err := runtime.ReadInternalArtifact("request-2", "review.md"); !errors.Is(err, fs.ErrNotExist) {
apps/node/internal/workspace/cleanup_test.go:46:	if _, err := runtime.ReadInternalArtifact("request-1", "../request-2/plan.md"); !errors.Is(err, ErrInvalidRequest) {
apps/node/internal/workspace/cleanup_test.go:50:	target := filepath.Join(requestArtifactRoot(root, "request-1"), "plan.md")
apps/node/internal/workspace/cleanup_test.go:57:	if _, err := runtime.ReadInternalArtifact("request-1", "plan.md"); err == nil || errors.Is(err, fs.ErrNotExist) {
apps/node/internal/workspace/cleanup_test.go:74:	if err := runtime.WriteInternalArtifact("request-a", "plan.md", []byte("plan")); err != nil {
apps/node/internal/workspace/cleanup_test.go:77:	if err := runtime.WriteInternalArtifact("request-a", "nested/review.md", []byte("review")); err != nil {
apps/node/internal/workspace/cleanup_test.go:80:	if err := runtime.WriteInternalArtifact("request-b", "plan.md", []byte("foreign request")); err != nil {
apps/node/internal/workspace/cleanup_test.go:122:	if data, err := os.ReadFile(filepath.Join(requestArtifactRoot(root, "request-b"), "plan.md")); err != nil || string(data) != "foreign request" {
apps/node/internal/workspace/cleanup_test.go:138:	if err := runtime.WriteInternalArtifact("request-process", "plan.md", []byte("plan")); err != nil {
apps/node/internal/workspace/cleanup_test.go:180:	if err := runtime.WriteInternalArtifact("request-1", "plan.md", []byte("preserve on timeout")); err != nil {
apps/node/internal/workspace/cleanup_test.go:205:	if _, err := os.Stat(filepath.Join(requestArtifactRoot(root, "request-1"), "plan.md")); err != nil {
apps/node/internal/workspace/cleanup_test.go:227:			if err := runtime.WriteInternalArtifact(requestID, "plan.md", []byte("owned")); err != nil {
apps/node/internal/workspace/cleanup_test.go:230:			target := filepath.Join(requestArtifactRoot(root, requestID), "plan.md")
apps/node/internal/workspace/cleanup_test.go:321:	if err := runtime.WriteInternalArtifact("request-close", "review.md", []byte("review")); err != nil {
apps/node/internal/workspace/runtime_test.go:162:	if _, err := first.internalPath(".iop/job/request-2/plan.md"); err == nil {
apps/node/internal/workspace/runtime_test.go:165:	if _, err := first.internalPath(".iop/job/request-1/plan.md"); err != nil {
agent-contract/inner/edge-node-runtime-wire.md:71:- workspace wire: `NodeConfigPayload.workspaces` delivers the operator-approved Node-private catalog. Edge constructs `WorkspaceOpenRequest` from the frozen request authority and sends every workspace request only to the exact admitted Node id and dispatch-ready connection generation; Node returns the paired typed response. The coordinator-only `WorkspaceArtifactRequest`/`WorkspaceArtifactResponse` family selects only `PLAN` or `REVIEW` and `READ` or `WRITE`; Node alone maps the kind to `plan.md` or `review.md`. This boundary is independent of provider `RunRequest`, provider execution, and `NodeCommand`.
agent-contract/inner/edge-node-runtime-wire.md:100:- `WorkspaceArtifactRequest`: carries only immutable `request_id`, closed `kind` (`PLAN` or `REVIEW`), closed `operation` (`READ` or `WRITE`), and bounded write `content`. READ requires empty request content. It has no relative path, public workspace operation, stage/tool-call identity, Node/root selector, executable, or environment.
agent-contract/inner/edge-node-runtime-wire.md:101:- `WorkspaceArtifactResponse`: echoes `request_id`, `kind`, and `operation`, carries the canonical status/error triple, and carries bounded content only for a successful READ. Successful WRITE and every non-success response have empty content. Canonical outcomes are success, runtime not-ready, artifact not-found, invalid request, and generic internal failure; contradictory triples, mismatched echoes, oversized content, and raw Node error text are rejected as a stable Edge transport error.
agent-contract/inner/edge-node-runtime-wire.md:133:- The Node parser accepts `WorkspaceOpenRequest`, `WorkspaceToolRequest`, `WorkspaceArtifactRequest`, `WorkspaceCancelRequest`, and `WorkspaceCleanupRequest`; the Edge parser accepts all five paired responses. Existing provider request/response registrations are unchanged.
agent-spec/runtime/edge-node-execution.md:181:| workspace runtime wire | The dedicated `WorkspaceOpen`/`Tool`/`Artifact`/`Cancel`/`Cleanup` request-response families carry immutable coordinator identities and closed status/error codes. `WorkspaceArtifact` admits only enum-selected `PLAN`/`REVIEW` and `READ`/`WRITE`; it carries no relative path. Edge overwrites open capabilities with frozen request authority; Node copies only catalog-subset operations/command ids and equal-or-lower effective limits. |
agent-spec/runtime/edge-node-execution.md:184:| request-owned internal artifacts | `SingleRequestController` exposes closed plan/review read/write operations. Artifact calls and model workspace tools share one serialized lazy `WorkspaceOpen`, the exact admitted Node generation, the active stage deadline, the immutable output bound, in-flight work accounting, and one terminal cleanup. Node alone maps selectors to `plan.md` and `review.md`, and inventoried descriptor-relative reads fail closed on identity replacement. |
agent-spec/runtime/edge-node-execution.md:206:- Request-owned plan and review artifact access is implemented between the controller and the same dedicated workspace wire. Only `SingleRequestArtifactPlan` and `SingleRequestArtifactReview` are accepted. Artifact and model-tool callers share one serialized open attempt and the same opened cleanup gate; terminal and cancellation paths wait for in-flight artifact work before issuing exactly one cleanup. Edge bounds writes before dispatch and reads before acceptance, validates the echoed kind/operation and canonical terminal, and never reselects after a generation mismatch. Node maps the closed selectors to `plan.md` and `review.md`, validates the inventoried parent/file identity with descriptor-relative no-follow reads, and never grants the public workspace tool surface access to `.iop`.
agent-spec/runtime/edge-node-execution.md:233:            Edge->>Node: WorkspaceArtifactRequest(PLAN or REVIEW, READ or WRITE)
agent-spec/runtime/edge-node-execution.md:234:            Node->>Node: map selector to plan.md or review.md and validate inventory
agent-spec/runtime/edge-node-execution.md:235:            Node-->>Edge: bounded typed WorkspaceArtifactResponse
agent-spec/runtime/edge-node-execution.md:279:- `go test -race -count=1 ./apps/edge/internal/service ./apps/node/internal/transport -run 'Test.*(WorkspaceArtifact|SingleRequestArtifact)'`
agent-spec/runtime/edge-node-execution.md:280:- `go test -count=1 ./apps/node/internal/workspace ./apps/node/internal/node ./apps/node/internal/transport -run 'Test.*(InternalArtifact|WorkspaceArtifact)'`
agent-spec/runtime/edge-node-execution.md:323:- 2026-08-07: Added the closed request-owned PLAN/REVIEW artifact wire and controller lifecycle. Artifact calls share the model-tool lazy open and terminal cleanup gate, use the frozen Node generation and immutable bounds, and map only inside Node to inventoried `plan.md`/`review.md` files. Provider-specific stage drivers and actual Claude qualification remain deferred.
```

### 8. Diff hygiene

`git diff --check`

Expected: exit zero with no output.

```text
```

External note: actual Claude/Mac full-cycle evidence is intentionally owned by SDD S12 and Milestone task `claude-smoke`, not this packet.

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

## Section Ownership

| Section | Owner | Note |
|---------|-------|------|
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
| Review-Only Checklist | Review agent only | Implementing agent must not modify or check this section |
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholder text with actual content |
| Reviewer Checkpoints | Fixed at stub creation | Pre-filled from plan |
| Verification Results (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `Deviations from Plan` entry |
| Code Review Result | Review agent appends | Not included in stub |

## Code Review Result

- Overall Verdict: PASS
- Dimension Assessment:
  - Correctness: Pass
  - Completeness: Pass
  - Test Coverage: Pass
  - API Contract: Pass
  - Code Quality: Pass
  - Implementation Deviation: Pass
  - Verification Trust: Pass
  - Spec Conformance: Pass
- Findings: None
- Routing Signals:
  - `review_rework_count=0`
  - `evidence_integrity_failure=false`
- Next Step: PASS — archive the reviewed pair, write `complete.log`, and emit Milestone contribution metadata for runtime aggregation.
