<!-- task=m-iop-agent-cli-runtime/24+19,21,22,23_daemon_wiring plan=3 tag=REVIEW_REVIEW_TEST -->

# Complete Daemon Lifecycle Boundary Evidence

## For the Implementing Agent

Run every verification command, fill the implementation-owned sections in `CODE_REVIEW-*-G??.md` with actual notes and stdout/stderr, keep 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 only in implementation-owned evidence fields; do not ask the user, call user-input tools, create stop files, classify the next state, archive logs, or write `complete.log`.

## Background

The daemon lifecycle implementation now fails closed without a task runtime, rolls back partially launched clients, and responds to production termination signals. The second review found that the regression suite does not yet prove three required boundary outcomes: typed local-control read errors, preservation of a cleanup error alongside the launch error, and termination of a real configured client after SIGTERM. This test-only follow-up closes those evidence gaps without changing the reviewed production behavior.

## Archive Evidence Snapshot

- Current pair after finalization: `agent-task/m-iop-agent-cli-runtime/24+19,21,22,23_daemon_wiring/plan_local_G08_2.log` and `agent-task/m-iop-agent-cli-runtime/24+19,21,22,23_daemon_wiring/code_review_cloud_G08_2.log`.
- Verdict: FAIL; Required=3, Suggested=0, Nit=0.
- Required findings: service-level unavailable reads lack typed envelope assertions; partial-start rollback does not test joined cleanup error identity; built-binary SIGTERM does not prove configured-client cleanup.
- Affected files: `apps/agent/internal/bootstrap/module_test.go` and `apps/agent/cmd/agent/main_test.go`.
- Fresh reviewer verification passed focused tests, race suites, adjacent packages, vet, agent build, Darwin arm64 cross-build, built help, whole-Go tests, and `git diff --check`.
- Roadmap carryover: this remains a regression-only Milestone-adjacent packet with no `Roadmap Targets`; it must not claim S10, S11, or S15 completion.

## Analysis

### Files Read

- `agent-task/m-iop-agent-cli-runtime/24+19,21,22,23_daemon_wiring/PLAN-local-G08.md`
- `agent-task/m-iop-agent-cli-runtime/24+19,21,22,23_daemon_wiring/CODE_REVIEW-cloud-G08.md`
- `agent-task/m-iop-agent-cli-runtime/24+19,21,22,23_daemon_wiring/code_review_cloud_G08_1.log`
- `apps/agent/internal/bootstrap/module.go`
- `apps/agent/internal/bootstrap/module_test.go`
- `apps/agent/cmd/agent/main.go`
- `apps/agent/cmd/agent/main_test.go`
- `apps/agent/internal/host/host.go`
- `apps/agent/internal/clientprocess/process.go`
- `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-contract/inner/agent-runtime.md`
- `agent-contract/inner/iop-agent-cli-runtime.md`

### SDD Criteria

- SDD: `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`; status `[승인됨]`, lock released.
- S10 / `cli-surface`: the built binary must prove bounded signal-driven shutdown without claiming the pending full headless transcript.
- S11 / `local-control`: unavailable task-runtime reads and mutations must return typed errors and must not emit a state-change event.
- S15 / `client-process-manager`: daemon-owned configured clients must terminate during daemon shutdown and partial-start cleanup must preserve exact failure evidence.
- The S10, S11, and S15 Evidence Map rows require the three focused regressions and the integrated race/build checks below. This packet completes no Milestone Task id.

### Verification Context

- No verification handoff was supplied. Repository-native evidence came from the local test rules, Agent/testing domain rules, approved SDD, contracts, current source, and fresh reviewer commands.
- Runner: `/config/workspace/iop-s0`, Linux arm64; Go is `/config/.local/bin/go`, `go1.26.2 linux/arm64`, GOROOT `/config/opt/go`.
- Fresh reviewer evidence: focused bootstrap/CLI tests, package race tests, adjacent packages, vet, `make build-agent`, Darwin arm64 cross-build, built help, `go test -count=1 ./...`, and `git diff --check` passed.
- The built-binary fixture uses only local `/bin/sh` and `/bin/sleep`, writes its PID under `t.TempDir`, and requires no credential, provider login, remote service, or external runner. Darwin verification remains compile-only.
- Fresh execution is required; every Go test command uses `-count=1`. Confidence is high.

### Test Coverage Gaps

- `TestDaemonTaskRuntimeFailsClosedWithoutOwner` covers bridge errors and one service mutation, but not a service-level read error or exact `invalid_state` response typing.
- `TestDaemonPartialClientLaunchFailureRollsBack` proves the first client is reaped and the launch error is retained, but its backend cannot return a cleanup error and the joined identity is untested.
- `TestBuiltBinarySignalShutdownCleansDaemon` proves bounded exit and socket removal, but the test configuration starts no client process.

### Symbol References

- No production symbol is renamed or removed.
- Test helper changes remain local to `module_test.go` and `main_test.go`.

### Split Judgment

- Keep one packet. The three regressions close one daemon ownership boundary: unavailable runtime requests cannot fabricate success, partial startup retains all errors, and signal shutdown reaps all daemon-owned resources. The packet is test-only and independently passes without a production change.
- Predecessors 19, 21, 22, and 23 remain satisfied by the already recorded prior-loop evidence; no new archive lookup is required for this unchanged subtask path.

### Scope Rationale

- Exclude `apps/agent/internal/bootstrap/module.go` and `apps/agent/cmd/agent/main.go` because fresh review found their required behavior implemented correctly.
- Exclude `apps/agent/internal/taskloop/**`, authenticated smoke files, contracts, and roadmap state because child 25/26 and runtime completion own those scopes.
- Do not add a new process helper file: use a temporary shell/sleep fixture declared inside the existing built-binary test.

### Final Routing

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

## Implementation Checklist

- [ ] Prove unavailable task-runtime reads and mutations produce typed local-control `invalid_state` envelopes, no success payload, and no retained event.
- [ ] Prove partial configured-client startup preserves both launch and cleanup error identities while reaping the earlier client.
- [ ] Prove built-binary SIGTERM removes the daemon socket and terminates a deterministic configured fixture client within the shutdown bound.
- [ ] Run the complete fresh focused, race, adjacent-package, vet, build, Darwin cross-build, whole-Go, help, and diff verification.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_REVIEW_TEST-1] Assert Typed Fail-Closed Service Responses

#### Problem

`apps/agent/internal/bootstrap/module_test.go:687` sends only `project.start` through `localcontrol.Service` and checks only that some error payload exists. The plan requires a `project.status` read at the service boundary and exact typed failure evidence for both read and mutation paths.

#### Solution

Add a service-level project-status request beside the existing mutation request. Assert both envelopes have kind `ERROR`, code `localcontrol.ErrorInvalidState`, no response payload, and correct correlation. Preserve the ledger replay assertion that no event was appended.

Before (`apps/agent/internal/bootstrap/module_test.go:708`):

```go
resp := svc.Handle(ctx, true, req)
if resp.GetError() == nil {
	t.Fatalf("expected error envelope from Service.Handle for unwired mutation, got %#v", resp)
}
```

After:

```go
for _, req := range []*iop.AgentLocalEnvelope{statusRequest, startRequest} {
	resp := svc.Handle(ctx, true, req)
	if resp.GetKind() != iop.AgentLocalKind_AGENT_LOCAL_KIND_ERROR ||
		resp.GetError().GetCode() != localcontrol.ErrorInvalidState ||
		resp.GetResponse() != nil {
		t.Fatalf("unavailable response = %#v", resp)
	}
}
```

#### Modified Files and Checklist

- [ ] `apps/agent/internal/bootstrap/module_test.go` — add read and mutation service requests with exact typed error/no-success assertions.

#### Test Strategy

Extend `TestDaemonTaskRuntimeFailsClosedWithoutOwner`; use the existing production bridge, service, and durable ledger. Require exact error typing and zero retained events.

#### Verification

```bash
go test -count=1 ./apps/agent/internal/bootstrap -run '^TestDaemonTaskRuntimeFailsClosedWithoutOwner$'
```

Expected: both unavailable operation classes fail with `invalid_state`, no success payload, and no event.

### [REVIEW_REVIEW_TEST-2] Preserve Launch and Cleanup Error Identities

#### Problem

`apps/agent/internal/bootstrap/module_test.go:755` always returns nil from cleanup, so `TestDaemonPartialClientLaunchFailureRollsBack` cannot prove the joined-error contract implemented by `clientProcessAdapter.Start`.

#### Solution

Give `partialFailBackend` an optional cleanup error. Its `Signal` path must still abort the matching fake process before returning that error. Exercise the cleanup-error case and require the returned daemon startup error to match both `launchErr` and `cleanupErr`, while retaining the existing process-reaped and stopped-host assertions.

Before (`apps/agent/internal/bootstrap/module_test.go:755`):

```go
func (b *partialFailBackend) Signal(...) error {
	// abort matching process
	return nil
}
```

After:

```go
func (b *partialFailBackend) Signal(...) error {
	// abort matching process
	return b.cleanupErr
}
```

#### Modified Files and Checklist

- [ ] `apps/agent/internal/bootstrap/module_test.go` — inject cleanup failure and assert both joined identities plus no surviving client.

#### Test Strategy

Extend `TestDaemonPartialClientLaunchFailureRollsBack` or make it table-driven with clean-cleanup and cleanup-error cases. The cleanup-error case must still close the fake process before returning its sentinel.

#### Verification

```bash
go test -count=1 ./apps/agent/internal/bootstrap -run '^TestDaemonPartialClientLaunchFailureRollsBack$'
```

Expected: the earlier client is reaped and `errors.Is` finds both launch and cleanup sentinels.

### [REVIEW_REVIEW_TEST-3] Reap a Configured Client on Production SIGTERM

#### Problem

`apps/agent/cmd/agent/main_test.go:633` derives local config from `trackedLocalConfig`, which contains no `clients` section. The built-binary test therefore proves socket cleanup but not the inherited required client-process cleanup.

#### Solution

Append a launch-on-start Flutter fixture using `/bin/sh -c "echo $$ > <temp pid file>; exec /bin/sleep 60"`. Wait for both socket and PID-file readiness, send SIGTERM to `iop-agent`, require exit code 0 within five seconds, and poll signal 0 on the recorded child PID until it returns `os.ErrProcessDone` or `syscall.ESRCH`. Register bounded test cleanup for both processes on every failure path.

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

```go
localConfig := strings.ReplaceAll(trackedLocalConfig, "/tmp/iop-agent-test/state", stateRoot)
localConfig = strings.ReplaceAll(localConfig, "/tmp/iop-agent-test/logs", logRoot)
```

After:

```go
pidPath := filepath.Join(tmpDir, "fixture.pid")
localConfig += fmt.Sprintf(`
clients:
  flutter:
    executable: /bin/sh
    working_directory: %s
    args: ["-c", "echo $$ > %s; exec /bin/sleep 60"]
    launch_on_start: true
`, tmpDir, pidPath)
```

#### Modified Files and Checklist

- [ ] `apps/agent/cmd/agent/main_test.go` — add the long-lived fixture, readiness, bounded cleanup guards, and post-SIGTERM PID-exit assertion.

#### Test Strategy

Extend `TestBuiltBinarySignalShutdownCleansDaemon`; use only `t.TempDir`, `/bin/sh`, and `/bin/sleep`. Do not add a tracked fixture or depend on provider credentials.

#### Verification

```bash
go test -count=1 ./apps/agent/cmd/agent -run '^TestBuiltBinarySignalShutdownCleansDaemon$'
```

Expected: the built daemon exits cleanly after SIGTERM and both its socket and configured client PID disappear.

## Dependencies and Execution Order

Implement REVIEW_REVIEW_TEST-1 and REVIEW_REVIEW_TEST-2 in `module_test.go`, then REVIEW_REVIEW_TEST-3 in `main_test.go`, followed by integrated verification.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/agent/internal/bootstrap/module_test.go` | REVIEW_REVIEW_TEST-1, REVIEW_REVIEW_TEST-2 |
| `apps/agent/cmd/agent/main_test.go` | REVIEW_REVIEW_TEST-3 |
| `agent-task/m-iop-agent-cli-runtime/24+19,21,22,23_daemon_wiring/CODE_REVIEW-cloud-G06.md` | Implementation evidence |

## Final Verification

```bash
gofmt -w apps/agent/internal/bootstrap/module_test.go apps/agent/cmd/agent/main_test.go
go test -count=1 ./apps/agent/internal/bootstrap -run 'TestDaemon(TaskRuntimeFailsClosedWithoutOwner|PartialClientLaunchFailureRollsBack)'
go test -count=1 ./apps/agent/cmd/agent -run 'Test(BuiltBinarySignalShutdownCleansDaemon|RunServeCancellationAndCleanup)'
go test -count=1 -race ./apps/agent/internal/bootstrap ./apps/agent/cmd/agent ./apps/agent/internal/command
go test -count=1 ./apps/agent/internal/clientprocess ./apps/agent/internal/localcontrol ./apps/agent/internal/projectlog ./packages/go/agentstate
go vet ./apps/agent/internal/bootstrap ./apps/agent/cmd/agent ./apps/agent/internal/command
make build-agent
review_tmp_dir=$(mktemp -d)
GOOS=darwin GOARCH=arm64 go build -trimpath -o "$review_tmp_dir/iop-agent-darwin-arm64" ./apps/agent/cmd/agent
test -s "$review_tmp_dir/iop-agent-darwin-arm64"
build/bin/iop-agent --help >/dev/null
go test -count=1 ./...
git diff --check
```

Expected: typed fail-closed envelopes, joined cleanup error identities, configured-client signal cleanup, fresh race/adjacent/whole-Go suites, vet, builds, help, and diff checks all pass without external credentials. Cached output is not accepted.

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