<!-- task=m-iop-agent-cli-runtime/15+13_host_lifecycle plan=2 tag=REVIEW_REVIEW_API -->

# Prove Terminal Status Follows Concurrent Cleanup

## For the Implementing Agent

Filling implementation-owned sections in `CODE_REVIEW-cloud-G04.md` is mandatory. Run every verification command, paste actual notes and output, leave the active pair 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 fields; 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 prior follow-up removed the concurrent `Start`/`Stop` cancellation deadlock and added coverage for cancellation, started-prefix rollback, and repeated stop. Review found that the test reads terminal status only after both lifecycle goroutines finish, so it does not prove that `Status.Stopped` remains false while rollback cleanup is still running. Add one deterministic cleanup gate and make that temporal invariant observable without changing production lifecycle code.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-iop-agent-cli-runtime/15+13_host_lifecycle/plan_local_G04_1.log`
- Prior review: `agent-task/m-iop-agent-cli-runtime/15+13_host_lifecycle/code_review_cloud_G04_1.log`
- Verdict: FAIL
- Findings: Required 1, Suggested 0, Nit 0. `TestHostStopCancelsInProgressStart` waits for both lifecycle goroutines before reading `Status().Stopped`, so the planned no-premature-terminal invariant is not verified.
- Affected files: `apps/agent/internal/host/host_test.go`
- Verification evidence: fresh focused, full-package, race, vet, formatting, and diff checks passed; source inspection confirms the prior deadlock is fixed, but the regression has no cleanup-in-progress observation point.
- Roadmap carryover: this remains a compatibility prerequisite for the active IOP Agent CLI Runtime Milestone and does not complete a Roadmap Task id.

## Analysis

### Files Read

- `apps/agent/internal/host/host.go`
- `apps/agent/internal/host/ports.go`
- `apps/agent/internal/host/host_test.go`
- `packages/go/agenttask/ports.go`
- `packages/go/agenttask/types.go`
- `agent-contract/inner/agent-runtime.md`
- `agent-contract/inner/iop-agent-cli-runtime.md`
- `agent-roadmap/phase/automation-runtime-bridge/PHASE.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-ops/rules/project/domain/agent/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-task/m-iop-agent-cli-runtime/15+13_host_lifecycle/plan_local_G04_1.log`
- `agent-task/m-iop-agent-cli-runtime/15+13_host_lifecycle/code_review_cloud_G04_1.log`

### SDD Criteria

- SDD: `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`
- Status: `[승인됨]`; SDD lock: released; active `USER_REVIEW.md`: absent.
- This prerequisite does not claim a Milestone Task id or an Evidence Map row. S10 / `cli-surface`, S11 / `local-control`, S12 / `project-logs`, and S15 / `client-process-manager` consume the host lifecycle seam.
- The follow-up adds deterministic lifecycle ordering evidence only; later task-specific CLI, socket, log, and client-process evidence remains unchanged.

### Verification Context

- Supplied handoff: none.
- Repository-native sources: local test rules, Agent and testing domain rules, runtime contracts, the approved SDD, current host source/tests, and the archived review finding.
- Local preflight: `/config/.local/bin/go`; `go version go1.26.2 linux/arm64`; `GOROOT=/config/opt/go`; repository root `/config/workspace/iop-s0`.
- Fresh evidence: focused and full-package tests, package race test, vet, `gofmt -d`, and `git diff --check` all passed in the current intentional dirty checkout.
- Preconditions: modify only the host test file and keep sibling task artifacts untouched. No external runner, service, credential, or user entrypoint is required.
- Constraint: use fresh `-count=1` package evidence and repeat the focused temporal regression to detect synchronization flakiness.
- Gap: no assertion observes host status while the started-prefix `Stop` callback is blocked.
- Confidence: high; the missing observation is explicit in the test control flow, and a channel gate gives a deterministic oracle.

### Test Coverage Gaps

- Prompt cancellation of an in-progress component start is covered.
- Rollback of only the successfully started prefix, error identity, terminal status after cleanup, and repeated stop are covered.
- `Status.Stopped == false` while prefix cleanup is in progress is not covered.

### Symbol References

None. No production symbol is renamed, removed, or added.

### Split Judgment

Keep one compact test-only packet. The cleanup gate and its before/after status assertions are one temporal invariant and independently pass with the existing host package suite. Predecessor `13` remains satisfied by `agent-task/archive/2026/07/m-iop-agent-cli-runtime/13_agent_domain/complete.log`.

### Scope Rationale

Change only `apps/agent/internal/host/host_test.go`. Do not alter `host.go`, `ports.go`, bootstrap composition, shared `agenttask` lifecycle behavior, contracts, configuration, or sibling task artifacts because source inspection and fresh race evidence show the production fix already satisfies the invariant.

### Final Routing

- evaluation_mode: `isolated-reassessment`
- finalizer: `finalize-task-policy.sh pair`
- build closures: all `true`; grade scores `0/2/0/0/1`; base route `local-fit`; final route `recovery-boundary`; lane/grade `cloud-G03`; filename `PLAN-cloud-G03.md`
- review closures: all `true`; grade scores `0/2/0/1/1`; route `official-review`; lane/grade `cloud-G04`; filename `CODE_REVIEW-cloud-G04.md`
- large_indivisible_context: `false`
- positive loop risks: `temporal_state`, `concurrent_consistency` (count `2`)
- recovery signals: `review_rework_count=2`, `evidence_integrity_failure=false`
- capability-gap evidence: none

## Implementation Checklist

- [x] Add a deterministic cleanup gate to `TestHostStopCancelsInProgressStart`, assert `Status().Stopped` remains false while started-prefix cleanup is blocked, then preserve the existing terminal, identity, trace, and idempotency assertions.
- [x] Run fresh focused repetition, full-package, race, vet, formatting, and diff verification.
- [x] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_REVIEW_API-1] Observe Status Before Releasing Rollback Cleanup

#### Problem

At `apps/agent/internal/host/host_test.go:191`, the regression waits for `Stop` to return before it inspects status at lines 226-229. The test therefore proves only the final state and cannot detect an implementation that publishes `Status.Stopped` before the ready prefix finishes cleanup.

Before:

```go
// apps/agent/internal/host/host_test.go:191
// Wait for both goroutines to complete within a bounded deadline.
done := make(chan struct{})
go func() {
	wg.Wait()
	close(done)
}()

select {
case <-done:
case <-time.After(5 * time.Second):
	t.Fatal("timeout waiting for Stop and Start to complete")
}

// Status is read only after cleanup and both lifecycle calls have completed.
status := host.Status()
```

#### Solution

Give the ready fake component a `stopStarted` signal and a `stopRelease` gate. Wait until its rollback `Stop` callback is blocked, assert that `host.Status().Stopped` is still false, release cleanup exactly once, and then retain the existing final status, cancellation identity, trace, and repeated-stop checks.

After:

```go
stopStarted := make(chan struct{})
stopRelease := make(chan struct{})
readyComp := &fakeComponent{
	name:        "ready",
	trace:       trace,
	stopStarted: stopStarted,
	stopRelease: stopRelease,
}

// After concurrent Stop has cancelled Start and rollback has entered ready.Stop:
<-stopStarted
if status := host.Status(); status.Stopped {
	t.Fatal("Status().Stopped = true before cleanup completed")
}
close(stopRelease)

// Existing completion, error identity, trace, terminal, and idempotency
// assertions run only after cleanup is released.
```

Use a `sync.Once`-guarded release helper or an equivalent cleanup-safe pattern so a failed assertion cannot strand test goroutines.

#### Modified Files and Checklist

- [x] `apps/agent/internal/host/host_test.go` — add deterministic fake-component stop gates and the cleanup-in-progress status assertion.

#### Test Strategy

Extend `TestHostStopCancelsInProgressStart`; do not add a second test that duplicates the same concurrent setup. The existing ready component becomes the gated cleanup fixture. Assert `Stopped == false` after cleanup entry and before release, then keep the existing `context.Canceled`, exact trace, terminal status, and repeated-stop assertions. Run the focused test repeatedly and run the package under `-race`.

#### Verification

```bash
gofmt -w apps/agent/internal/host/*.go
go test -count=20 -run '^TestHostStopCancelsInProgressStart$' ./apps/agent/internal/host
```

Expected: formatting is clean and all 20 deterministic iterations pass.

## Dependencies and Execution Order

Predecessor `13_agent_domain` is complete at `agent-task/archive/2026/07/m-iop-agent-cli-runtime/13_agent_domain/complete.log`. Keep the existing `15+13_host_lifecycle` task path; this test-only follow-up must PASS before dependent bootstrap composition treats the lifecycle prerequisite as complete.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/agent/internal/host/host_test.go` | REVIEW_REVIEW_API-1 |

## Final Verification

```bash
gofmt -w apps/agent/internal/host/*.go
go test -count=20 -run '^TestHostStopCancelsInProgressStart$' ./apps/agent/internal/host
go test -count=1 ./apps/agent/internal/host
go test -count=1 -race ./apps/agent/internal/host
go vet ./apps/agent/internal/host
git diff --check
```

Expected: focused repetition, package, race, vet, formatting, and diff checks all pass with no race or formatting output.

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