<!-- task=m-iop-agent-cli-runtime/23+14,16,22_client_process_manager plan=2 tag=REVIEW_REVIEW_API -->

# Close Receipt Replay and Mutation Fencing Gaps

## For the Implementing Agent

Complete every implementation item and run every verification command below. Fill all implementation-owned sections in `CODE_REVIEW-cloud-G08.md` with actual notes and stdout/stderr, leave the active pair in place, and report ready for review. Final verdict classification, log/archive transitions, `complete.log`, and control-plane stop files belong only to the official reviewer. If blocked, record only the exact blocker, attempted commands/output, and resume condition in implementation-owned evidence; do not ask the user, call user-input tools, classify the next state, archive files, or write `complete.log`.

## Background

The second S15 review reproduced two lifecycle failures and found that the named accepted-incomplete test never reached manager receipt recovery. Durable receipts currently replay mutable state, evict far earlier than the local-control ledger, and collide with fixed daemon lifecycle command IDs. Focus/detail mutations also outlive manager closure because they use only the caller context.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-iop-agent-cli-runtime/23+14,16,22_client_process_manager/plan_local_G07_1.log`
- Prior review: `agent-task/m-iop-agent-cli-runtime/23+14,16,22_client_process_manager/code_review_cloud_G07_1.log`
- Verdict: FAIL
- Findings: 3 Required, 0 Suggested, 0 Nit.
- Affected boundary: checksum-covered `clientprocess.Record` receipts, internal daemon start/stop generations, and close-time focus/detail/connection mutation fencing.
- Verified evidence: focused, repeated race, package, vet, Darwin arm64 cross-build, formatting, `./packages/go/...`, and `./apps/agent/...` checks passed. Reviewer barrier regressions failed because a restarted daemon skipped launch-on-start and an in-flight focus context remained live after `Close`.
- Roadmap carryover: S15 / `client-process-manager` remains incomplete until exact accepted-incomplete recovery and lifecycle closure pass with PID/start/focus evidence.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md`
- Milestone link: [Milestone document](agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md)
- Task ids:
  - `client-process-manager`: daemon-owned Flutter/Unity start, stop, reconnect, crash recovery, and detail routing
- Completion mode: check-on-pass

## Analysis

### Files Read

- `agent-task/m-iop-agent-cli-runtime/23+14,16,22_client_process_manager/PLAN-local-G07.md`
- `agent-task/m-iop-agent-cli-runtime/23+14,16,22_client_process_manager/CODE_REVIEW-cloud-G07.md`
- `agent-task/m-iop-agent-cli-runtime/23+14,16,22_client_process_manager/plan_cloud_G10_0.log`
- `agent-task/m-iop-agent-cli-runtime/23+14,16,22_client_process_manager/code_review_cloud_G10_0.log`
- `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-contract/inner/agent-runtime.md`
- `agent-contract/inner/iop-agent-cli-runtime.md`
- `apps/agent/internal/clientprocess/types.go`
- `apps/agent/internal/clientprocess/store.go`
- `apps/agent/internal/clientprocess/manager.go`
- `apps/agent/internal/clientprocess/process.go`
- `apps/agent/internal/clientprocess/store_test.go`
- `apps/agent/internal/clientprocess/manager_test.go`
- `apps/agent/internal/localcontrol/ledger.go`
- `apps/agent/internal/localcontrol/client_operations.go`
- `apps/agent/internal/localcontrol/client_operations_test.go`
- `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/22+16_local_control/complete.log`

### SDD Criteria

The approved and unlocked SDD at `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md` maps S15 to `client-process-manager`. S15 requires singleton Flutter/Unity ownership, crash/reconnect, daemon-routed detail focus, and daemon survival; its Evidence Map requires fixture process tests plus a PID/start/focus trace. The receipt, restart-generation, and close-fencing regressions below are required to make that trace trustworthy rather than merely happy-path evidence.

### Verification Context

No external handoff was supplied. Repository-native inputs were the Agent, platform-common, testing, and local test rules; the approved SDD; both matching runtime contracts; current source/tests; the two archived review loops; and the three exact predecessor completion logs. Local preflight resolved `/config/.local/bin/go`, Go `1.26.2 linux/arm64`, GOROOT `/config/opt/go`, and the dirty shared checkout. Fresh reviewer commands passed for focused and repeated race tests, package suites, vet, Darwin arm64 cross-build, formatting, and diff checks, but deterministic reviewer-only barriers reproduced stale `daemon-start` receipt replay and an uncancelled focus operation. No external runner, credential, UI, service, or port is required; the Go test binary supplies real S15 fixture processes. Cached Go output is not accepted.

### Test Coverage Gaps

- `TestAcceptedIncompleteClientCommandReusesCompletedManagerReceipt` completes the local-control ledger before rebuilding it, so manager receipt recovery is untested.
- No test changes client state between a process-side completed receipt and accepted-incomplete ledger recovery, so replay of mutable current state is unobserved.
- No test proves the process receipt bound covers every command still retained by the 4096-entry local-control ledger or rejects action/result corruption.
- No test constructs a second daemon generation after prior `daemon-start`/`daemon-stop` receipts.
- Launch closure is covered, but focus/detail cancellation and connection mutation fencing are not.

### Symbol References

No public symbol is renamed. `checkReceiptLocked` is private to `manager.go` and is called by `Start`, `Stop`, `Focus`, and `StartOrFocusFlutter`; update all four call sites if its action-aware signature changes. Fixed `daemon-start`, `daemon-stop`, and `crash-restart` command IDs occur only in `manager.go`.

### Split Judgment

Keep one plan. Receipt retention/replay, daemon generation identity, and closure fencing all mutate the same checksum-covered per-kind record and must agree atomically with local-control recovery. Independent children could each pass while leaving a stale receipt or post-close side effect in the shared slot.

The encoded predecessors are satisfied:

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

### Scope Rationale

Do not change protobuf fields, the S11 ledger schema/capacity, client configuration, daemon bootstrap composition, Flutter/Unity applications, or direct client IPC. The existing OS backend already honors the context it receives; this packet corrects manager receipt semantics, internal lifecycle identity, mutation admission/cancellation, exact regressions, and S15 contract evidence only.

### Final Routing

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

## Implementation Checklist

- [ ] Make process receipts action-aware, strictly validated, retained for every ledger-visible command, and capable of replaying the immutable original result after intervening state changes.
- [ ] Separate automatic daemon lifecycle actions from caller command receipt replay and prove each daemon generation launches and stops its current process.
- [ ] Bind focus/detail and connection mutations to manager-owned closure admission, cancellation, and completion fencing.
- [ ] Update S15 contract evidence and pass fresh focused, repeated race, package, vet, Darwin cross-build, formatting, and diff verification.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_REVIEW_API-1] Make Process Receipt Recovery Exact

#### Problem

`apps/agent/internal/clientprocess/manager.go:835-888` returns the current slot record for a completed receipt, ignores the receipt action, and arbitrarily evicts entries at 50. `apps/agent/internal/clientprocess/store.go:147-156` validates only receipt key and status. The authoritative S11 ledger retains 4096 commands, and `apps/agent/internal/localcontrol/client_operations_test.go:230-297` never leaves its command accepted-but-incomplete.

#### Solution

Keep a bounded receipt for every command the local-control ledger can retain, reject capacity exhaustion instead of evicting an arbitrary recoverable command, validate action/status/result invariants, and require receipt lookup to match the expected action. Reconstruct the returned local-control-relevant `Result` from the immutable receipt snapshot rather than current slot state.

Before (`apps/agent/internal/clientprocess/manager.go:843`):

```go
if receipt.Status == CommandReceiptCompleted {
	return Result{
		Record:  cloneRecord(slot.record),
		Changed: receipt.Result.Changed,
		Action:  receipt.Result.Action,
	}, true, nil
}
```

After:

```go
receipt, err := validatedReceipt(slot.record, commandID, expectedAction)
if err != nil {
	return Result{}, true, err
}
return receipt.Result.Restore(slot.record.Kind), true, nil
```

#### Modified Files and Checklist

- [ ] `apps/agent/internal/clientprocess/types.go` — define the bounded immutable receipt result projection and capacity/error contract.
- [ ] `apps/agent/internal/clientprocess/store.go` — strictly validate receipt action, status, result, and ledger-aligned capacity; preserve deep copies.
- [ ] `apps/agent/internal/clientprocess/manager.go` — use action-aware lookup, exact result restoration, and fail-closed capacity handling without arbitrary eviction.
- [ ] `apps/agent/internal/clientprocess/store_test.go` — add invalid receipt projection and capacity boundary tables.
- [ ] `apps/agent/internal/localcontrol/client_operations_test.go` — inject failure on ledger `FinishCommand`, change client state, restart the ledger, and prove the original response returns with zero second process mutation.

#### Test Strategy

Add `TestAcceptedIncompleteClientCommandReusesExactManagerReceiptAfterStateChange`, `TestCommandReceiptCapacityMatchesLedger`, and table-driven `TestRecordRejectsInvalidCommandReceiptProjection`. The local-control regression must fail only the first final ledger CAS, retain the manager completion, alter the live projection, and recover through a new `Ledger` instance.

#### Verification

```bash
go test -count=1 ./apps/agent/internal/clientprocess ./apps/agent/internal/localcontrol -run 'TestAcceptedIncompleteClient|TestCommandReceipt|TestRecordRejectsInvalidCommandReceiptProjection'
```

Expected: exact original result replay, action mismatch/corruption rejection, no eviction below ledger capacity, and zero repeated focus/start mutation.

### [REVIEW_REVIEW_API-2] Separate Daemon Generations from Caller Receipts

#### Problem

`apps/agent/internal/clientprocess/manager.go:150` calls `Start` with fixed `daemon-start`, while `manager.go:509` calls `Stop` with fixed `daemon-stop`. Because `Start` and `Stop` consult durable caller receipts first, a later daemon generation can replay an old success and skip its current launch or stop.

#### Solution

Route automatic launch-on-start, crash restart, and manager close through explicit internal lifecycle operations that do not participate in caller `command_id` receipt replay. Keep their lifecycle action/result evidence in the record without creating reusable external receipt identities.

Before (`apps/agent/internal/clientprocess/manager.go:150`):

```go
if _, err := m.Start(ctx, kind, "daemon-start"); err != nil {
	return err
}
```

After:

```go
if _, err := m.startLifecycle(ctx, kind, lifecycleDaemonStart); err != nil {
	return err
}
```

#### Modified Files and Checklist

- [ ] `apps/agent/internal/clientprocess/manager.go` — separate internal lifecycle execution from external command receipt lookup/write.
- [ ] `apps/agent/internal/clientprocess/manager_test.go` — add restart-generation launch and current-generation close regressions.

#### Test Strategy

Add `TestStartConfiguredLaunchesAfterDaemonRestart` and `TestCloseStopsCurrentGenerationAfterPriorLifecycleReceipts` over one durable store and deterministic backend. Assert the second generation increments the start count, owns a new identity, stops that exact identity, and ends in `stopped`.

#### Verification

```bash
go test -count=25 -race ./apps/agent/internal/clientprocess -run 'TestStartConfiguredLaunchesAfterDaemonRestart|TestCloseStopsCurrentGenerationAfterPriorLifecycleReceipts'
```

Expected: every daemon generation launches once and closes the exact current process without stale receipt replay.

### [REVIEW_REVIEW_API-3] Fence Every Mutation Against Manager Closure

#### Problem

`apps/agent/internal/clientprocess/manager.go:347-375` and `manager.go:420-448` persist a pending receipt, release the slot, and call `backend.Focus` with only the caller context. `Close` cancels the manager context, not that focus context, and does not wait for the focus mutation. `SetConnected` similarly checks openness only before its durable mutation.

#### Solution

Introduce manager-owned mutation admission and cancellation separate from process watcher lifetime. Close must atomically stop new mutation admission, cancel admitted blocking mutations, wait for them to finish, perform exact internal client stop/reap, then stop watchers. Recheck the closure fence before every durable post-side-effect commit.

Before (`apps/agent/internal/clientprocess/manager.go:355`):

```go
focusErr := m.backend.Focus(ctx, kind, spec)
```

After:

```go
mutationCtx, finishMutation, err := m.beginMutation(ctx)
if err != nil {
	return Result{}, err
}
defer finishMutation()
focusErr := m.backend.Focus(mutationCtx, kind, spec)
```

#### Modified Files and Checklist

- [ ] `apps/agent/internal/clientprocess/manager.go` — add close-safe mutation admission/cancellation/waiting and apply it to start, stop, focus, detail, and connection projection changes.
- [ ] `apps/agent/internal/clientprocess/manager_test.go` — add deterministic focus/detail/connection barriers crossing `Close`.

#### Test Strategy

Add `TestConcurrentCloseCancelsInFlightFocus`, `TestConcurrentCloseCancelsInFlightDetail`, and `TestConcurrentCloseFencesConnectionMutation`. Backends/stores must expose barriers after admission; assert manager cancellation reaches the blocking call, no post-close durable mutation commits, all goroutines finish, and the current client identity is reaped.

#### Verification

```bash
go test -count=25 -race ./apps/agent/internal/clientprocess -run 'TestConcurrentClose'
```

Expected: launch, focus, detail, and connection crossings converge without leaked work, late mutation, race, or deadlock.

### [REVIEW_REVIEW_API-4] Refresh S15 Evidence and Contract Anchors

#### Problem

`agent-contract/inner/iop-agent-cli-runtime.md:49` claims durable command convergence and exact reaping, but its evidence list omits the recovery, daemon-generation, and close-fencing tests required by the reproduced failures.

#### Solution

Add the exact new test anchors and state that completed process receipts replay immutable accepted results only for matching external command actions, while internal daemon lifecycle generations cannot reuse external receipts.

#### Modified Files and Checklist

- [ ] `agent-contract/inner/iop-agent-cli-runtime.md` — update S15 evidence and lifecycle/idempotency wording to match the fixed implementation.

#### Test Strategy

No separate document test is added. Deterministic anchor searches plus the focused/race/package suites verify every referenced test exists and exercises the production boundary.

#### Verification

```bash
rg -n --sort path 'TestAcceptedIncompleteClientCommandReusesExactManagerReceiptAfterStateChange|TestCommandReceiptCapacityMatchesLedger|TestStartConfiguredLaunchesAfterDaemonRestart|TestCloseStopsCurrentGenerationAfterPriorLifecycleReceipts|TestConcurrentCloseCancelsInFlightFocus|TestConcurrentCloseCancelsInFlightDetail|TestConcurrentCloseFencesConnectionMutation' apps/agent/internal/clientprocess apps/agent/internal/localcontrol agent-contract/inner/iop-agent-cli-runtime.md
```

Expected: each test definition and its contract evidence anchor are present with deterministic path ordering.

## Dependencies and Execution Order

Implement exact receipts first, then internal lifecycle separation, then closure fencing, and finally update contract anchors. Existing predecessor completion is proven by the three exact `complete.log` paths under `Analysis > Split Judgment`.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/agent/internal/clientprocess/types.go` | REVIEW_REVIEW_API-1 |
| `apps/agent/internal/clientprocess/store.go` | REVIEW_REVIEW_API-1 |
| `apps/agent/internal/clientprocess/manager.go` | REVIEW_REVIEW_API-1, REVIEW_REVIEW_API-2, REVIEW_REVIEW_API-3 |
| `apps/agent/internal/clientprocess/store_test.go` | REVIEW_REVIEW_API-1 |
| `apps/agent/internal/clientprocess/manager_test.go` | REVIEW_REVIEW_API-2, REVIEW_REVIEW_API-3 |
| `apps/agent/internal/localcontrol/client_operations_test.go` | REVIEW_REVIEW_API-1 |
| `agent-contract/inner/iop-agent-cli-runtime.md` | REVIEW_REVIEW_API-4 |
| `agent-task/m-iop-agent-cli-runtime/23+14,16,22_client_process_manager/CODE_REVIEW-cloud-G08.md` | REVIEW_REVIEW_API-1, REVIEW_REVIEW_API-2, REVIEW_REVIEW_API-3, REVIEW_REVIEW_API-4 |

## Final Verification

```bash
gofmt -w apps/agent/internal/clientprocess/types.go apps/agent/internal/clientprocess/store.go apps/agent/internal/clientprocess/manager.go apps/agent/internal/clientprocess/store_test.go apps/agent/internal/clientprocess/manager_test.go apps/agent/internal/localcontrol/client_operations_test.go
go test -count=1 ./apps/agent/internal/clientprocess ./apps/agent/internal/localcontrol -run 'TestAcceptedIncompleteClient|TestCommandReceipt|TestRecordRejectsInvalidCommandReceiptProjection|TestStartConfiguredLaunchesAfterDaemonRestart|TestCloseStopsCurrentGenerationAfterPriorLifecycleReceipts|TestConcurrentClose'
go test -count=1 ./packages/go/agentconfig ./apps/agent/internal/clientprocess ./apps/agent/internal/localcontrol
go test -count=1 -race ./apps/agent/internal/clientprocess ./apps/agent/internal/localcontrol ./packages/go/agentstate
go test -count=25 -race ./apps/agent/internal/clientprocess ./apps/agent/internal/localcontrol -run 'TestAcceptedIncompleteClient|TestStartConfiguredLaunchesAfterDaemonRestart|TestCloseStopsCurrentGenerationAfterPriorLifecycleReceipts|TestConcurrentClose'
go vet ./packages/go/agentconfig ./apps/agent/internal/clientprocess ./apps/agent/internal/localcontrol
review_darwin_dir=$(mktemp -d) && review_darwin_bin="$review_darwin_dir/clientprocess.test" && GOOS=darwin GOARCH=arm64 go test -c -o "$review_darwin_bin" ./apps/agent/internal/clientprocess && test -s "$review_darwin_bin"
go test -count=1 ./packages/go/...
go test -count=1 ./apps/agent/...
git diff --check
```

Expected: exact receipt recovery never repeats a process mutation; every daemon generation launches/stops its own identity; all close crossings cancel and finish without late state; contract anchors, fresh/race/repeated/package/vet/Darwin/format/diff checks pass. Edge-Node diagnostics, external provider smoke, and client UI full-cycle execution remain not applicable because this packet changes no external route, provider, UI, or daemon composition.

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