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

# Request-owned internal artifact wire

## For the Implementing Agent

Filling the implementation-owned sections of `CODE_REVIEW-cloud-G09.md` is the mandatory last implementation step. Run every verification command, paste actual output, keep both active files in place, and report ready for review; only the official code-review skill may append a verdict, archive files, write `complete.log`, or classify the next state. If blocked, record the exact blocker, attempted commands/output, and resume condition only in the review evidence fields; do not ask the user, call a user-input tool, create a control-plane stop file, or change the owner or scope.

## Background

The coordinator can create request-owned Node workspace state only as a side effect of a model tool call, while SDD S08 requires the planner to persist `plan.md` before Work begins. The existing `WriteInternalArtifact` helper is Node-local and has no read path or typed Edge-Node contract. This packet adds a closed plan/review artifact family and integrates it with coordinator workspace-open and cleanup ownership without allowing model tools to name `.iop`.

## 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/client/rules.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/testing/rules.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-spec/runtime/edge-node-execution.md`
- `agent-contract/inner/edge-node-runtime-wire.md`
- `proto/iop/runtime.proto`
- `proto/gen/iop/runtime.pb.go`
- `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/node/internal/workspace/runtime.go`
- `apps/node/internal/workspace/cleanup.go`
- `apps/node/internal/workspace/cleanup_path_unix.go`
- `apps/node/internal/workspace/cleanup_path_other.go`
- `apps/node/internal/node/workspace_handler.go`
- `apps/node/internal/transport/parser.go`
- `apps/node/internal/transport/session.go`
- `apps/edge/internal/transport/server.go`
- `apps/edge/internal/service/workspace_wire.go`
- `apps/edge/internal/service/single_request.go`
- `apps/edge/internal/service/single_request_tool_loop.go`
- `packages/go/workspaceprotocol/terminal.go`
- `packages/go/workspaceprotocol/terminal_test.go`
- `apps/node/internal/workspace/cleanup_test.go`
- `apps/node/internal/node/workspace_handler_test.go`
- `apps/node/internal/transport/parser_test.go`
- `apps/node/internal/transport/session_test.go`
- `apps/edge/internal/transport/server_test.go`
- `apps/edge/internal/service/workspace_wire_test.go`
- `apps/edge/internal/service/single_request_test.go`
- `apps/edge/internal/service/single_request_cleanup_test.go`
- `apps/edge/internal/service/single_request_tool_loop_test.go`

### SDD Criteria

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md`, `[승인됨]`, `SDD 잠금: 해제`.
- First-line contribution ids: `plan-stage,work-stage,review-stage`.
- Targeted scenarios: S08 needs an empty request job and internal `plan.md` write; S09 needs Work to read the plan; S10 needs durable review evidence before finalization.
- Evidence Map drivers: the S08 plan artifact fixture, S09 plan-read/work fixture, and S10 review pass/repair artifact fixture. They require a reserved artifact kind, bounded read/write, identity echo validation, and cleanup evidence in this foundation packet; stage-specific provider assertions remain in dependent packets.

### Verification Context

- No verification handoff was supplied. Repository-native sources were `agent-test/local/rules.md`, `client-smoke.md`, `edge-smoke.md`, `node-smoke.md`, and `platform-common-smoke.md` plus the Makefile `proto`/`proto-dart` targets.
- Original plan preflight at HEAD `3bb4a24ad750a9ce5b7754a70db24545554ba238` found a clean worktree; Go `1.26.2 linux/arm64`, protoc `29.3`, `protoc-gen-go`, `protoc-gen-dart`, and Flutter `3.41.5` were available.
- The original baseline command passed at that preflight: `go test ./apps/edge/internal/service ./apps/edge/internal/openai ./apps/edge/internal/input ./apps/edge/internal/transport ./apps/node/internal/workspace ./apps/node/internal/node ./apps/node/internal/transport ./packages/go/workspaceprotocol -count=1`.
- Fresh Epic preparation review used branch `feature/iop-owned-single-request-agent-execution` at checkpoint `f4dad6ba88ae442e08ab67f49a5b1a65dd4719e9`; production source matches that checkpoint and only active task-artifact refinement changes are present. Implementation and fresh final verification remain unstarted.
- Constraints: local deterministic tests use ephemeral connections and temporary directories; no provider endpoint or credential is required. Actual Claude/Mac qualification is owned by SDD S12 `claude-smoke` and is not completion evidence for this packet.
- Confidence: high. Current source already owns secure artifact creation and exact cleanup inventory; the missing pieces are a bounded read primitive, a closed wire family, and coordinator lifecycle integration.

### Test Coverage Gaps

- Existing cleanup tests cover write inventory, exact-tree cleanup, identity replacement, sibling isolation, and cancellation, but do not cover internal reads.
- Existing Node handler and transport tests cover open/tool/cancel/cleanup families, but no reserved artifact request/response.
- Existing Edge wire tests cover stale-generation fencing, canonical response triples, timeout, and raw-error redaction, but no artifact kind/content bounds.
- Existing coordinator cleanup tests cover tool-triggered open only; they do not prove artifact-triggered open, one cleanup, or cancellation races. All four gaps receive deterministic tests in this packet.

### Symbol References

No symbol is renamed or removed. `SingleRequestController` at `apps/edge/internal/service/single_request.go:69` gains methods; its only production implementation is `singleRequestHandle`, and repository search found no test fake that directly implements the interface.

### Split Judgment

- `17_internal_artifact_wire`: stable contract is a closed `PLAN`/`REVIEW` artifact read/write wire plus coordinator-owned open/cleanup; PASS is proto regeneration and cross-Edge/Node lifecycle tests. No new sibling predecessor is required.
- `18+17_plan_stage`: consumes this contract to write the plan artifact.
- `19+18_work_stage`: consumes the completed plan runner and artifact read.
- `20+19_review_repair`: consumes Work to implement and persist Review/repair evidence.
- `21+20_single_request_executor`: consumes the completed stages to compose the private executor without production activation.
- `22+21_executor_activation`: installs the proven executor and synchronizes the active contract/spec.
- Indices 01-16 are occupied by archived siblings under the same task group; only directory basenames were inspected for collision-free allocation, not archive contents.

### Scope Rationale

This packet does not implement provider prompts, provider response decoding, Work tool policy, Review repair, production executor installation, the `error-cancel` task, or actual Claude qualification. It does not expose arbitrary internal paths: the wire carries an enum for `PLAN` or `REVIEW`, and Node alone maps that enum to `plan.md` or `review.md`. Public workspace tools continue to reject `.iop` paths.

### Final Routing

- `evaluation_mode=first-pass`; `finalizer=finalize-task-policy.sh`; `finalizer_mode=pair`.
- Build closures: scope/context/verification/evidence/ownership/decision all true. Scores `2/2/2/1/2` => G09, base/route `grade-boundary`, `worker/cloud/G09`, `PLAN-cloud-G09.md`.
- Review closures: all true. Scores `2/2/2/1/2` => G09, `official-review`, `review/cloud/G09`, `CODE_REVIEW-cloud-G09.md`.
- `large_indivisible_context=false`; positive loop risks: `temporal_state`, `concurrent_consistency`, `boundary_contract` (3); `review_rework_count=0`; `evidence_integrity_failure=false`; no capability gap.

## Implementation Checklist

- [ ] Add and regenerate the closed request-owned PLAN/REVIEW artifact protobuf family, including Go and Dart generated bindings.
- [ ] Implement bounded Node internal artifact read/write handling and typed transport dispatch without exposing `.iop` to model workspace tools.
- [ ] 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.
- [ ] Update the inner runtime contract and current implementation spec, then run focused, race, broader Edge/Node/shared, generation, client, vet, and diff checks.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [API-1] Define the closed artifact protocol and canonical terminals

**Problem**

`proto/iop/runtime.proto:383-471` defines open/tool/cancel/cleanup, but it has no coordinator-only artifact family. Reusing `WorkspaceToolRequest.relative_path` would make `.iop` naming part of the model tool surface and violate SDD D08.

**Solution**

Add `WorkspaceArtifactKind` (`PLAN`, `REVIEW`) and `WorkspaceArtifactOperation` (`READ`, `WRITE`) plus request/response messages. The request contains only `request_id`, kind, operation, and bounded write content; the response echoes identity/operation and carries the canonical status triple and bounded read content. Add `ArtifactTerminal` to the shared terminal authority, then regenerate bindings only through the Makefile.

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

```proto
message WorkspaceCleanupRequest {
  string request_id = 1;
}
```

After:

```proto
enum WorkspaceArtifactKind { /* UNSPECIFIED, PLAN, REVIEW */ }
enum WorkspaceArtifactOperation { /* UNSPECIFIED, READ, WRITE */ }
message WorkspaceArtifactRequest { /* request_id, kind, operation, content */ }
message WorkspaceArtifactResponse { /* echoed identity, terminal triple, content */ }
```

**Modified Files and Checklist**

- [ ] Update `proto/iop/runtime.proto` without renumbering existing fields or extending `WorkspaceOperation`.
- [ ] Regenerate `proto/gen/iop/runtime.pb.go` with `make proto`.
- [ ] Regenerate `apps/client/lib/gen/proto/iop/runtime.pb.dart`, `apps/client/lib/gen/proto/iop/runtime.pbenum.dart`, and `apps/client/lib/gen/proto/iop/runtime.pbjson.dart` with `make proto-dart`; never hand-edit them.
- [ ] Add canonical artifact terminal pairs in `packages/go/workspaceprotocol/terminal.go` and table coverage in `packages/go/workspaceprotocol/terminal_test.go`.

**Test Strategy**

Write normal and boundary tests: parser round trips must retain enum numbers and bytes; canonical success/not-ready/not-found/invalid/internal outcomes must be accepted and contradictory pairs rejected.

**Verification**

Run `make proto && make proto-dart && go test ./packages/go/workspaceprotocol -count=1`; generation and tests must exit zero.

### [API-2] Implement Node-owned artifact access

**Problem**

`apps/node/internal/workspace/cleanup.go:19` can create an inventoried artifact, but there is no identity-checked read primitive. `apps/node/internal/node/workspace_handler.go:15-166` and `apps/node/internal/transport/session.go:31-237` dispatch only the four existing workspace families.

**Solution**

Add `ReadInternalArtifact` with the same request lock, cleaning fence, descriptor-relative no-follow checks, inventory identity comparison, and fixed size bound as write. Node maps the two enum kinds to fixed filenames, rejects every malformed kind/operation/content combination before filesystem effects, emits canonical generic terminals, and registers the typed request concurrently with the other workspace messages.

Before (`apps/node/internal/workspace/cleanup.go:19`):

```go
func (r *Runtime) WriteInternalArtifact(requestID, relativePath string, content []byte) error
```

After:

```go
func (r *Runtime) ReadInternalArtifact(requestID, relativePath string) ([]byte, error)
func (r *Runtime) WriteInternalArtifact(requestID, relativePath string, content []byte) error
```

**Modified Files and Checklist**

- [ ] Add the runtime read and Unix descriptor helper in `apps/node/internal/workspace/cleanup.go` and `apps/node/internal/workspace/cleanup_path_unix.go`, with the unsupported-platform stub in `apps/node/internal/workspace/cleanup_path_other.go`.
- [ ] Add `OnWorkspaceArtifact` mapping and generic canonical failures in `apps/node/internal/node/workspace_handler.go`.
- [ ] Register parser and concurrent request listener support in `apps/node/internal/transport/parser.go` and `apps/node/internal/transport/session.go`.
- [ ] Extend `apps/node/internal/workspace/cleanup_test.go`, `apps/node/internal/node/workspace_handler_test.go`, `apps/node/internal/transport/parser_test.go`, and `apps/node/internal/transport/session_test.go` with read/write, malformed, not-found, identity replacement, unsupported-handler, and disconnect/cleanup cases.

**Test Strategy**

Write `TestWorkspaceInternalArtifactReadWriteIsolation`, `TestNodeWorkspaceArtifactMapping`, `TestNodeParserMapWorkspaceArtifact`, and `TestSessionWorkspaceArtifactRequest`. Fixtures use `t.TempDir`/`net.Pipe`, assert plan/review only, prove sibling request isolation and no raw path/error leakage, and keep the public `.iop` denial assertion.

**Verification**

Run `go test ./apps/node/internal/workspace ./apps/node/internal/node ./apps/node/internal/transport -run 'Test.*(InternalArtifact|WorkspaceArtifact)' -count=1`; all focused assertions must pass freshly.

### [API-3] Make artifacts part of coordinator lifecycle ownership

**Problem**

`apps/edge/internal/service/workspace_wire.go:28-210` cannot send artifacts, and `SingleRequestController` at `apps/edge/internal/service/single_request.go:69` cannot request them. Cleanup at `single_request.go:522` only knows whether a model tool opened the workspace, so an artifact-only request could leak its `.iop/job/<request_id>` tree or race terminal cleanup.

**Solution**

Add an artifact wire method that freezes the admitted Node generation, validates every response echo and canonical terminal, and enforces `binding.Limits.MaxOutputBytes` on writes and reads. Extend the controller with typed plan/review read/write methods implemented in a cohesive `single_request_artifact.go`: it shares the existing lazy open, work accounting, wait group, request/stage deadlines, and `toolLoop.opened` cleanup gate so one request opens once and every terminal path waits for the artifact operation before one cleanup.

Before (`apps/edge/internal/service/single_request.go:69`):

```go
type SingleRequestController interface {
    RequestID() string
    Binding() *SingleRequestBinding
    Context() context.Context
    State() SingleRequestState
    SubmitEnvelope(env SingleRequestEnvelope) error
}
```

After:

```go
type SingleRequestController interface {
    // existing methods
    ReadInternalArtifact(context.Context, SingleRequestArtifactKind) ([]byte, error)
    WriteInternalArtifact(context.Context, SingleRequestArtifactKind, []byte) error
}
```

**Modified Files and Checklist**

- [ ] Add Edge response parsing in `apps/edge/internal/transport/server.go` and round-trip coverage in `apps/edge/internal/transport/server_test.go`.
- [ ] Add the fenced sender/validator in `apps/edge/internal/service/workspace_wire.go` and fixtures in `apps/edge/internal/service/workspace_wire_test.go`.
- [ ] Add the typed controller contract in `apps/edge/internal/service/single_request.go` and coordinator implementation in new `apps/edge/internal/service/single_request_artifact.go`.
- [ ] Add lifecycle/race tests in new `apps/edge/internal/service/single_request_artifact_test.go`, including artifact-first open, tool-after-artifact no second open, terminal/cancel wait, exactly-one cleanup, size rejection before send, and stale/malformed response failure.

**Test Strategy**

Write `TestWorkspaceArtifactWire` and `TestSingleRequestArtifactLifecycle` families over `net.Pipe`. Use blocked artifact responders to prove cleanup waits and cancellation terminates without duplicate open/cleanup; run the coordinator package under `-race`.

**Verification**

Run `go test -race ./apps/edge/internal/service -run 'Test(WorkspaceArtifactWire|SingleRequestArtifact)' -count=1`; it must pass with no race report.

### [API-4] Synchronize the implemented contract and spec

**Problem**

`agent-contract/inner/edge-node-runtime-wire.md:71-99` documents internal artifact inventory and cleanup but no artifact read/write wire. `agent-spec/runtime/edge-node-execution.md:175,286` likewise cannot describe how Plan, Work, and Review share request artifacts.

**Solution**

Document the closed enum surface, identity/generation fences, bounds, no-follow inventory reads, lazy-open sharing, cleanup ordering, and continued public `.iop` denial. Keep provider stage drivers and actual Claude qualification explicitly deferred to the dependent stage packets.

**Modified Files and Checklist**

- [ ] Update `agent-contract/inner/edge-node-runtime-wire.md` with request/response fields and failure/privacy semantics.
- [ ] Update `agent-spec/runtime/edge-node-execution.md` with the implemented artifact lifecycle and remaining stage-driver limitation.

**Test Strategy**

No document-only test is added; the contract claims are backed by API-1 through API-3 tests and deterministic searches in final verification.

**Verification**

Run `rg --sort path -n 'WorkspaceArtifact|plan\.md|review\.md|public workspace' agent-contract/inner/edge-node-runtime-wire.md agent-spec/runtime/edge-node-execution.md`; output must show the closed artifact family and public-path exclusion.

## Modified Files Summary

| File | Items |
|------|-------|
| `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 |
| `packages/go/workspaceprotocol/terminal.go` | API-1 |
| `packages/go/workspaceprotocol/terminal_test.go` | API-1 |
| `apps/node/internal/workspace/cleanup.go` | API-2 |
| `apps/node/internal/workspace/cleanup_path_unix.go` | API-2 |
| `apps/node/internal/workspace/cleanup_path_other.go` | API-2 |
| `apps/node/internal/workspace/cleanup_test.go` | API-2 |
| `apps/node/internal/node/workspace_handler.go` | API-2 |
| `apps/node/internal/node/workspace_handler_test.go` | API-2 |
| `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-3 |
| `apps/edge/internal/transport/server_test.go` | API-3 |
| `apps/edge/internal/service/workspace_wire.go` | API-3 |
| `apps/edge/internal/service/workspace_wire_test.go` | API-3 |
| `apps/edge/internal/service/single_request.go` | API-3 |
| `apps/edge/internal/service/single_request_artifact.go` | API-3 |
| `apps/edge/internal/service/single_request_artifact_test.go` | API-3 |
| `agent-contract/inner/edge-node-runtime-wire.md` | API-4 |
| `agent-spec/runtime/edge-node-execution.md` | API-4 |
| `agent-task/m-iop-owned-single-request-agent-execution/17_internal_artifact_wire/CODE_REVIEW-cloud-G09.md` | API-1, API-2, API-3, API-4 |

## Final Verification

Fresh output is required; Go tests use `-count=1` and cached results are not acceptable.

1. `make proto && make proto-dart` — both generators exit zero and tracked Go/Dart bindings reflect the source schema.
2. `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` — focused cross-boundary packages pass.
3. `go test -race ./apps/edge/internal/service ./apps/node/internal/transport -run 'Test.*(WorkspaceArtifact|SingleRequestArtifact)' -count=1` — artifact lifecycle/correlation tests pass without races.
4. `go vet ./packages/go/... && go vet ./apps/node/... && go vet ./apps/edge/internal/service` — relevant shared, Node, and Edge packages vet cleanly.
5. `go test ./packages/go/... ./apps/node/... ./apps/edge/... -count=1` — broader shared/consumer regression passes.
6. `make client-test` — generated Dart bindings compile in the client test suite and all tests pass.
7. `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` — results are confined to the private artifact/runtime boundary and its tests/docs.
8. `git diff --check` — no whitespace errors.

Actual Claude/Mac full-cycle evidence is intentionally not run here; SDD S12 and Milestone task `claude-smoke` own that separate external verification.

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