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

# Same-User Local Control Proto-Socket

## For the Implementing Agent

Fill all implementation-owned sections in `CODE_REVIEW-cloud-G10.md` with actual code and command evidence. Leave the pair active and report ready for review. Only the official reviewer may classify next state, archive, write `complete.log`, or create control-plane artifacts. Record blockers and exact resume conditions in evidence fields only.

## Background

The standalone daemon must expose one local control boundary shared by future Flutter and Unity clients without moving runtime ownership into either client. The accepted contract requires protobuf frames, same-OS-user authorization, mutation idempotency, replayable events, and fail-closed recovery.

## 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-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/agent-runtime.md`
- `agent-contract/inner/iop-agent-cli-runtime.md`
- `packages/go/agenttask/ports.go`
- `packages/go/agenttask/types.go`
- `packages/go/agentstate/store.go`
- `proto/iop/control.proto`
- `apps/control-plane/internal/wire/client.go`
- `apps/control-plane/internal/wire/client_test.go`
- `Makefile`
- `go.mod`

### SDD Criteria

Approved SDD scenario S11 maps to `local-control`. The Evidence Map requires a new local-control agent contract, OS-user socket boundary test, no app-token fallback, denied cross-user dispatch, and snapshot recovery. The checklist derives directly from envelope/version, operation, authorization, command-id, event sequence, replay-gap, and safe-error rows in `agent-contract/inner/iop-agent-cli-runtime.md`.

### Verification Context

No handoff was supplied. Local sources are `agent-test/local/rules.md`, platform-common smoke, and testing smoke. Current runner is Linux arm64 with Go 1.26.2; required production targets are Linux and macOS. External Verification Preflight: repo `/config/workspace/iop-s0`, branch `feature/iop-agent-cli-runtime`, analyzed HEAD `bf64b7d86511a527ff48ddf196e33531551e79a5`, dirty only for current roadmap/plan artifacts; local proto-socket module resolves at `../proto-socket/go`. Tests must use ephemeral Unix socket paths under a temp directory, no fixed ports/hosts, and no external services. Cross-compile Darwin arm64 and run Linux same-user integration locally. Cached output is not accepted.

### Test Coverage Gaps

- Existing proto-socket use is TCP/WebSocket and has no Unix listener or OS peer credential boundary.
- No `LocalControlEnvelope` protobuf exists.
- No persistent command-id ledger, replay cursor, or snapshot recovery test exists.
- A real cross-user process is not available in the current unprivileged runner; denied-peer behavior needs a credential-source seam plus exact UID mismatch trace.

### Symbol References

None. This adds a new protocol and application package. Existing `control.proto` remains the Control Plane client contract and must not be repurposed.

### Split Judgment

Authorization, command idempotency, replay, and mutation dispatch are one indivisible security invariant: no frame may reach a mutator before all gates pass. The packet depends on `16+13,15_bootstrap_composition`; its `complete.log` is currently missing. S15 depends on this packet because client-process operations are added to this stable control service later.

### Scope Rationale

Do not modify Control Plane WebSocket behavior, add app tokens, implement Flutter/Unity clients, expose TCP, duplicate shared task state transitions, or invent cross-user fallback. Concrete client process operations remain S15. Generated protobuf files must come only from `make proto`.

### Final Routing

- evaluation_mode: `first-pass`
- finalizer: `finalize-task-policy.sh pair`
- build: `cloud-G10`, basis `grade-boundary`, filename `PLAN-cloud-G10.md`
- review: `cloud-G10`, basis `official-review`, filename `CODE_REVIEW-cloud-G10.md`
- large_indivisible_context: `true`
- positive loop risks: `cross-platform peer credentials`, `durable idempotency/replay invariant` (count `2`)
- recovery signals: rework `0`, evidence-integrity failure `false`
- capability-gap evidence: none; G10 grade boundary selected

## Implementation Checklist

- [ ] Define and generate the versioned local-control protobuf envelope, requests, responses, events, errors, snapshots, and operation payloads.
- [ ] Implement a daemon-owned Unix proto-socket with strict permissions and Linux/macOS same-user peer authorization before frame dispatch.
- [ ] Implement bounded frame validation, command-id idempotency, ordered event retention/replay, snapshot recovery, and safe typed errors.
- [ ] Bind read/project mutation operations to narrow host ports and prove rejected frames perform zero mutation.
- [ ] Update Makefile and the standalone contract with actual S11 transport/source/test paths.
- [ ] Run fresh protocol, security, restart, race, and Linux/Darwin build verification.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [API-1] Define the Local-Control Protocol

#### Problem

`proto/iop/control.proto:1-16` is owned by Control Plane communication, while `agent-contract/inner/iop-agent-cli-runtime.md:91-120` requires a distinct versioned local envelope and error/replay model.

#### Solution

Add `proto/iop/agent.proto` with explicit envelope kind/version, typed request/response/event/error payloads, stable operations, command id, correlation id, state revision, event sequence, replay floor, and snapshot-required metadata. Reserve unused fields and keep credentials/raw paths out.

```proto
message AgentLocalEnvelope {
  uint32 protocol_version = 1;
  AgentLocalKind kind = 2;
  string message_id = 3;
  string correlation_id = 4;
  uint64 event_sequence = 5;
  string operation = 6;
  oneof payload { /* typed messages */ }
}
```

#### Modified Files and Checklist

- [ ] `proto/iop/agent.proto` — add the canonical protocol.
- [ ] `proto/gen/iop/agent.pb.go` — regenerate via `make proto`, never edit directly.
- [ ] `Makefile` — include `agent.proto` in Go protobuf generation.

#### Test Strategy

Protocol validation tests live in API-3 and cover version, kind/payload pairing, required IDs, unsupported fields/operations, and size limits.

#### Verification

```bash
make proto
cp proto/gen/iop/agent.pb.go /tmp/iop-agent.pb.go
make proto
cmp -s proto/gen/iop/agent.pb.go /tmp/iop-agent.pb.go
```

Expected: the second generation is byte-for-byte deterministic.

### [API-2] Enforce the Same-User Unix Socket Boundary

#### Problem

Existing `apps/control-plane/internal/wire/client.go:17-78` binds WebSocket/TCP and origin checks; it cannot prove a local OS user. The standalone contract requires same-effective-UID authorization and no token fallback.

#### Solution

Create a Unix listener under the configured state root, require owner-only parent and socket modes, reject symlink/non-socket replacement, and wrap accepted `net.Conn` with proto-socket communicator framing. Read kernel peer credentials using `SO_PEERCRED` on Linux and `getpeereid` on Darwin in build-tagged files. Unsupported platforms fail before listening. Authorize UID before constructing/dispatching a protocol session.

#### Modified Files and Checklist

- [ ] `apps/agent/internal/localcontrol/server.go` — own listener/session lifecycle.
- [ ] `apps/agent/internal/localcontrol/peercred.go` — define credential seam.
- [ ] `apps/agent/internal/localcontrol/peercred_linux.go` — implement Linux peer UID.
- [ ] `apps/agent/internal/localcontrol/peercred_darwin.go` — implement Darwin peer UID.
- [ ] `apps/agent/internal/localcontrol/peercred_unsupported.go` — fail closed elsewhere.
- [ ] `apps/agent/internal/localcontrol/server_test.go` — verify permissions, same-user session, denied UID, and replacement attacks.

#### Test Strategy

Use a real Linux Unix connection for same-user evidence and injected peer credentials for exact UID mismatch/zero-dispatch evidence. Cross-build Darwin to verify build tags and API shape.

#### Verification

```bash
go test -count=1 ./apps/agent/internal/localcontrol -run 'TestServer|TestPeer'
GOOS=darwin GOARCH=arm64 go test -c -o /tmp/localcontrol-darwin.test ./apps/agent/internal/localcontrol
```

Expected: Linux tests pass and Darwin test binary cross-compiles.

### [API-3] Implement Idempotency, Replay, and Safe Failures

#### Problem

`packages/go/agentstate/store.go:50-136` offers opaque CAS records, but no local-control ledger prevents duplicate mutations or restores event replay after daemon restart.

#### Solution

Persist a versioned local-control ledger in an `agentstate.Store` integration record. Hash operation plus canonical immutable arguments under each command ID; return the accepted response on identical replay and `command_id_conflict` on mismatch. Assign monotonic event sequences, retain a bounded window, return `replay_unavailable` with snapshot-required metadata for gaps, and keep safe error messages free of paths/credentials/output.

#### Modified Files and Checklist

- [ ] `apps/agent/internal/localcontrol/protocol.go` — validate/decode/encode frames and safe errors.
- [ ] `apps/agent/internal/localcontrol/ledger.go` — persist idempotency and replay state.
- [ ] `apps/agent/internal/localcontrol/protocol_test.go` — protocol/error matrix.
- [ ] `apps/agent/internal/localcontrol/ledger_test.go` — duplicate/conflict/restart/replay matrix.

#### Test Strategy

Write `TestProtocolValidationMatrix`, `TestCommandIdempotencySurvivesRestart`, `TestCommandIDConflictHasZeroMutation`, and `TestReplayGapRequiresSnapshot`.

#### Verification

```bash
go test -count=1 ./apps/agent/internal/localcontrol -run 'TestProtocol|TestCommand|TestReplay'
```

Expected: every malformed/unauthorized/conflicting frame has zero mutations and stable typed errors.

### [API-4] Dispatch Through Narrow Host Ports

#### Problem

The contract operation matrix at `agent-contract/inner/iop-agent-cli-runtime.md:103-112` requires coherent reads and delegated project mutations, but a transport must not become a second lifecycle owner.

#### Solution

Define a `Service` with read-only snapshot methods and project start/stop/resume methods implemented by host adapters. Validate authorization and idempotency before calling it. Emit accepted mutation events only after durable command acceptance; expose current snapshot revision and replay cursor.

#### Modified Files and Checklist

- [ ] `apps/agent/internal/localcontrol/service.go` — define operation dispatch and host ports.
- [ ] `apps/agent/internal/localcontrol/service_test.go` — test every operation and zero-call rejection.
- [ ] `apps/agent/internal/localcontrol/server.go` — connect authorized sessions to the service.

#### Test Strategy

Write a recording fake and cover all read/project operations, unsupported client operations, repeated requests, cancellation, and concurrent subscribers.

#### Verification

```bash
go test -count=1 -race ./apps/agent/internal/localcontrol -run 'TestService|TestConcurrent'
```

Expected: operations delegate exactly once and concurrent subscribers are race-free.

### [API-5] Record Concrete S11 Sources

#### Problem

`agent-contract/inner/iop-agent-cli-runtime.md:13,30,41,143` intentionally leaves the transport unspecified until implementation.

#### Solution

Replace only the speculative S11 source status with actual proto, localcontrol, peer credential, and focused test paths. Record Unix proto-socket selection and supported OS behavior without copying protobuf definitions.

#### Modified Files and Checklist

- [ ] `agent-contract/inner/iop-agent-cli-runtime.md` — add concrete S11 implementation/evidence links.

#### Test Strategy

No new file; the S11 matrix above supplies evidence.

#### Verification

```bash
rg -n --sort path 'S11|agent.proto|localcontrol|same OS user|SO_PEERCRED|getpeereid' agent-contract/inner/iop-agent-cli-runtime.md apps/agent/internal/localcontrol proto/iop/agent.proto
```

Expected: actual sources and both supported authorization implementations are present.

## Dependencies and Execution Order

Predecessor `16+13,15_bootstrap_composition` must have a same-group active or archived `complete.log`; it is currently missing. Complete API-1 before protocol code, API-2 before accepting sessions, then API-3/API-4, followed by API-5 and final verification.

## Modified Files Summary

| File | Item |
|------|------|
| `proto/iop/agent.proto` | API-1 |
| `proto/gen/iop/agent.pb.go` | API-1 |
| `Makefile` | API-1 |
| `apps/agent/internal/localcontrol/server.go` | API-2, API-4 |
| `apps/agent/internal/localcontrol/peercred.go` | API-2 |
| `apps/agent/internal/localcontrol/peercred_linux.go` | API-2 |
| `apps/agent/internal/localcontrol/peercred_darwin.go` | API-2 |
| `apps/agent/internal/localcontrol/peercred_unsupported.go` | API-2 |
| `apps/agent/internal/localcontrol/server_test.go` | API-2 |
| `apps/agent/internal/localcontrol/protocol.go` | API-3 |
| `apps/agent/internal/localcontrol/ledger.go` | API-3 |
| `apps/agent/internal/localcontrol/protocol_test.go` | API-3 |
| `apps/agent/internal/localcontrol/ledger_test.go` | API-3 |
| `apps/agent/internal/localcontrol/service.go` | API-4 |
| `apps/agent/internal/localcontrol/service_test.go` | API-4 |
| `agent-contract/inner/iop-agent-cli-runtime.md` | API-5 |

## Final Verification

```bash
gofmt -w apps/agent/internal/localcontrol/*.go
make proto
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
GOOS=darwin GOARCH=arm64 go test -c -o /tmp/localcontrol-darwin.test ./apps/agent/internal/localcontrol
git diff --check
```

Expected: generated sources are stable, Linux security/restart/race tests pass, Darwin cross-build passes, and every rejected request has zero mutation. After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.

<!-- Archived after review cycle 0. -->
