<!-- task=m-iop-owned-single-request-agent-execution/18+17_plan_stage plan=2 tag=REVIEW_API milestone-task=plan-stage -->

# Complete the authorized Plan stage and its fail-closed provider codec

## For the Implementing Agent

Filling the implementation-owned sections of `CODE_REVIEW-cloud-G07.md` is the mandatory final 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 in the review evidence fields; do not ask the user, call user-input tools, create a control-plane stop file, change ownership, or expand the write boundary.

## Background

The prior implementation added an immutable managed dispatch snapshot and a private tunnel codec, but it did not implement SDD S08's Plan runner or `plan.md` write. The codec also drops the approved high-reasoning option, substitutes a timeout-derived response cap for the admitted byte limit, accepts invalid tunnel sequences, and the recorded focused command does not select the new tests. This follow-up completes the private Plan component without installing an incomplete composite executor.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-iop-owned-single-request-agent-execution/18+17_plan_stage/plan_local_G06_1.log`.
- Prior review: `agent-task/m-iop-owned-single-request-agent-execution/18+17_plan_stage/code_review_cloud_G06_1.log`.
- Verdict: `FAIL`; Required R1-R4, Suggested 0, Nit 0; `review_rework_count=1`, `evidence_integrity_failure=true`.
- R1: no Plan runner submits `planning`, validates plan/verification JSON, or writes `SingleRequestArtifactPlan`.
- R2: the codec reads a separate options map, removes `reasoning_effort`, uses `TimeoutSec * 1 MiB` as its byte cap, and does not apply the admitted stage deadline.
- R3: the frame collector accepts invalid order and provider `ERROR` terminals and wraps raw underlying errors.
- R4: the recorded focused regex selects only binding tests; no Plan-stage fixture exists, and the current spec still declares every provider-specific stage driver deferred.
- Fresh reviewer evidence: the predecessor dependency, vet, actual codec tests, full `go test ./apps/edge/... -count=1`, no-activation check, and `git diff --check` passed. `go test -list 'TestSingleRequest(Binding|PresetBinding|ProviderStage|PlanStage)'` listed only binding/preset tests, proving the claimed provider/Plan coverage was absent.
- Roadmap carryover: this packet continues to contribute only `milestone-task=plan-stage` and SDD S08. Work, Review/repair, composite activation, error/cancel integration, and S12 Claude/Mac qualification remain deferred.

## Finding Resolution Map

| Finding | Mode | Exact fix and changed precondition |
|---------|------|------------------------------------|
| Required R1 | `direct-fix` | Add `apps/edge/internal/openai/single_request_plan_stage.go` and `apps/edge/internal/openai/single_request_plan_stage_test.go`; the new runner must emit `planning`, require strict non-empty plan/verification output, render bounded Markdown, and write `SingleRequestArtifactPlan`. The precondition changes from no S08 execution component to a directly tested Plan artifact path. |
| Required R2 | `direct-fix` | Update `apps/edge/internal/openai/single_request_provider_stage.go` and its test to source options from the frozen stage binding, preserve the approved Plan `reasoning_effort=high`, and enforce the admitted stage deadline and exact `MaxOutputBytes`. The precondition changes from mutable/derived request policy to admitted policy only. |
| Required R3 | `direct-fix` | Update `apps/edge/internal/openai/single_request_provider_stage.go` and its test with an explicit response-frame state machine, generic error projection, exact selected-dispatch checks, and close assertions. The precondition changes from terminal counting to ordered fail-closed decoding. |
| Required R4 | `direct-fix` | Rename/add discoverable `TestSingleRequestProviderStage...` and `TestSingleRequestPlanStage...` fixtures and update `agent-spec/runtime/edge-node-execution.md` with the implemented-but-not-installed Plan component. The precondition changes from misleading green evidence/stale spec to deterministic coverage and current partial-state documentation. |

## Analysis

### Files Read

- `AGENTS.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/PHASE.md`
- `agent-roadmap/phase/knowledge-tool-optimization-extension/milestones/iop-owned-single-request-agent-execution.md`
- `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md`
- `agent-contract/outer/anthropic-compatible-api.md`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `agent-spec/runtime/edge-node-execution.md`
- `apps/edge/internal/service/single_request.go`
- `apps/edge/internal/service/single_request_artifact.go`
- `apps/edge/internal/service/single_request_types.go`
- `apps/edge/internal/service/provider_pool.go`
- `apps/edge/internal/service/provider_tunnel.go`
- `apps/edge/internal/openai/chat_types.go`
- `apps/edge/internal/openai/principal_routes.go`
- `apps/edge/internal/openai/route_resolution.go`
- `apps/edge/internal/openai/single_request_preset_binding.go`
- `apps/edge/internal/openai/single_request_preset_binding_test.go`
- `apps/edge/internal/openai/single_request_provider_stage.go`
- `apps/edge/internal/openai/single_request_provider_stage_test.go`
- `agent-task/m-iop-owned-single-request-agent-execution/18+17_plan_stage/plan_local_G06_1.log`
- `agent-task/m-iop-owned-single-request-agent-execution/18+17_plan_stage/code_review_cloud_G06_1.log`
- `agent-task/archive/2026/08/m-iop-owned-single-request-agent-execution/17_internal_artifact_wire/complete.log`

### SDD Criteria

- SDD: `agent-roadmap/sdd/knowledge-tool-optimization-extension/iop-owned-single-request-agent-execution/SDD.md`; status `[승인됨]`, lock released, no user review.
- First-line contribution: `plan-stage`.
- Target: S08 — immutable user task and empty request job produce a small plan plus verification criteria using Gemini 3.6 Flash high, followed by an internal `plan.md` write.
- Evidence Map: “Gemini plan request/options/artifact fixture” under `plan-stage`.
- Consequence: the implementation checklist requires effective provider-body inspection, frozen route/credential/limit checks, strict result parsing, the planning envelope, exact PLAN artifact content, and fail-closed provider/artifact failures. Passing a tunnel codec test without the artifact path cannot satisfy S08.

### Verification Context

- No external verification handoff was supplied. Repository-native sources are the local test rules, Edge smoke profile, approved SDD S08, current service/controller APIs, and adjacent Edge tests.
- Current checkout: branch `feature/iop-owned-single-request-agent-execution`, HEAD `22a8b81201e89d75c1e6c92342a8081472e8e436`, dirty with the predecessor artifact work and this task's uncommitted changes; unrelated dirty files must be preserved.
- Toolchain: `go version go1.26.2 linux/arm64`.
- Fresh reviewer commands established that the predecessor archive is unique, actual codec tests pass their current assertions, focused packages vet, all Edge packages pass, the incomplete executor is not installed, and diff hygiene is clean.
- Coverage gap: the plan-recorded focused regex selected no codec or Plan test. The existing codec tests assert the wrong high-reasoning behavior and do not cover the required Plan artifact path.
- External Verification Preflight: no remote provider, credential, Mac Node, or writable external workspace is required for S08 because deterministic provider frames and a fake `SingleRequestController` prove the private component. Actual Claude/Mac execution is intentionally deferred to S12 after composite activation.
- Confidence: high; the failures are directly visible in source and deterministic test discovery output.

### Test Coverage Gaps

- Frozen stage options: existing tests preserve options in admission but the codec test asserts `reasoning_effort` is absent instead of high.
- Output/deadline policy: current body-limit coverage derives bytes from `TimeoutSec`; there is no test for exact admitted `MaxOutputBytes` or stage context deadline.
- Frame state: duplicate terminal and missing terminal are covered, but body-before-start, duplicate start, body-after-terminal, provider `ERROR`, unknown frame, and exact selected-dispatch mismatch are not.
- Plan result: no strict small-plan/verification schema, trailing JSON, empty field, oversize rendering, planning envelope, or artifact-write-failure test exists.
- Verification selection: codec test names do not match the required focused regex, and no `PlanStage` test exists.

### Symbol References

- `singleRequestProviderStage`, `singleRequestProviderStageRequest`, and `buildSingleRequestChatBody` are referenced only by `single_request_provider_stage.go` and `single_request_provider_stage_test.go`; no production composite calls them yet.
- `singleRequestProviderStageTestable`, `providerStageSubmitResult`, and the logger field have no functional caller and may be removed while repairing the private component.
- `SingleRequestController.SubmitEnvelope` and `WriteInternalArtifact` are the existing, unchanged Plan-runner ports. No public symbol is renamed.

### Split Judgment

Keep one packet. The provider codec, strict Plan schema, planning envelope, and artifact write form one S08 correctness invariant, and the required fixture must observe them in one call. Splitting the codec repair from the runner would leave another intermediate state that still cannot produce independent `plan-stage` PASS evidence. The existing `18+17_plan_stage` dependency remains satisfied by the unique archived predecessor `complete.log`.

### Scope Rationale

Modify only the private provider/Plan stage files, their deterministic tests, the current implementation spec, and the active review artifact. Do not alter stage admission DTOs that passed API-1, the public Anthropic contract, provider-pool service semantics, Work/Review drivers, composite executor construction/installation, public streaming, generic error/cancel integration, roadmap checkboxes, or external S12 evidence.

### Final Routing

- `evaluation_mode=isolated-reassessment`; `finalizer=finalize-task-policy.sh`; `finalizer_mode=pair`.
- Build closures: scope/context/verification/evidence/ownership/decision all true. Scores `2/1/1/2/1` => G07, base `local-fit`; `evidence_integrity_failure=true` selects `recovery-boundary`, yielding `worker/cloud/G07` and `PLAN-cloud-G07.md`.
- Review closures: all true. Scores `2/1/1/2/1` => G07, `official-review`, `review/cloud/G07`, `CODE_REVIEW-cloud-G07.md`.
- `large_indivisible_context=false`; positive loop risks are `temporal_state`, `boundary_contract`, and `structured_interpretation` (`loop_risk_count=3`); `review_rework_count=1`; `evidence_integrity_failure=true`; no capability gap.

## Dependencies and Execution Order

1. The unique predecessor evidence remains `agent-task/archive/2026/08/m-iop-owned-single-request-agent-execution/17_internal_artifact_wire/complete.log`; do not search unrelated archives or replace its artifact API.
2. Repair the provider codec first so the Plan runner consumes one stable ordered/result boundary.
3. Add the Plan runner and integrated artifact fixture, then synchronize the spec from the executable evidence.
4. Do not construct or install the composite executor; dependent Work/Review and activation tasks retain that ownership.

## Implementation Checklist

- [ ] Repair the private provider-stage codec so frozen stage options, high reasoning, exact limits, selected-dispatch facts, ordered frames, provider errors, context cancellation, and generic failures are enforced with deterministic regressions.
- [ ] Implement the S08 Plan runner so it emits planning, sends the immutable task through the repaired codec, strictly requires a small plan plus verification criteria, and writes bounded Markdown through `SingleRequestArtifactPlan`.
- [ ] Rename/add focused tests selected by the canonical regex and synchronize the current spec to distinguish the implemented Plan component from deferred Work, Review/repair, composite activation, and S12 qualification.
- [ ] Run dependency, discovery, focused, vet, broader Edge, no-activation, spec, formatting, and diff-hygiene checks with fresh output.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_API-1] Make the provider codec enforce the admitted contract

**Problem**

`apps/edge/internal/openai/single_request_provider_stage.go:200` builds the provider body from `req.Options`, while the immutable authority is `req.StageBinding.Options`; line 299 discards `reasoning_effort`. Line 250 uses `TimeoutSec * 1 MiB` instead of the admitted output cap, and the loop at line 242 has no response-order state or provider-error failure.

**Solution**

Remove the duplicate options authority and pass the admitted `SingleRequestLimits` with the stage request. Build reserved `model`, `messages`, and `stream` fields server-side, merge only the frozen stage options, and preserve the validated high reasoning value. Bound submission by the earlier caller/stage deadline and body accumulation by `MaxOutputBytes`. Validate the returned model group, provider/profile, target, credential slot/revision, tunnel path, and Chat driver against the frozen dispatch facts. Consume exactly one `RESPONSE_START`, zero or more `BODY`, then one `END`; every other order, `ERROR`, unknown kind, or post-terminal frame fails with stable generic errors and closes the acquired handle.

Before (`apps/edge/internal/openai/single_request_provider_stage.go:200`):

```go
BuildBody: func(target string) ([]byte, error) {
    return buildSingleRequestChatBody(req.Prompt, req.Options, target)
},
```

After:

```go
BuildBody: func(target string) ([]byte, error) {
    return buildSingleRequestChatBody(req.Messages, req.StageBinding.Options, target)
},
```

**Modified Files and Checklist**

- [ ] Update `apps/edge/internal/openai/single_request_provider_stage.go` to use one immutable options/limits authority and remove unused private scaffolding.
- [ ] Update `apps/edge/internal/openai/single_request_provider_stage_test.go` with exact effective-body, selected-dispatch, deadline/output, frame-order, provider-error, sanitized-error, and close assertions.

**Test Strategy**

Rename fixtures to `TestSingleRequestProviderStage...`. Add table cases for response body before start, duplicate start, body after terminal, duplicate terminal, `ERROR`, unknown frame, non-2xx, missing/extra terminal, output at/over the exact byte cap, context deadline, normalized path, and every frozen dispatch mismatch. Assert the captured body contains high reasoning and no caller-selected model/messages/tools/credentials.

**Verification**

Run `go test ./apps/edge/internal/openai -run '^TestSingleRequestProviderStage' -count=1`; all provider-codec fixtures must pass freshly.

### [REVIEW_API-2] Implement the S08 Plan artifact runner

**Problem**

`apps/edge/internal/openai/single_request_provider_stage.go:161` exposes only a raw codec submission. No component submits `SingleRequestStatePlanning`, converts the immutable task to the fixed Gemini Plan request, validates the structured result, or invokes `SingleRequestController.WriteInternalArtifact`.

**Solution**

Add `single_request_plan_stage.go` with a private runner that accepts the request id, immutable task, frozen Plan binding/limits, request-scoped dispatch fields, sequence, and `SingleRequestController`. Submit the planning envelope first, send a fixed system/user message pair through the codec with the admitted Plan options, strictly decode exactly one JSON object with non-empty bounded `plan` and `verification` fields and no trailing value, render deterministic Markdown, and write it through `WriteInternalArtifact(ctx, SingleRequestArtifactPlan, content)`. Do not expose provider reasoning or install the runner as the outer executor.

Before (`apps/edge/internal/openai/single_request_provider_stage.go:161`):

```go
func (s *singleRequestProviderStage) submit(ctx context.Context, req singleRequestProviderStageRequest) (*singleRequestProviderStageResponse, error)
```

After (`apps/edge/internal/openai/single_request_plan_stage.go`):

```go
func (s *singleRequestPlanStage) run(ctx context.Context, req singleRequestPlanStageRequest, ctrl edgeservice.SingleRequestController) ([]byte, error)
```

**Modified Files and Checklist**

- [ ] Add `apps/edge/internal/openai/single_request_plan_stage.go` with fixed prompt, strict result schema, bounded Markdown rendering, planning envelope, and PLAN artifact write.
- [ ] Add `apps/edge/internal/openai/single_request_plan_stage_test.go` with a fake provider service and controller that capture the complete S08 sequence.

**Test Strategy**

Write `TestSingleRequestPlanStageWritesArtifact` to assert immutable task inclusion, frozen Gemini target/candidate/credential facts, `reasoning_effort=high`, no tools/caller authority, planning sequence, exact `SingleRequestArtifactPlan`, and deterministic Markdown. Add table cases for empty/malformed/unknown/trailing/multiple provider result shapes, missing plan/verification, oversized rendering, codec failure, context cancellation, envelope rejection, and artifact-write failure; every failure must be generic and must not expose reasoning/raw provider errors.

**Verification**

Run `go test ./apps/edge/internal/openai -run '^TestSingleRequestPlanStage' -count=1`; success and every fail-closed S08 case must pass freshly.

### [REVIEW_API-3] Restore trustworthy discovery evidence and current spec

**Problem**

`apps/edge/internal/openai/single_request_provider_stage_test.go:189` starts with `TestProviderStage...`, so the required `TestSingleRequest(...ProviderStage|PlanStage)` regex does not select the codec tests. `agent-spec/runtime/edge-node-execution.md:304` still states that no provider-specific Plan driver exists.

**Solution**

Use canonical discoverable prefixes for both test families and add an explicit test-list preflight that requires at least one provider and one Plan test. Update the spec's source evidence, feature list/scope, verification, limitations, and change history to describe the tested Plan component while clearly retaining deferred Work, Review/repair, composite activation, and actual Claude/Mac qualification.

Before (`apps/edge/internal/openai/single_request_provider_stage_test.go:189`):

```go
func TestProviderStageMissingBinding(t *testing.T) {
```

After:

```go
func TestSingleRequestProviderStageMissingBinding(t *testing.T) {
```

**Modified Files and Checklist**

- [ ] Finish canonical test naming and discovery assertions in `apps/edge/internal/openai/single_request_provider_stage_test.go` and `apps/edge/internal/openai/single_request_plan_stage_test.go`.
- [ ] Update `agent-spec/runtime/edge-node-execution.md` from the reviewed code and tests without claiming composite activation or S12 evidence.

**Test Strategy**

Do not add a document-only test. Use `go test -list` to prove both families are selected, executable fixtures for behavior, and deterministic `rg --sort path` output for the partial-state wording.

**Verification**

Run the discovery and spec commands in Final Verification; both test prefixes must be present and the spec must state Plan implemented/not installed with later stages deferred.

## Modified Files Summary

| File | Items |
|------|-------|
| `apps/edge/internal/openai/single_request_provider_stage.go` | REVIEW_API-1 |
| `apps/edge/internal/openai/single_request_provider_stage_test.go` | REVIEW_API-1, REVIEW_API-3 |
| `apps/edge/internal/openai/single_request_plan_stage.go` | REVIEW_API-2 |
| `apps/edge/internal/openai/single_request_plan_stage_test.go` | REVIEW_API-2, REVIEW_API-3 |
| `agent-spec/runtime/edge-node-execution.md` | REVIEW_API-3 |
| `agent-task/m-iop-owned-single-request-agent-execution/18+17_plan_stage/CODE_REVIEW-cloud-G07.md` | REVIEW_API-1, REVIEW_API-2, REVIEW_API-3 |

## Final Verification

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

1. `bash -c 'set -euo pipefail; shopt -s nullglob; candidates=(agent-task/m-iop-owned-single-request-agent-execution/17_*/complete.log agent-task/m-iop-owned-single-request-agent-execution/17+*/complete.log agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/17_*/complete.log agent-task/archive/*/*/m-iop-owned-single-request-agent-execution/17+*/complete.log); printf "%s\n" "${candidates[@]}"; ((${#candidates[@]} == 1))'` — prints exactly one predecessor completion path and exits zero.
2. `bash -c 'set -euo pipefail; listed=$(go test ./apps/edge/internal/openai -list "TestSingleRequest(ProviderStage|PlanStage)"); printf "%s\n" "$listed"; rg -q "^TestSingleRequestProviderStage" <<<"$listed"; rg -q "^TestSingleRequestPlanStage" <<<"$listed"'` — exits zero only when both focused families are discoverable.
3. `go test ./apps/edge/internal/service ./apps/edge/internal/openai -run 'TestSingleRequest(Binding|PresetBinding|ProviderStage|PlanStage)' -count=1` — admission, repaired codec, and S08 Plan/artifact fixtures all pass freshly.
4. `go vet ./apps/edge/internal/service ./apps/edge/internal/openai` — changed packages vet cleanly.
5. `go test ./apps/edge/... -count=1` — broader Edge regression passes freshly.
6. `test ! -e apps/edge/internal/openai/single_request_executor.go && bash -c 'set -euo pipefail; if rg --sort path -n "NewSingleRequestExecutor|SetSingleRequestExecutor" apps/edge/internal/input/manager.go; then exit 1; else test $? -eq 1; fi'` — exits zero with no incomplete production activation.
7. `rg --sort path -n 'Plan stage|plan\.md|not installed|deferred' agent-spec/runtime/edge-node-execution.md` — records the implemented Plan component and deferred Work/Review/activation/S12 boundaries.
8. `gofmt -d apps/edge/internal/openai/single_request_provider_stage.go apps/edge/internal/openai/single_request_provider_stage_test.go apps/edge/internal/openai/single_request_plan_stage.go apps/edge/internal/openai/single_request_plan_stage_test.go` — exits zero with no output.
9. `git diff --check` — exits zero with no whitespace errors.

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