<!-- task=m-iop-agent-cli-runtime/22+16_local_control plan=2 tag=REVIEW_API -->

# Complete Durable Local-Control Command Convergence

## For the Implementing Agent

Fill every implementation-owned section in `CODE_REVIEW-*-G??.md` with fresh command output, keep the active pair in place, and report ready for review. 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 control-plane stop files, classify the next state, archive logs, or write `complete.log`; finalization is owned by the code-review skill.

## Background

Two official reviews have left S11 command convergence incomplete. The current ledger still exposes a provisional durable acceptance as a terminal replay, concurrent duplicates do not wait for completion, accepted-but-unfinished restart recovery is absent, and the follow-up review artifact contains no implementation or verification evidence.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-iop-agent-cli-runtime/22+16_local_control/plan_cloud_G08_1.log`
- Prior review: `agent-task/m-iop-agent-cli-runtime/22+16_local_control/code_review_cloud_G08_1.log`
- Verdict: `FAIL`; Required `2`, Suggested `0`, Nit `0`.
- Findings: `Ledger.BeginCommand` still returns a matching incomplete response without checking `durableCommand.Complete`; `Service.handleProjectMutation` returns that provisional `"accepted"` response; the blocking-concurrency and accepted-but-incomplete restart regressions are absent; all implementation and verification fields in the review artifact were left unfilled.
- Affected files: `apps/agent/internal/localcontrol/ledger.go`, `apps/agent/internal/localcontrol/service.go`, `apps/agent/internal/localcontrol/ledger_test.go`, `apps/agent/internal/localcontrol/service_test.go`, and the active review artifact.
- Verification evidence: `go test -list` found only the completed-restart, conflict, and response-kind-only concurrency tests. The focused plan pattern passed without running an accepted-but-incomplete regression, so it is not completion evidence.
- Roadmap carryover: approved SDD scenario S11 and Milestone task `local-control` remain incomplete until durable command convergence and fresh final verification are proven.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/automation-runtime-bridge/milestones/iop-agent-cli-runtime.md)
- Task ids:
  - `local-control`: daemon-owned same-user local proto-socket lifecycle, state, events, and control endpoints
- Completion mode: check-on-pass

## Analysis

### Files Read

- `agent-task/m-iop-agent-cli-runtime/22+16_local_control/PLAN-cloud-G08.md`
- `agent-task/m-iop-agent-cli-runtime/22+16_local_control/CODE_REVIEW-cloud-G08.md`
- `agent-task/m-iop-agent-cli-runtime/22+16_local_control/code_review_cloud_G10_0.log`
- `agent-task/archive/2026/07/m-iop-agent-cli-runtime/16+13,15_bootstrap_composition/complete.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/index.md`
- `agent-contract/inner/iop-agent-cli-runtime.md`
- `agent-contract/inner/agent-runtime.md`
- `agent-spec/index.md`
- `agent-ops/rules/project/domain/agent/rules.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-test/local/testing-smoke.md`
- `apps/agent/internal/localcontrol/protocol.go`
- `apps/agent/internal/localcontrol/ledger.go`
- `apps/agent/internal/localcontrol/service.go`
- `apps/agent/internal/localcontrol/ledger_test.go`
- `apps/agent/internal/localcontrol/service_test.go`

### SDD Criteria

- SDD: `agent-roadmap/sdd/automation-runtime-bridge/iop-agent-cli-runtime/SDD.md`; status `[승인됨]`, lock released, no unresolved user review.
- Target: S11 maps to Milestone task `local-control`.
- Evidence Map: the `local-control` completion must link the local-control contract and prove same-user/other-user behavior. The current follow-up is the missing durable command-convergence portion of the contract's S11 evidence row: original completed replay, one effective mutation, one coherent event/cursor, and restart safety.
- The implementation checklist keeps production coordination and its deterministic concurrency/restart tests in one packet. Final verification retains the fresh package/race, contract anchor, protobuf determinism, Darwin arm64, shared-package, and whole-Go evidence required for Roadmap Completion.

### Verification Context

- No external handoff was supplied. Repository-native sources were the active pair, approved SDD, local-control contract, Agent and platform/testing domain rules, local test profiles, source, and tests.
- Local preflight: repo `/config/workspace/iop-s0`, branch `feature/iop-agent-cli-runtime`, HEAD `4e23ded3f482bf61b6de3651dbc913c2e3384193`, Linux arm64, Go `1.26.2` at `/config/.local/bin/go`, GOROOT `/config/opt/go`, no external service or credential.
- Fresh reviewer evidence: `go test -list 'Test(ConcurrentIdenticalCommand|AcceptedButIncomplete|CommandIdempotency|CommandIDConflict)' ./apps/agent/internal/localcontrol` did not list an accepted-but-incomplete test. The corresponding focused `go test -count=1` command exited zero because the missing pattern matches no test.
- The checkout contains intentional sibling task changes. Capture `git status --short` before and after verification and run final evidence only after the four planned source/test files are stable. Cached Go results are not accepted.
- Full daemon-cycle verification remains outside this packet because composition is owned by `24+19,21,22,23,24_daemon_wiring`. Package integration, the real local-control test surface, shared state verification, and Darwin cross-build are the current S11 execution boundary.
- Confidence: high. The incomplete-record return is directly visible in production source, the regression names are absent, and the archived blocking reproducer already demonstrated the divergent response.

### Test Coverage Gaps

- `TestConcurrentIdenticalCommandDispatchesExactlyOnce` checks response kind and one host call only; it does not compare completed response payloads, retained events, revisions, or replay cursors.
- `TestCommandIdempotencySurvivesRestart` restarts only after `FinishCommand`; it does not exercise an accepted-but-unfinished durable record or recovery-owner selection.
- No deterministic test proves that context-cancelled waiters exit without disturbing the owner, that restart recovery reuses the stable command ID, or that a different immutable hash conflicts without mutation while an identical command is incomplete.

### Symbol References

None. No public or shared symbol rename is required. Any new lifecycle/result types remain private to `apps/agent/internal/localcontrol`.

### Split Judgment

Keep one packet. Durable acceptance state, in-process owner/waiter coordination, restart recovery ownership, final response/event persistence, and regression tests form one command-convergence invariant and cannot independently PASS. Predecessor index `16` is satisfied by `agent-task/archive/2026/07/m-iop-agent-cli-runtime/16+13,15_bootstrap_composition/complete.log`.

### Scope Rationale

Do not change protobuf fields, peer credential code, socket framing, event retention policy, `packages/go/agentstate`, daemon composition, client-process operations, Control Plane paths, contracts, specs, or roadmap files. The active contract already defines the required behavior; the repair belongs only to local ledger/service coordination and focused tests.

### Final Routing

- evaluation_mode: `isolated-reassessment`
- finalizer: `finalize-task-policy.sh pair`
- closures: build and review scope, context, verification, evidence, ownership, and decisions are closed.
- build grade scores: scope `1`, state/concurrency `2`, blast/irreversibility `2`, evidence/diagnosis `1`, verification `1` → `G07`.
- build base: `local-fit`; final route: `recovery-boundary`, `cloud-G07`, `PLAN-cloud-G07.md`.
- review grade scores: scope `1`, state/concurrency `2`, blast/irreversibility `2`, evidence/diagnosis `1`, verification `1` → `G07`.
- review route: `official-review`, `cloud-G07`, `CODE_REVIEW-cloud-G07.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=true`
- capability-gap evidence: none.

## Implementation Checklist

- [ ] Distinguish new/recovery ownership, incomplete waiting, and completed replay so no incomplete durable response is exposed and every identical request converges on one stored final result.
- [ ] Add deterministic blocking-concurrency and accepted-but-incomplete restart regressions that prove final response, event, revision/cursor, conflict, cancellation, and effective-mutation behavior.
- [ ] Run fresh focused, race, contract, protobuf-determinism, Darwin cross-build, shared-package, and whole-Go verification on stable source.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_API-1] Complete Durable Ownership and Replay

#### Problem

`apps/agent/internal/localcontrol/ledger.go:141-155` decodes and returns every matching command record without checking `record.Complete`. `apps/agent/internal/localcontrol/service.go:237-258` persists provisional state `"accepted"` and returns that record as terminal for a duplicate. There is no owner/waiter lifecycle, and a process restart cannot distinguish incomplete recovery ownership from completed replay.

#### Solution

Keep the durable command schema compatible, but make the private ledger result explicitly distinguish `owner`, `wait`, and `completed`. A new command becomes an owner only after durable acceptance. A matching complete command returns the stored final response. A matching incomplete command joins an existing same-process flight as a waiter; when no live flight exists after restart, exactly one caller becomes the recovery owner and reuses the stable command ID at the idempotent controller boundary.

Before (`apps/agent/internal/localcontrol/ledger.go:141-155`):

```go
if record, exists := state.Commands[commandID]; exists {
	if record.RequestHash != requestHash {
		return commandConflict()
	}
	response, err := decodeStoredEnvelope(record.Response)
	if err != nil {
		return nil, false, protocolError(
			ErrorInternal,
			"durable command state is invalid",
		)
	}
	return response, false, nil
}
```

After:

```go
if record, exists := state.Commands[commandID]; exists {
	if record.RequestHash != requestHash {
		return commandConflict()
	}
	if record.Complete {
		return completedCommand(record)
	}
	return l.joinOrRecoverIncomplete(commandID, requestHash)
}
```

Use a context-aware wait path. On every owner exit, either persist the final response and release waiters to reload it or release them with a bounded internal failure; never strand a waiter or let a cancelled waiter cancel the owner. `FinishCommand` remains the single atomic command/event completion write and returns an existing complete record without appending another event.

#### Modified Files and Checklist

- [ ] `apps/agent/internal/localcontrol/ledger.go` — add private command lifecycle/flight coordination and expose only completed durable responses.
- [ ] `apps/agent/internal/localcontrol/service.go` — dispatch one new/recovery owner, wait context-safely for in-process completion, and always return the stored final result.

#### Test Strategy

Production concurrency and restart behavior changes, so REVIEW_API-2 regressions are mandatory. Existing completed replay, conflict, event retention, and protocol behavior remain compatibility oracles.

#### Verification

```bash
gofmt -d apps/agent/internal/localcontrol/ledger.go apps/agent/internal/localcontrol/service.go
go test -count=1 ./apps/agent/internal/localcontrol -run 'TestConcurrentIdenticalCommandConvergesOnCompletedResponse|TestAcceptedButIncompleteCommandReconcilesAfterRestart|TestCommandIdempotency|TestCommandIDConflict'
```

Expected: formatting is clean; all lifecycle tests pass; no provisional response is returned.

### [REVIEW_API-2] Prove Concurrency, Recovery, and Cancellation

#### Problem

`apps/agent/internal/localcontrol/service_test.go:315-350` allows divergent payloads because it checks only response kind. `apps/agent/internal/localcontrol/ledger_test.go:15-57` covers only replay after a completed command. Neither file proves incomplete recovery, cancellation isolation, one event/cursor, or conflict behavior during an in-flight command.

#### Solution

Add a channel-controlled idempotent controller fixture with separate invocation and effective-mutation counts. In the concurrent test, hold the owner after durable acceptance, start identical waiters plus a different-hash conflict, cancel one waiter, release the owner, and compare every successful response to the completed stored response. Assert one effective mutation, one retained/live event, one state revision, one replay cursor, and zero conflicting mutation.

For restart recovery, seed an incomplete durable acceptance and simulate a completed controller-side effect without `FinishCommand`. Reopen the ledger/service, replay the same request, and prove that recovery invokes the controller with the same stable command ID, the controller deduplicates the effect, the final response is stored, all later replays are identical, and exactly one event/cursor exists.

#### Modified Files and Checklist

- [ ] `apps/agent/internal/localcontrol/service_test.go` — replace the response-kind-only concurrency test with deterministic owner/waiter/conflict/cancellation assertions.
- [ ] `apps/agent/internal/localcontrol/ledger_test.go` — add accepted-but-incomplete restart reconciliation and final replay assertions.

#### Test Strategy

Write `TestConcurrentIdenticalCommandConvergesOnCompletedResponse` and `TestAcceptedButIncompleteCommandReconcilesAfterRestart` with channels, never sleeps. Assert protobuf equality, response state, command ID, state revision, replay daemon/cursor, retained event sequence, controller invocation/effective-mutation counts, waiter cancellation, and conflict zero-mutation behavior. Run both under `-race`.

#### Verification

```bash
go test -count=1 ./apps/agent/internal/localcontrol -run 'TestConcurrentIdenticalCommandConvergesOnCompletedResponse|TestAcceptedButIncompleteCommandReconcilesAfterRestart|TestCommandIdempotency|TestCommandIDConflict'
go test -count=1 -race ./apps/agent/internal/localcontrol ./packages/go/agentstate
```

Expected: deterministic regressions and the complete race suite pass with no race report.

### [REVIEW_API-3] Rebuild Trusted S11 Evidence

#### Problem

`agent-task/m-iop-agent-cli-runtime/22+16_local_control/CODE_REVIEW-cloud-G08.md:52-65` left every implementation item unchecked, and every verification output block remained a placeholder. The prior focused command exited zero despite the missing restart regression, so the artifact cannot support S11 completion.

#### Solution

After REVIEW_API-1 and REVIEW_API-2 are complete, run every final command fresh on stable source and paste exact stdout/stderr into `CODE_REVIEW-cloud-G07.md`. Record any command substitution or omission in `Deviations from Plan`; do not summarize or reconstruct output.

#### Modified Files and Checklist

- [ ] `agent-task/m-iop-agent-cli-runtime/22+16_local_control/CODE_REVIEW-cloud-G07.md` — record completed items, decisions, deviations, and exact fresh output.

#### Test Strategy

No additional test file is needed beyond REVIEW_API-2. This item validates evidence fidelity and the complete package/contract/build surface.

#### Verification

```bash
go test -count=1 ./apps/agent/internal/localcontrol
go vet ./apps/agent/internal/localcontrol ./packages/go/...
git diff --check
```

Expected: all commands exit zero and their exact output is present in the active review artifact.

## Dependencies and Execution Order

Predecessor `16+13,15_bootstrap_composition` is satisfied by `agent-task/archive/2026/07/m-iop-agent-cli-runtime/16+13,15_bootstrap_composition/complete.log`. Implement REVIEW_API-1 before REVIEW_API-2, then capture REVIEW_API-3 evidence.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/agent/internal/localcontrol/ledger.go` | REVIEW_API-1 |
| `apps/agent/internal/localcontrol/service.go` | REVIEW_API-1 |
| `apps/agent/internal/localcontrol/service_test.go` | REVIEW_API-2 |
| `apps/agent/internal/localcontrol/ledger_test.go` | REVIEW_API-2 |
| `agent-task/m-iop-agent-cli-runtime/22+16_local_control/CODE_REVIEW-cloud-G07.md` | REVIEW_API-3 |

## Final Verification

Cached Go results are not accepted.

```bash
command -v go
go version
go env GOROOT
git status --short
gofmt -w apps/agent/internal/localcontrol/ledger.go apps/agent/internal/localcontrol/service.go apps/agent/internal/localcontrol/service_test.go apps/agent/internal/localcontrol/ledger_test.go
go test -list 'Test(ConcurrentIdenticalCommandConvergesOnCompletedResponse|AcceptedButIncompleteCommandReconcilesAfterRestart|CommandIdempotency|CommandIDConflict)' ./apps/agent/internal/localcontrol
go test -count=1 ./apps/agent/internal/localcontrol -run 'TestConcurrentIdenticalCommandConvergesOnCompletedResponse|TestAcceptedButIncompleteCommandReconcilesAfterRestart|TestCommandIdempotency|TestCommandIDConflict'
go test -count=1 ./apps/agent/internal/localcontrol
go test -count=1 -race ./apps/agent/internal/localcontrol ./packages/go/agentstate
go vet ./apps/agent/internal/localcontrol ./packages/go/...
cp proto/gen/iop/agent.pb.go /tmp/iop-agent-localcontrol-agent.pb.go
make proto
cmp -s proto/gen/iop/agent.pb.go /tmp/iop-agent-localcontrol-agent.pb.go
GOOS=darwin GOARCH=arm64 go test -c -o /tmp/localcontrol-darwin.test ./apps/agent/internal/localcontrol
go test -count=1 ./packages/go/...
go test -count=1 ./...
rg -n --sort path 'S11|agent.proto|localcontrol|same OS user|SO_PEERCRED|LOCAL_PEERCRED|getpeereid' agent-contract/inner/iop-agent-cli-runtime.md apps/agent/internal/localcontrol proto/iop/agent.proto
git diff --check
git status --short
```

Expected: the two required regression names are listed, focused/package/race/shared/repository tests and vet pass freshly, protobuf generation is unchanged, the Darwin arm64 cross-build succeeds, contract anchors remain present, and the before/after worktree status shows no unplanned drift in the claimed files. Repository Edge-Node diagnostics, auxiliary E2E smoke, external providers, and a full `iop-agent` daemon cycle remain out of scope because daemon composition is a downstream task.

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