<!-- task=m-iop-agent-cli-runtime/19+14,16,18_cli_binary_contract plan=1 tag=REVIEW_API -->

# Fail-Closed CLI Binary Boundary and S10 Evidence Correction

## For the Implementing Agent

Filling implementation-owned sections in `CODE_REVIEW-*-G??.md` is mandatory. Run every verification command, record actual notes and raw output, leave the active files in place, and report ready for review. Finalization belongs only to the code-review skill. 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 control-plane stop files, classify the next state, archive logs, or write `complete.log`.

## Background

The first review proved that the new binary reports successful Milestone and lifecycle mutations without an authoritative runtime behind them. It also found that the claimed S10 transcript and no-unselected-start test do not exercise the recorded acceptance criteria. This follow-up makes the partial binary fail closed, corrects the contract's implementation status, and leaves `cli-surface` open for the downstream daemon/runtime wiring packet.

## Archive Evidence Snapshot

- Current pair will archive as `agent-task/m-iop-agent-cli-runtime/19+14,16,18_cli_binary_contract/plan_local_G06_0.log` and `agent-task/m-iop-agent-cli-runtime/19+14,16,18_cli_binary_contract/code_review_cloud_G06_0.log`.
- Verdict: FAIL with 2 Required, 0 Suggested, and 0 Nit findings.
- Required behavior correction: `MilestoneList`, `MilestoneSelect`, `Serve`, `Start`, `Stop`, `Resume`, and `Status` must not report state that was never read or persisted.
- Required evidence correction: the selected fixture made `TestRunStartWithoutSelection` vacuous, no serve/cancellation evidence existed, and the recorded transcript omitted most of S10.
- Fresh reviewer evidence: focused, race, vet, local build, Darwin arm64 build, `git diff --check`, and `go test -count=1 ./...` passed; direct binary probes showed selection did not persist, start left status idle, unknown-project mutation commands succeeded, and serve exited `0` immediately.
- Roadmap carryover: this corrective packet intentionally omits `Roadmap Targets`; PASS must not check `cli-surface` until authoritative lifecycle wiring and the complete S10 Evidence Map exist.

## Analysis

### Files Read

- `apps/agent/cmd/agent/main.go`
- `apps/agent/cmd/agent/main_test.go`
- `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`
- `Makefile`
- `agent-contract/index.md`
- `agent-contract/inner/iop-agent-cli-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/19+14,16,18_cli_binary_contract/PLAN-local-G06.md`
- `agent-task/m-iop-agent-cli-runtime/19+14,16,18_cli_binary_contract/CODE_REVIEW-cloud-G06.md`

### SDD Criteria

- SDD: `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`; status `[승인됨]`, lock released.
- Targeted scenario: S10, mapped to Milestone Task `cli-surface`.
- Evidence Map: S10 requires a binary and split-config command integration test plus a headless operation transcript.
- The current implementation cannot satisfy S10 because its lifecycle adapter is not wired. The checklist therefore restores fail-closed behavior and truthful evidence without claiming `cli-surface`; the complete selection/lifecycle/status transcript remains required in the downstream wiring closure.

### Verification Context

- No separate verification-context handoff was supplied.
- Repository-native sources: `agent-test/local/rules.md`, `agent-test/local/testing-smoke.md`, the S10 contract change checklist, the command tests, and the Makefile.
- Local preflight: `/config/.local/bin/go` resolves to `/config/opt/go/bin/go`; runtime is `go1.26.2 linux/arm64`, while the module baseline is Go 1.24. The current worktree is intentionally dirty with sibling Milestone work, so final evidence must use the dispatcher-owned file claim and run after this packet's target files are stable.
- Fresh review commands already proved package/race/vet/build/cross-build/repository regression success. Direct binary probes are the deterministic oracle for fail-closed behavior.
- External verification: none. No credentials, provider process, remote host, or user-controlled runner is required.
- Confidence: high. The contradiction is reproduced through the built entrypoint and the correction can be verified locally.

### Test Coverage Gaps

- `TestRunStartWithoutSelection` uses a selected fixture and accepts both success and broad failure; add an exact unselected fixture and rejection assertion.
- No binary test proves that unsupported lifecycle commands fail instead of reporting fictitious success; add a table-driven fail-closed matrix.
- No test proves the partial `serve` command returns an explicit unavailable error instead of a successful placeholder exit.
- Existing command-port tests remain valid because they verify the abstract `command.Service` boundary, not the incomplete production adapter.
- Existing validate/provider/project read tests cover the implemented offline paths and should remain.

### Symbol References

- No symbol is renamed or removed.
- `adapter` is constructed only by `run` in `apps/agent/cmd/agent/main.go`; its `command.Service` methods are invoked through `apps/agent/internal/command/root.go`.

### Split Judgment

Keep one plan. Production fail-closed behavior, binary regression tests, and the contract status must change atomically so the executable and its evidence cannot disagree again. Predecessors 14, 16, and 18 are already satisfied by their archived `complete.log` files recorded in the prior plan.

### Scope Rationale

- Do not implement daemon bootstrap, local control, client process management, project-log integration, overlay/change-set integration, or provider execution here; those remain in downstream sibling packets.
- Do not change `apps/agent/internal/command` DTOs or formatting unless a compile-only adjustment is unavoidable; the reviewed command-port contract is already complete.
- Do not mark `cli-surface` complete or add `Roadmap Targets` until the authoritative runtime adapter and full S10 transcript exist.

### Final Routing

- evaluation_mode: `isolated-reassessment`
- finalizer: `finalize-task-policy.sh pair`
- build: closure complete; grade G08; route `cloud`, basis `recovery-boundary`; filename `PLAN-cloud-G08.md`
- review: closure complete; grade G08; route `cloud`, basis `official-review`; filename `CODE_REVIEW-cloud-G08.md`
- large_indivisible_context: `false`
- positive loop risks: `boundary_contract`, `variant_product` (count `2`)
- recovery signals: `review_rework_count=1`, `evidence_integrity_failure=true`
- capability-gap evidence: none

## Implementation Checklist

- [ ] Make every production-adapter command without authoritative state fail closed while preserving implemented offline validate/provider/project reads and the exact unselected-start rejection.
- [ ] Replace permissive binary tests with exact selected/unselected, unknown-project, mutation, status, and serve fail-closed assertions; record a fresh partial-surface transcript.
- [ ] Correct the standalone contract so it describes S10 as partial and does not claim completion evidence before daemon/runtime wiring.
- [ ] Run fresh formatting, focused/race/vet, local/Darwin builds, built-binary probes, repository regression, and diff checks.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_API-1] Make the Partial Binary Fail Closed

#### Problem

`apps/agent/cmd/agent/main.go:147-313` maps policy rules to Milestones and reports successful select/serve/start/stop/resume/status outcomes without reading or persisting authoritative runtime state. The built binary therefore violates the `command.Service` method contracts and the S10 no-fictitious-state boundary.

#### Solution

Keep `Validate`, `ProviderList`, and `ProjectList` as offline configuration reads. Preserve the exact no-selected-Milestone check in `Start`, but return one stable unavailable error for every selected-project lifecycle mutation and live-state command until the downstream runtime service exists. Validate project identity before returning unavailable, stop exposing selection rules as Milestones, and remove the success placeholder written by `Serve`.

Before (`apps/agent/cmd/agent/main.go:235`):

```go
func (a *adapter) Serve(_ context.Context, req command.ServeRequest) error {
    // ...
    fmt.Fprintf(a.stderr, "serve: runtime bootstrap not yet wired ...")
    return nil
}
```

After:

```go
var errRuntimeUnavailable = errors.New("standalone runtime lifecycle is not wired")

func (a *adapter) Serve(_ context.Context, req command.ServeRequest) error {
    if err := validateRequiredPaths(req); err != nil {
        return err
    }
    return errRuntimeUnavailable
}
```

Apply the same fail-closed sentinel after command-specific configuration/project/selection validation for Milestone list/select, preview when live evaluation is required, start, stop, resume, and status. Do not return a success DTO for an operation that did not occur.

#### Modified Files and Checklist

- [ ] `apps/agent/cmd/agent/main.go` — replace fictitious production-adapter success with validated fail-closed errors.

#### Test Strategy

Write regression tests in `apps/agent/cmd/agent/main_test.go`. The tests must distinguish missing config, unknown project, unselected project, selected project with unavailable runtime, and implemented offline reads. No external provider process is allowed.

#### Verification

```bash
go test -count=1 ./apps/agent/cmd/agent -run 'TestRun(OfflineReads|StartWithoutSelection|LifecycleFailsClosed|ServeFailsClosed)$'
```

Expected: all exact exit-code/stdout/stderr assertions pass.

### [REVIEW_API-2] Replace Vacuous S10 Evidence

#### Problem

`apps/agent/cmd/agent/main_test.go:320-345` names a no-selection test but uses `selected_milestone: milestone-1` and accepts either success or failure. The review artifact then cites it as proof. No test asserts the production adapter's serve or mutation behavior, and the transcript does not cover the claimed surface.

#### Solution

Add a distinct unselected local-config fixture and exact binary-seam assertions. Add a table-driven matrix proving that unsupported Milestone/lifecycle/live-status operations exit `1`, write no success output, and emit the stable unavailable error after their boundary validation. Add a built-binary transcript command set for help, validate, provider list, project list, unselected start, and each fail-closed operation; record raw output in the review artifact.

Before (`apps/agent/cmd/agent/main_test.go:337`):

```go
// The local config has selected_milestone: milestone-1, so this should succeed.
if exit != 0 {
    // broad error acceptance
}
```

After:

```go
if exit != 1 {
    t.Fatalf("expected unselected start to exit 1, got %d", exit)
}
if got := stderr.String(); got != "start: project iop-s0 has no selected milestone\n" {
    t.Fatalf("unexpected stderr: %q", got)
}
```

#### Modified Files and Checklist

- [ ] `apps/agent/cmd/agent/main_test.go` — add exact partial-binary contract regressions and fixtures.
- [ ] `agent-task/m-iop-agent-cli-runtime/19+14,16,18_cli_binary_contract/CODE_REVIEW-cloud-G08.md` — record actual raw verification output.

#### Test Strategy

Create `TestRunStartWithoutSelection`, `TestRunLifecycleFailsClosed`, and `TestRunServeFailsClosed` with deterministic in-test YAML fixtures. Keep `TestRunHelpShowsFullSurface`, validate, provider list, project list, preview-not-registered, and output-format tests. Tests must not invoke provider binaries.

#### Verification

```bash
go test -count=1 ./apps/agent/cmd/agent
go test -count=1 -race ./apps/agent/cmd/agent
```

Expected: fresh package and race executions pass and no provider subprocess is launched.

### [REVIEW_API-3] Correct the Standalone Contract Status

#### Problem

`agent-contract/inner/iop-agent-cli-runtime.md:13` labels S10 as implemented and its evidence row describes full lifecycle proof, while the production adapter is partial and the recorded evidence omits that lifecycle. This makes the contract and Roadmap completion signal untrustworthy.

#### Solution

Change the metadata to identify the current binary/command/config paths as partial S10 implementation, explicitly name authoritative lifecycle wiring and the complete headless matrix as pending, and retain the S10 Evidence Map row as the completion requirement. Do not weaken the required final behavior and do not claim `cli-surface` completion in this packet.

#### Modified Files and Checklist

- [ ] `agent-contract/inner/iop-agent-cli-runtime.md` — correct S10 implementation/evidence status without weakening the final contract.

#### Test Strategy

No separate code test is needed for prose, but deterministic searches and built-binary results must prove the document no longer labels partial lifecycle behavior as complete.

#### Verification

```bash
rg --sort path -n 'partial S10|S10|lifecycle' agent-contract/inner/iop-agent-cli-runtime.md
git diff --check -- agent-contract/inner/iop-agent-cli-runtime.md
```

Expected: the metadata says partial, the final S10 requirement remains, and the contract diff is clean.

## Dependencies and Execution Order

Implement REVIEW_API-1 and REVIEW_API-2 together, then update the contract from the verified executable behavior. The archived predecessor evidence remains:

- `agent-task/archive/2026/07/m-iop-agent-cli-runtime/14+13_cli_config_fixtures/complete.log`
- `agent-task/archive/2026/07/m-iop-agent-cli-runtime/16+13,15_bootstrap_composition/complete.log`
- `agent-task/archive/2026/07/m-iop-agent-cli-runtime/18+14,15_cli_command_tree/complete.log`

## Modified Files Summary

| File | Item |
|------|------|
| `apps/agent/cmd/agent/main.go` | REVIEW_API-1 |
| `apps/agent/cmd/agent/main_test.go` | REVIEW_API-2 |
| `agent-contract/inner/iop-agent-cli-runtime.md` | REVIEW_API-3 |
| `agent-task/m-iop-agent-cli-runtime/19+14,16,18_cli_binary_contract/CODE_REVIEW-cloud-G08.md` | REVIEW_API-2 |

## Final Verification

```bash
gofmt -w apps/agent/cmd/agent/*.go
go test -count=1 ./apps/agent/cmd/agent ./apps/agent/internal/command
go test -count=1 -race ./apps/agent/cmd/agent ./apps/agent/internal/command
go vet ./apps/agent/cmd/agent ./apps/agent/internal/command
make build-agent
GOOS=darwin GOARCH=arm64 go build -trimpath -o /tmp/iop-agent-darwin-arm64 ./apps/agent/cmd/agent
build/bin/iop-agent --help
build/bin/iop-agent validate --repo-config configs/iop-agent.runtime.yaml --local-config configs/iop-agent.local.example.yaml --provider-catalog configs/iop-agent.providers.yaml
build/bin/iop-agent provider list --provider-catalog configs/iop-agent.providers.yaml
build/bin/iop-agent project list --repo-config configs/iop-agent.runtime.yaml --local-config configs/iop-agent.local.example.yaml
go test -count=1 ./...
rg --sort path -n 'placeholder|not yet wired|returns a placeholder' apps/agent/cmd/agent
git diff --check
```

Expected: fresh formatting, focused/race/vet, local/Darwin builds, implemented offline binary commands, full repository tests, and diff checks pass; the placeholder search returns no matches. The review artifact must also contain the exact raw output for unselected start and every fail-closed production-adapter command. Cached test output is not acceptable because every Go test command uses `-count=1`.

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