<!-- task=m-iop-agent-cli-runtime/18+14,15_cli_command_tree plan=2 tag=REVIEW_API -->

# Enforce Pre-Dispatch CLI Validation and Exact Output Contracts

## For the Implementing Agent

Filling implementation-owned sections in `CODE_REVIEW-*-G??.md` is mandatory. Run the verification commands exactly, paste literal stdout/stderr, keep active files in place, and report ready for review. Finalization is code-review-only. If blocked, record the exact blocker, attempted commands/output, and resume condition in implementation-owned evidence. Do not ask the user, call user-input tools, create stop files, classify the next state, archive logs, or write `complete.log`.

## Background

The service boundary now carries usable configuration paths and the submitted commands are reproducible, but output validation still occurs after service dispatch. The regression matrix also leaves request fields and stable output projections partially unasserted, allowing state mutation on a rejected command and silent response-field loss. This follow-up closes those command-boundary invariants without taking ownership of the binary or concrete runtime adapter.

## Archive Evidence Snapshot

- Prior task: `agent-task/m-iop-agent-cli-runtime/18+14,15_cli_command_tree`
- Predicted plan archive: `agent-task/m-iop-agent-cli-runtime/18+14,15_cli_command_tree/plan_cloud_G05_1.log`
- Predicted review archive: `agent-task/m-iop-agent-cli-runtime/18+14,15_cli_command_tree/code_review_cloud_G05_1.log`
- Verdict: FAIL
- Findings: 3 Required, 0 Suggested, 0 Nit.
- Affected files: `apps/agent/internal/command/root.go`, `apps/agent/internal/command/service.go`, `apps/agent/internal/command/root_test.go`, and the active review evidence.
- Reviewer verification: fresh focused, package, race, vet, formatting, stale-symbol, and diff checks passed with stable source hashes; the failures are uncovered command-ordering and assertion-coverage defects rather than reconstructed command output.
- Roadmap carryover: this remains an S10 command-tree prerequisite and must not add `Roadmap Targets`; the downstream binary/contract closure owns `cli-surface` completion and the headless transcript.

## Analysis

### Files Read

- `apps/agent/internal/command/root.go`
- `apps/agent/internal/command/service.go`
- `apps/agent/internal/command/root_test.go`
- `apps/agent/internal/command/config_test.go`
- `agent-contract/inner/iop-agent-cli-runtime.md`
- `agent-contract/inner/agent-runtime.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md`
- `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`
- `agent-task/m-iop-agent-cli-runtime/18+14,15_cli_command_tree/PLAN-cloud-G05.md`
- `agent-task/m-iop-agent-cli-runtime/18+14,15_cli_command_tree/CODE_REVIEW-cloud-G05.md`
- `agent-task/m-iop-agent-cli-runtime/18+14,15_cli_command_tree/plan_local_G06_0.log`
- `agent-task/m-iop-agent-cli-runtime/18+14,15_cli_command_tree/code_review_cloud_G06_0.log`
- `agent-task/m-iop-agent-cli-runtime/19+14,16,18_cli_binary_contract/PLAN-local-G06.md`

### SDD Criteria

- SDD: `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`, status `[승인됨]`, lock released.
- Targeted scenario: S10 / `cli-surface`.
- Evidence Map row: binary plus split-config command integration and a headless operation transcript.
- This follow-up repairs only the command-level prerequisite: invalid output must be rejected before dispatch, every configuration path must remain observable at the service request boundary, and text/JSON output must preserve the response contract. It does not claim S10 completion; the downstream binary/contract packet still owns binary and transcript evidence.

### Verification Context

- No separate verification-context handoff was supplied.
- Repository-native sources: agent domain rules, testing domain rules, local test rules, the command tests, the S10 SDD row, and the two inner contracts listed above.
- Local preflight: Go resolves to `/config/opt/go/bin/go`; reviewer host reports `go1.26.2 linux/arm64`.
- Fresh reviewer commands passed: focused package tests, full package tests, race tests, vet, `gofmt -d`, stale-symbol search, and `git diff --check`.
- Constraint: the worktree contains intentional sibling milestone work and `apps/agent` is untracked, so `git diff` is not a sufficient isolation oracle. The four reviewed file hashes remained unchanged across verification; the follow-up must keep its write-set exact and repeat the hash-stable verification.
- No binary, remote host, credential, or external runner is required. Binary/headless transcript verification remains downstream.
- Cached test output is not acceptable; use `-count=1`.
- Confidence: high for the command ordering defect, response projection gaps, and deterministic in-memory regression strategy.

### Test Coverage Gaps

- Invalid `--output` is tested only after `Validate` has already been called; no test proves zero service dispatch or zero mutation for rejected output.
- The matrix omits at least one `RuntimeConfigPaths` field assertion for milestone list/select, preview, start, stop, resume, and status.
- Text assertions are partial for validate, provider, project, milestone, preview, and status; command-level output does not expose the extra blank line created by `Fprintln`.
- `ProjectEntry.AutoResumeInterrupt` and status-blocker retryability are not preserved by both text and JSON projections.

### Symbol References

- No symbol removal is required.
- `RuntimeConfigPaths` and all request DTO call sites are confined to `apps/agent/internal/command/root.go`, `service.go`, and `root_test.go`.
- `NewRoot` is currently consumed only by `root_test.go`; the downstream binary task is the future production consumer.

### Split Judgment

Keep one atomic follow-up. Pre-dispatch validation, exact request capture, and stable response projection are one CLI boundary: independent partial fixes would still allow a rejected command to mutate state or a passing matrix to hide contract drift.

### Scope Rationale

Do not add `apps/agent/cmd/agent`, a concrete `Service`, bootstrap wiring, Makefile changes, local control, subprocesses, project logs, contract edits, or Roadmap completion evidence. Those remain owned by existing downstream packets.

### Final Routing

- evaluation_mode: `isolated-reassessment`
- finalizer: `finalize-task-policy.sh pair`
- build closures: scope/context/verification/evidence/ownership/decision all `true`
- build grade scores: scope `1`, state `1`, blast `1`, evidence `1`, verification `1`
- build base route: `local-fit`; final route: `recovery-boundary`, `cloud-G05`, filename `PLAN-cloud-G05.md`
- review closures: scope/context/verification/evidence/ownership/decision all `true`
- review grade scores: scope `1`, state `1`, blast `1`, evidence `1`, verification `1`
- review route: `official-review`, `cloud-G05`, filename `CODE_REVIEW-cloud-G05.md`
- large_indivisible_context: `false`
- positive loop risks: `boundary_contract`, `variant_product` (count `2`)
- recovery signals: `review_rework_count=2`, `evidence_integrity_failure=false`
- capability-gap evidence: none

## Implementation Checklist

- [ ] Reject unsupported output formats before every service dispatch and prove zero calls and zero mutations.
- [ ] Make request and response regressions exact, including every config path, stable text/JSON, auto-resume state, and blocker retryability.
- [ ] Run fresh focused, package, race, vet, formatting, stale-symbol, diff, and hash-stability verification and record literal stdout/stderr.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_API-1] Reject Unsupported Output Before Dispatch

#### Problem

`apps/agent/internal/command/root.go:194` and the other output-bearing commands call `Service` before `printOutput` validates the format at line 336. Invalid output can therefore mutate durable state and then report a CLI error.

#### Solution

Validate the persistent output flag in a root-level pre-run hook before any child `RunE`, while retaining a defensive check inside `printOutput`:

```go
// Before: apps/agent/internal/command/root.go:336
func printOutput(cmd *cobra.Command, resp interface{}, format string) error {
    if format != "text" && format != "json" {
        return fmt.Errorf("unsupported output format: %s", format)
    }
```

```go
// After
func validateOutputFormat(format string) error {
    if format != "text" && format != "json" {
        return fmt.Errorf("unsupported output format: %s", format)
    }
    return nil
}
```

Bind the helper through `PersistentPreRunE` so read and mutation commands share the same zero-dispatch rejection boundary. Keep `serve` deterministic under the same persistent flag contract.

#### Modified Files and Checklist

- [ ] `apps/agent/internal/command/root.go` — validate output before child dispatch and avoid adding a second newline to text responses.
- [ ] `apps/agent/internal/command/root_test.go` — assert invalid output produces zero service calls and zero mutations across output-bearing mutation commands.

#### Test Strategy

Strengthen `TestUnsupportedOutputFormat` as a table over read and mutation commands. Assert the exact error, empty call list, zero mutation count, and empty stdout.

#### Verification

```bash
go test -count=1 ./apps/agent/internal/command -run 'TestCommandMatrix|TestUnsupportedOutputFormat'
```

Expected: invalid formats fail before dispatch and the complete command matrix passes with fresh execution.

### [REVIEW_API-2] Make Request and Output Contracts Exact

#### Problem

`apps/agent/internal/command/root_test.go:201` compares only selected request fields, while line 407 uses substring text checks. `apps/agent/internal/command/service.go:293` and line 422 drop `AutoResumeInterrupt`, status output drops blocker retryability, and `root.go:347` adds a second newline to already terminated text.

#### Solution

Use exact expected request structs in `TestCommandMatrix`. Define complete deterministic text/JSON projections for every response, render text with one newline, preserve `auto_resume_interrupted` and blocker `retryable`, and validate decoded JSON values in addition to exact serialized output.

#### Modified Files and Checklist

- [ ] `apps/agent/internal/command/service.go` — preserve all operator-visible response fields in deterministic text and JSON.
- [ ] `apps/agent/internal/command/root.go` — emit formatter-owned text without an extra newline.
- [ ] `apps/agent/internal/command/root_test.go` — compare exact request DTOs, exact command text, exact JSON, and decoded JSON fields for the full response set.
- [ ] `agent-task/m-iop-agent-cli-runtime/18+14,15_cli_command_tree/CODE_REVIEW-cloud-G05.md` — paste literal stdout/stderr from the exact commands.

#### Test Strategy

Strengthen `TestCommandMatrix`, `TestStableTextAndJSONOutput`, and `TestStatusIncludesOverlayIntegrationAndBlockers`. Use exact struct equality for requests, exact strings for stable formatting, and `encoding/json` decoding for every advertised JSON response.

#### Verification

```bash
go test -count=1 ./apps/agent/internal/command -run 'TestCommandMatrix|TestStableTextAndJSONOutput|TestStatusIncludesOverlayIntegrationAndBlockers'
```

Expected: every request field and every stable response projection is covered by deterministic assertions.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/agent/internal/command/root.go` | REVIEW_API-1, REVIEW_API-2 |
| `apps/agent/internal/command/service.go` | REVIEW_API-2 |
| `apps/agent/internal/command/root_test.go` | REVIEW_API-1, REVIEW_API-2 |
| `agent-task/m-iop-agent-cli-runtime/18+14,15_cli_command_tree/CODE_REVIEW-cloud-G05.md` | REVIEW_API-2 |

## Final Verification

```bash
sha256sum apps/agent/internal/command/root.go apps/agent/internal/command/service.go apps/agent/internal/command/root_test.go
gofmt -d apps/agent/internal/command/root.go apps/agent/internal/command/service.go apps/agent/internal/command/root_test.go
go test -count=1 ./apps/agent/internal/command
go test -count=1 -race ./apps/agent/internal/command
go vet ./apps/agent/internal/command
! rg --sort path -n 'loadSnapshot|SnapshotHandle' apps/agent/internal/command
git diff --check
sha256sum apps/agent/internal/command/root.go apps/agent/internal/command/service.go apps/agent/internal/command/root_test.go
```

Expected: the before/after hashes match within the verification run; formatting has no diff; fresh, race, and vet checks pass; removed placeholder symbols have no matches; and the worktree diff has no whitespace errors.

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