<!-- task=m-multi-edge-operations/02+01_edge_fleet_ops plan=1 tag=REVIEW_API -->

# Plan - REVIEW_API Edge Fleet Ops Follow-up

## 이 파일을 읽는 구현 에이전트에게

`CODE_REVIEW-cloud-G06.md`의 구현 에이전트 소유 섹션을 채우는 것이 구현의 마지막 단계다. 코드 변경, 테스트 실행, 실제 stdout/stderr 기록을 끝낸 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review 전용이다.

구현 중 사용자만 결정할 수 있는 scope 변경, 사용자 소유 외부 환경/secret, 또는 자동 후속으로 해결할 수 없는 차단이 생기면 `CODE_REVIEW-cloud-G06.md`의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 채우고 중단한다. 명령 재실행이나 증거 수집으로 닫을 수 있는 공백은 사용자 리뷰 요청이 아니라 구현/검증 항목으로 처리한다.

## 배경

이전 구현은 native fleet command 표면을 추가했지만 `agent.command`가 실제 node/service command를 실행하지 않고 성공을 시뮬레이션한다. 또한 OTO/build-deploy 상태가 lifecycle과 무관하게 `ready`로 표시되고, Control Plane status에 full `NodeConfigPayload`가 남아 summary-only 및 config 비노출 요구와 충돌한다. 이 follow-up은 기존 Edge-owned service 경계를 유지하면서 해당 Required 결함만 닫는다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. code-review가 그 요청을 검증하고 필요할 때만 `USER_REVIEW.md`를 작성한다.

## 분석 결과

### 읽은 파일

- `agent-task/m-multi-edge-operations/02+01_edge_fleet_ops/plan_cloud_G05_0.log`
- `agent-task/m-multi-edge-operations/02+01_edge_fleet_ops/code_review_cloud_G05_0.log`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/edge-smoke.md`
- `apps/edge/internal/service/service.go`
- `apps/edge/internal/service/service_test.go`
- `apps/edge/internal/controlplane/connector.go`
- `apps/edge/internal/controlplane/connector_test.go`
- `apps/edge/internal/node/mapper.go`
- `apps/edge/internal/node/registry.go`
- `proto/iop/control.proto`
- `proto/iop/runtime.proto`

### 테스트 환경 규칙

- test_env: `local`
- `agent-test/local/rules.md` present/read: yes
- matched profile: `agent-test/local/edge-smoke.md`
- applied commands: `go test -count=1 ./apps/edge/internal/service ./apps/edge/internal/controlplane`, `go test -count=1 ./apps/edge/...`
- follow-up final commands: use fresh Go tests, `git diff --check`, and Control Plane-Edge wire smoke when command/status wire behavior changes.

### 테스트 커버리지 공백

- `agent.command` real dispatch: not covered. Current `TestServiceExecuteCommand/agent.command` succeeds with a `NodeEntry` that has no client.
- busy/error summary: not covered. Current capability test uses lifecycle `deploying` but expects `ready`.
- status config non-leak: inverted. Current connector test asserts `NodeConfigPayload` is propagated.
- diff cleanliness: not covered. `git diff --check` currently fails on trailing whitespace and EOF blank lines.

### 심볼 참조

- Renamed/removed symbols: none.
- Interface impact: `StatusProvider` is implemented by `service.Service` and test fake providers in `apps/edge/internal/controlplane/connector_test.go`.

### 분할 판단

- Existing split subtask: `m-multi-edge-operations/02+01_edge_fleet_ops`.
- Predecessor `01_native_contract` is satisfied by `agent-task/archive/2026/06/m-multi-edge-operations/01_native_contract/complete.log`.
- No new split is created because all fixes are in the same Edge service/controlplane status-command boundary and share the same verification commands.

### 범위 결정 근거

- Do not implement Control Plane HTTP fleet API or client UI; those belong to dependent subtasks `03+01,02_control_plane_fleet_api` and `04+03_client_fleet_ui`.
- Do not expand OTO artifact/log storage. Status remains summary-only.
- Prefer not to change proto schema unless unavoidable. It is sufficient to stop populating sensitive config in Edge status and add regression tests; generated proto churn belongs only to an explicit contract change.
- This follow-up does not add `Roadmap Targets` because the archived parent plan did not contain a fixed Roadmap Targets section.

### 빌드 등급

- build=`cloud-G06`, review=`cloud-G06`: native wire command semantics, status/security summary, and weak existing tests require stronger review than a mechanical local cleanup.

## 구현 체크리스트

- [ ] [REVIEW_API-1] `agent.command`가 실제 Edge-local service/node command 경로를 사용하거나 명시적으로 `unsupported`를 반환하도록 수정하고, fake success 회귀 테스트를 추가한다.
- [ ] [REVIEW_API-2] OTO/build-deploy capability/domain agent summary가 lifecycle 기반 ready/busy/error 상태를 반영하도록 수정하고 테스트한다.
- [ ] [REVIEW_API-3] Control Plane status response에서 full `NodeConfigPayload`/secret-like config detail이 새지 않도록 수정하고 테스트한다.
- [ ] [REVIEW_API-4] `gofmt`/`git diff --check`/fresh Go tests를 실행하고 결과를 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_API-1] 실제 command dispatch

문제:

`apps/edge/internal/service/service.go:654`의 `agent.command` 분기는 node를 resolve한 뒤 실제 downstream command를 보내지 않고 synthetic `running/completed` event와 `completed` response를 만든다.

현재:

```go
// apps/edge/internal/service/service.go:654
case "agent.command":
    entry, err := s.ResolveNode(req.TargetSelector)
    ...
    time.Sleep(10 * time.Millisecond)
    return &iop.EdgeCommandResponse{
        Status:  "completed",
        Summary: fmt.Sprintf("Command successfully executed on agent %s", entry.Alias),
    }, nil
```

해결 방법:

`agent.command`는 `req.Parameters`의 명시 command를 기존 service command로 매핑한다. 지원 범위가 아직 확정되지 않은 command는 성공 시뮬레이션 대신 `unsupported`를 반환한다.

예시 방향:

```go
// after
case "agent.command":
    switch req.GetParameters()["command"] {
    case "capabilities":
        view, err := s.Capabilities(ctx, NodeCommandRequestSpec{NodeRef: req.TargetSelector, Adapter: req.GetParameters()["adapter"], Target: req.GetParameters()["target"], SessionID: req.GetParameters()["session_id"]})
        return edgeCommandResponseFromNodeCommand(req, view, err), nil
    default:
        return unsupportedEdgeCommand(req, "unsupported agent.command command"), nil
    }
```

수정 파일 및 체크리스트:

- [ ] `apps/edge/internal/service/service.go`: synthetic success 제거.
- [ ] `apps/edge/internal/service/service.go`: downstream error를 `Status: "error"`와 `Error`로 변환.
- [ ] `apps/edge/internal/service/service_test.go`: `agent.command`가 client 없는 node에서 성공하지 않는 테스트 추가.
- [ ] `apps/edge/internal/service/service_test.go`: supported command가 실제 `NodeCommandRequest`/service method를 호출하고 node error를 전파하는 테스트 추가.

테스트 작성:

- Write `TestServiceExecuteCommandAgentCommandDoesNotFakeSuccess`.
- Write a supported-command test with a fake proto-socket node or injectable command sender if the current service cannot be tested cleanly.

중간 검증:

```bash
go test -count=1 ./apps/edge/internal/service
```

### [REVIEW_API-2] lifecycle 기반 상태 요약

문제:

`apps/edge/internal/service/service.go:550`은 OTO가 `LifecycleState: "deploying"`이어도 `build-deploy` capability를 항상 `Status: "ready"`로 반환한다. busy/error 상태를 명확히 반환하라는 계획 요구와 맞지 않는다.

현재:

```go
// apps/edge/internal/service/service.go:573
out = append(out, &iop.EdgeCapabilitySummary{
    Kind:      "build-deploy",
    Available: true,
    Status:    "ready",
    Summary:   "OTO build-deploy capability available",
})
```

해결 방법:

`NodeSnapshot.LifecycleState`를 summary status로 변환하는 작은 helper를 둔다. 최소 기준은 connected/empty = `ready`, deploying/running/busy = `busy`, error/failed = `error`, 그 밖은 `busy` 또는 명시 summary로 둔다. `EdgeDomainAgentSummary`도 같은 판단을 반영한다.

수정 파일 및 체크리스트:

- [ ] `apps/edge/internal/service/service.go`: lifecycle-to-summary helper 추가.
- [ ] `apps/edge/internal/service/service.go`: OTO capability status와 available 값을 helper 결과로 설정.
- [ ] `apps/edge/internal/service/service_test.go`: deploying/running/error 상태별 ready/busy/error 기대값 추가.
- [ ] `apps/edge/internal/service/service_test.go`: busy/error command response가 명확한지 확인하는 테스트 추가.

테스트 작성:

- Extend `TestServiceCapabilitiesAndDomainAgents` or split table tests for lifecycle states.

중간 검증:

```bash
go test -count=1 ./apps/edge/internal/service
```

### [REVIEW_API-3] status config 비노출

문제:

`apps/edge/internal/controlplane/connector.go:366`은 `EdgeNodeSnapshot.Config`에 full node config를 복사한다. `apps/edge/internal/node/mapper.go:16`와 `apps/edge/internal/node/mapper.go:69` 기준 이 payload에는 workspace root, adapter endpoint, CLI command/args/env가 포함될 수 있다.

현재:

```go
// apps/edge/internal/controlplane/connector.go:367
nodes = append(nodes, &iop.EdgeNodeSnapshot{
    NodeId:    s.NodeID,
    Alias:     s.Alias,
    Label:     s.Label,
    Connected: true,
    Config:    s.Config,
})
```

해결 방법:

Control Plane status response에는 identity, connected flag, capability/domain-agent summary만 싣는다. `Config`는 nil로 두거나 summary-safe 필드로 별도 변환한다. 기존 `TestConnectorBuildStatusResponseWithConfig`는 config propagation 기대 대신 non-leak 기대로 바꾼다.

수정 파일 및 체크리스트:

- [ ] `apps/edge/internal/controlplane/connector.go`: `EdgeNodeSnapshot.Config` population 제거 또는 sanitize.
- [ ] `apps/edge/internal/controlplane/connector_test.go`: config가 nil이거나 sanitized임을 검증.
- [ ] `apps/edge/internal/controlplane/connector_test.go`: CLI env/base URL/workspace root가 status response에 나오지 않는 회귀 fixture 추가.
- [ ] `apps/edge/internal/service/service_test.go`: 필요하면 `ListNodeSnapshotsWithConfig` 기대 범위를 Edge-internal snapshot으로만 명확히 조정.

테스트 작성:

- Replace or rename `TestConnectorBuildStatusResponseWithConfig` with a non-leak regression test.

중간 검증:

```bash
go test -count=1 ./apps/edge/internal/controlplane
```

### [REVIEW_API-4] 검증과 정리

문제:

현재 `git diff --check`가 trailing whitespace와 EOF blank line을 보고한다. 코드 변경 후 fresh tests와 diff cleanliness를 같이 확인해야 한다.

해결 방법:

Go 파일은 `gofmt`로 정리하고, proto EOF blank line과 trailing whitespace를 제거한다. 테스트는 cache 없는 명령으로 실행한다.

수정 파일 및 체크리스트:

- [ ] `apps/edge/internal/service/service.go`, `apps/edge/internal/service/service_test.go`, `apps/edge/internal/controlplane/connector.go`, `apps/edge/internal/controlplane/connector_test.go`에 `gofmt` 적용.
- [ ] `proto/iop/control.proto` EOF blank line 정리. proto schema 자체는 불필요하면 변경하지 않는다.
- [ ] `git diff --check` 통과.

테스트 작성:

- 별도 테스트 파일 추가 없음. 위 항목들의 회귀 테스트와 diff check가 검증이다.

중간 검증:

```bash
git diff --check
```

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/internal/service/service.go` | REVIEW_API-1, REVIEW_API-2, REVIEW_API-4 |
| `apps/edge/internal/service/service_test.go` | REVIEW_API-1, REVIEW_API-2, REVIEW_API-4 |
| `apps/edge/internal/controlplane/connector.go` | REVIEW_API-3, REVIEW_API-4 |
| `apps/edge/internal/controlplane/connector_test.go` | REVIEW_API-3, REVIEW_API-4 |
| `proto/iop/control.proto` | REVIEW_API-4 only if whitespace cleanup is still needed |

## 최종 검증

```bash
go test -count=1 ./apps/edge/internal/service ./apps/edge/internal/controlplane
go test -count=1 ./apps/edge/...
make test-control-plane-edge-wire
git diff --check
```

예상 결과: 모든 명령 exit code 0. `go test` cache output은 완료 증거로 쓰지 말고 `-count=1` fresh output을 기록한다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
