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

# Close strict JSON and evidence-integrity gaps

## For the Implementing Agent

Filling the implementation-owned sections of `CODE_REVIEW-cloud-G04.md` is the mandatory final implementation step. Execute this plan's exact write boundary and verification commands, 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 implementation-owned evidence fields; do not ask the user, call user-input tools, create control-plane stop files, or change ownership.

## Background

The assistant-role/stop predicate is now present and the recorded suites are green, but the private decoder still promotes non-string Chat content into Plan JSON and the Plan decoder accepts duplicate known keys. The follow-up also left several explicitly checked evidence rows absent, so the current strict-result and complete-matrix claims are not trustworthy yet. This packet closes those parser and evidence gaps without installing the composite executor.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-iop-owned-single-request-agent-execution/18+17_plan_stage/plan_cloud_G05_3.log`.
- Prior review: `agent-task/m-iop-owned-single-request-agent-execution/18+17_plan_stage/code_review_cloud_G05_3.log`.
- Verdict: `FAIL`; Required R1-R2, Suggested 0, Nit 0; `review_rework_count=3`, `evidence_integrity_failure=true`.
- R1: the private provider decoder accepts object-valued Chat content as JSON text, and the Plan decoder accepts duplicate known keys with last-value-wins semantics.
- R2: the checked matrix still omits invalid nested shapes, `USAGE`, complete frozen request/credential assertions, duplicate Plan keys, and rejected artifact no-write evidence.
- Fresh reviewer evidence: all eleven recorded commands passed, while a temporary focused reproducer failed because object-valued content and duplicate `plan` keys were accepted. The temporary test file was removed and `git diff --check` remained clean.
- Roadmap carryover: this packet contributes only `milestone-task=plan-stage` and SDD S08. Work, Review/repair, composite activation, generic error/cancel integration, and S12 Claude/Mac qualification remain deferred.

## Finding Resolution Map

| Finding | Mode | Exact fix and changed precondition |
|---------|------|------------------------------------|
| Required R1 | `direct-fix` | Update `apps/edge/internal/openai/single_request_provider_stage.go`, `apps/edge/internal/openai/single_request_plan_stage.go`, and their tests with one private duplicate-key-aware JSON validator plus private typed Chat response structs whose content is required to be a JSON string. The precondition changes from two accepted malformed shapes to deterministic rejection before any artifact write. |
| Required R2 | `direct-fix` | Complete `apps/edge/internal/openai/single_request_provider_stage_test.go` and `apps/edge/internal/openai/single_request_plan_stage_test.go` with the named missing frame, frozen-authority, credential, nested-shape, duplicate-key, and artifact no-write assertions. The precondition changes from green family-level evidence to executable coverage of every checked row. |

## Analysis

### Files Read

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.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-ops/rules/common/rules-agent-spec.md`
- `agent-spec/index.md`
- `agent-contract/index.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-node-runtime-wire.md`
- `agent-contract/inner/edge-config-runtime-refresh.md`
- `agent-spec/runtime/edge-node-execution.md`
- `apps/edge/internal/openai/chat_types.go`
- `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`
- `apps/edge/internal/openai/single_request_preset_binding.go`
- `apps/edge/internal/service/run_types.go`
- `apps/edge/internal/service/provider_pool.go`
- `apps/edge/internal/service/provider_tunnel.go`
- `apps/edge/internal/service/single_request.go`
- `apps/edge/internal/service/single_request_types.go`
- `agent-task/m-iop-owned-single-request-agent-execution/18+17_plan_stage/plan_cloud_G05_3.log`
- `agent-task/m-iop-owned-single-request-agent-execution/18+17_plan_stage/code_review_cloud_G05_3.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`.
- S08 requires the immutable task and empty request job to produce a small plan plus verification criteria through Gemini 3.6 Flash high and then complete an internal `plan.md` write.
- The S08 Evidence Map requires a Gemini plan request/options/artifact fixture with high-option and small-plan evidence.
- Therefore the implementation checklist and final verification require strict string content, duplicate-key rejection, exact frozen dispatch authority, closed frame order, deterministic rendering, and no persisted artifact on every rejected result.

### Verification Context

- No external verification handoff was supplied. Repository-native sources are the local rules, Edge smoke profile, approved SDD S08, current private stage code, adjacent service DTOs, and focused tests.
- Current checkout is `feature/iop-owned-single-request-agent-execution` at `22a8b81201e89d75c1e6c92342a8081472e8e436`, with accumulated milestone work that must be preserved.
- Fresh reviewer commands passed dependency discovery, canonical discovery, both stage families, focused integration, vet, all Edge packages, no-activation, spec search, formatting, and diff hygiene.
- Fresh focused reproducer: `go test ./apps/edge/internal/openai -run '^TestSingleRequestReviewProbeRejectsNonStringAndDuplicateShapes$' -count=1` failed because object-valued content became `{"plan":"A","verification":"B"}` and duplicate `plan` keys rendered the last value. The temporary test was removed immediately.
- External Verification Preflight: not applicable. The runner remains private and not installed; deterministic provider frames plus a fake controller are the approved S08 evidence. Actual Claude/Mac execution remains S12.
- Confidence: high; both correctness failures are deterministic standard-library decode behavior, and the absent matrix rows are directly visible in the current tests.

### Test Coverage Gaps

- Provider response shape: role/index/finish/tool cases exist, but object/array/null content, duplicate keys, and nested unknown fields are absent.
- Frame state: nil/unknown frames exist, but the contract's observation-only `USAGE` kind is not explicitly rejected by the private Plan codec fixture.
- Frozen authority: Run fields are mostly asserted, but Tunnel scalar zero/default fields and the complete credential binding are not compared; the candidate predicate fixture accepts every candidate.
- Plan result: empty/unknown/trailing cases exist, but duplicate `plan`/`verification` keys are absent.
- Artifact rejection: the fake records content before returning `writeErr`, and the test checks only the sentinel, so it does not prove rejected persistence leaves content empty.

### Symbol References

- No symbol is renamed or removed. The private provider/Plan types and helpers are referenced only by the same-package stage files and tests; the component remains uninstalled.

### Split Judgment

- Keep one packet. The duplicate-key validator, private typed response shape, and their evidence matrix form one strict JSON-to-PLAN write invariant; splitting parser behavior from its regression evidence would not yield an independently trustworthy PASS state.
- The encoded predecessor `17_internal_artifact_wire` remains satisfied by `agent-task/archive/2026/08/m-iop-owned-single-request-agent-execution/17_internal_artifact_wire/complete.log`.

### Scope Rationale

- Modify only the private provider/Plan decoders, their tests, and the active review artifact.
- Do not change shared Chat request/response behavior, admission/config contracts, provider-pool/service semantics, controller artifact APIs, Work/Review drivers, composite construction/installation, public Anthropic output, roadmap state, or 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 `1/0/0/2/1` => G04, base `local-fit`; `review_rework_count=3` and `evidence_integrity_failure=true` select `recovery-boundary`, yielding `worker/cloud/G04` and `PLAN-cloud-G04.md`.
- Review closures: all true. Scores `1/0/0/2/1` => G04, `official-review`, `review/cloud/G04`, `CODE_REVIEW-cloud-G04.md`.
- `large_indivisible_context=false`; positive loop risks are `boundary_contract` and `structured_interpretation` (`loop_risk_count=2`); `risk_boundary_matched=false`, `recovery_boundary_matched=true`; no capability gap.

## Dependencies and Execution Order

1. The `17_internal_artifact_wire` predecessor remains satisfied by `agent-task/archive/2026/08/m-iop-owned-single-request-agent-execution/17_internal_artifact_wire/complete.log`.
2. Add the private strict JSON validator and response structs first, then apply the same duplicate-key rule to Plan result decoding.
3. Complete the exact evidence matrix and rerun the S08 verification set without installing an outer executor.

## Implementation Checklist

- [ ] Reject non-string or structurally ambiguous Chat results and duplicate provider/Plan JSON keys before any PLAN artifact write, with deterministic regression cases.
- [ ] Complete frozen Run/Tunnel/credential/predicate, `USAGE` frame, nested response-shape, and rejected artifact no-write assertions.
- [ ] 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] Enforce strict private JSON shapes

**Problem**

`apps/edge/internal/openai/single_request_provider_stage.go:168` decodes into the shared `chatCompletionResponse`, whose `chatMessage.UnmarshalJSON` converts object-valued content into JSON text. `apps/edge/internal/openai/single_request_plan_stage.go:63` uses `encoding/json` without duplicate-key detection, so repeated `plan` or `verification` fields silently overwrite earlier values.

**Solution**

Add a private recursive token validator that rejects duplicate object keys at every nesting level and requires exactly one complete JSON value. Decode provider results into private response/choice/message structs with `Content *string`, strict unknown-field rejection, exactly one index-zero assistant/stop choice, and no tool calls. Run the duplicate-key validator before both provider typed decoding and Plan result decoding.

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

```go
var decoded chatCompletionResponse
decoder := json.NewDecoder(bytes.NewReader(body))
decoder.DisallowUnknownFields()
```

After:

```go
if err := validateSingleRequestJSON(body); err != nil {
    return nil, errProviderStageGeneric
}
var decoded singleRequestChatResponse
decoder := json.NewDecoder(bytes.NewReader(body))
decoder.DisallowUnknownFields()
```

**Modified Files and Checklist**

- [ ] Update `apps/edge/internal/openai/single_request_provider_stage.go` with the private duplicate-key validator and strict typed Chat response.
- [ ] Update `apps/edge/internal/openai/single_request_plan_stage.go` to validate duplicate-free JSON before typed Plan decoding.
- [ ] Add object/array/null content, nested unknown field, and duplicate-key cases to both stage test files.

**Test Strategy**

Extend `TestSingleRequestProviderStageRejectsResponseEnvelope` with exact `non-string-content-object`, `non-string-content-array`, `null-content`, `unknown-message-field`, `duplicate-response-key`, `duplicate-choice-key`, and `duplicate-message-key` cases. Extend `TestSingleRequestPlanStageFailsClosed` with `duplicate-plan-key` and `duplicate-verification-key`. Every case must assert the stable stage sentinel, acquired-tunnel close where applicable, and zero persisted artifact content.

**Verification**

Run `go test ./apps/edge/internal/openai -run 'TestSingleRequest(ProviderStageRejectsResponseEnvelope|PlanStageFailsClosed)' -count=1`; all named malformed-shape cases must reject freshly.

### [REVIEW_API-2] Make every checked evidence row executable

**Problem**

`apps/edge/internal/openai/single_request_provider_stage_test.go:143` checks only part of the Tunnel and credential request, line 269 omits a `USAGE` frame, and `apps/edge/internal/openai/single_request_plan_stage_test.go:183` does not prove a rejected artifact write leaves persisted content empty. The active review nevertheless marked the complete matrix and exact no-write assertions done.

**Solution**

Compare every scalar/default field of captured `Run` and `Tunnel` requests, the complete secret-free credential binding, and a selective candidate predicate with both accepted and rejected candidates. Add `usage-frame` to the fail-closed frame table. Separate artifact write attempts from persisted content in the fake controller so `writeErr` returns before persistence, then assert one attempt and zero content.

Before (`apps/edge/internal/openai/single_request_plan_stage_test.go:27`):

```go
c.kind = k
c.content = append([]byte(nil), b...)
return c.writeErr
```

After:

```go
c.writeAttempts++
if c.writeErr != nil {
    return c.writeErr
}
c.kind = k
c.content = append([]byte(nil), b...)
return nil
```

**Modified Files and Checklist**

- [ ] Expand `apps/edge/internal/openai/single_request_provider_stage_test.go` with complete Run/Tunnel/credential/default field comparisons, a selective predicate, and `usage-frame` rejection.
- [ ] Expand `apps/edge/internal/openai/single_request_plan_stage_test.go` with attempted-versus-persisted artifact state and exact no-write assertions for every failed Plan case.

**Test Strategy**

Keep the canonical test families. Use field-by-field comparisons without a new dependency, assert nil maps/bodies and false/zero authority fields where they must remain unset, and make the credential assertion cover principal, slot, route, profile, both revisions, and projection generation. The artifact fake must record attempt count independently and persist only on nil error.

**Verification**

Run `go test ./apps/edge/internal/openai -run '^TestSingleRequest(ProviderStage|PlanStage)' -count=1`; exact frozen authority, `usage-frame`, close, generic sentinel, and no-write cases must pass freshly.

## 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-2 |
| `apps/edge/internal/openai/single_request_plan_stage.go` | REVIEW_API-1 |
| `apps/edge/internal/openai/single_request_plan_stage_test.go` | REVIEW_API-1, REVIEW_API-2 |
| `agent-task/m-iop-owned-single-request-agent-execution/18+17_plan_stage/CODE_REVIEW-cloud-G04.md` | REVIEW_API-1, REVIEW_API-2 |

## 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"'` — both canonical test families remain discoverable.
3. `rg --sort path -n 'non-string-content-object|duplicate-message-key|usage-frame|duplicate-plan-key|duplicate-verification-key|artifact-write-failure-rejects' apps/edge/internal/openai/single_request_provider_stage_test.go apps/edge/internal/openai/single_request_plan_stage_test.go` — prints every mandatory named regression.
4. `go test ./apps/edge/internal/openai -run '^TestSingleRequestProviderStage' -count=1` — full provider authority/response/frame/deadline matrix passes freshly.
5. `go test ./apps/edge/internal/openai -run '^TestSingleRequestPlanStage' -count=1` — full strict Plan/render/envelope/artifact matrix passes freshly.
6. `go test ./apps/edge/internal/service ./apps/edge/internal/openai -run 'TestSingleRequest(Binding|PresetBinding|ProviderStage|PlanStage)' -count=1` — admission and S08 integration fixtures pass freshly.
7. `go vet ./apps/edge/internal/service ./apps/edge/internal/openai` — changed packages vet cleanly.
8. `go test ./apps/edge/... -count=1` — broader Edge regression passes freshly.
9. `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.
10. `rg --sort path -n 'Plan stage|plan\.md|not installed|deferred' agent-spec/runtime/edge-node-execution.md` — the spec still limits the component to implemented-but-not-installed Plan behavior and deferred later stages/S12.
11. `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.
12. `git diff --check` — exits zero with no whitespace errors.

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