<!-- task=m-multi-edge-operations/01_native_contract plan=0 tag=API -->
# Plan - Native Fleet Contract

## 작업 지시

Multi-Edge 운영의 첫 구현 단위는 native wire 계약이다. Control Plane은 Edge의 실질 데이터 원본을 소유하지 않고, Edge가 제공하는 연결/health, capability/status, command 결과, event relay만 다루도록 proto와 wire parser 계약을 확장한다.

## Roadmap Targets

- `agent-roadmap/phase/control-plane-portal-ops/milestones/multi-edge-operations.md` `[native-fleet]`
- `agent-roadmap/phase/control-plane-portal-ops/milestones/multi-edge-operations.md` `[edge-ownership]`
- 완료 방식: 이 plan의 검증이 통과하면 위 target 중 "계약/소유권 가드" 범위의 완료 근거를 milestone에 추가한다. 전체 UI/API 완료는 후속 plan에서 닫는다.

## 분석 결과

현재 native contract는 hello/status/node snapshot까지만 있다.

```proto
// proto/iop/control.proto:67
message EdgeStatusRequest {
  string request_id = 1;
}

// proto/iop/control.proto:85
message EdgeStatusResponse {
  string request_id = 1;
  string edge_id = 2;
  string status = 3;
  repeated EdgeNodeSnapshot nodes = 4;
}
```

Control Plane parser도 현재 hello/status/node event만 받는다.

```go
// apps/control-plane/internal/wire/edge.go:12
func EdgeParserMap() iopwire.ParserMap {
	return iopwire.ParserMap{
		iopcontrolv1.MessageType_MESSAGE_TYPE_EDGE_HELLO_REQUEST: func(payload []byte) (proto.Message, error) {
```

Edge connector 역시 status 요청만 처리하고, capability는 hello의 문자열 목록으로만 표현한다.

```go
// apps/edge/internal/controlplane/connector.go:41
type StatusProvider interface {
	ListNodeSnapshots() []service.NodeSnapshot
}

// apps/edge/internal/controlplane/connector.go:237
func (c *Connector) sendHello(ctx context.Context, client iopwire.Client) error {
```

## 구현 체크리스트

1. `proto/iop/control.proto`에 fleet command/control과 domain capability/status summary 메시지를 추가한다.
2. 메시지는 Edge source-of-truth 철학을 드러내야 한다. Control Plane이 Edge 설정/Node registry/runtime state 원본을 저장하는 필드는 추가하지 않는다.
3. `EdgeStatusResponse`에 Edge가 제공하는 capability/status summary를 포함한다.
4. command 요청/응답/이벤트 relay 메시지를 추가하되, artifact/log/detail store 원본을 Control Plane으로 끌어오지 않는다.
5. `apps/control-plane/internal/wire/edge.go`와 `apps/edge/internal/controlplane/connector.go` parser map을 새 메시지에 맞춰 갱신한다.
6. proto 생성물을 갱신한다. Go/Dart 생성물이 있는 경우 기존 생성 방식만 사용한다.
7. 소유권 회귀 테스트를 추가한다. Control Plane contract에 Edge config 원본 저장/Node registry 원본 저장/agent lifecycle detail 원본 저장 필드가 생기면 실패해야 한다.

## 세부 설계

### Fleet Command 계약

- `EdgeCommandRequest`: `request_id`, `command_id`, `target_selector`, `operation`, `parameters`
- `EdgeCommandResponse`: `request_id`, `command_id`, `edge_id`, `status`, `summary`, `error`
- `EdgeCommandEvent`: `command_id`, `edge_id`, `phase`, `summary`, `occurred_at`

`parameters`는 command-specific 원본이 아니라 요청 payload이다. 저장 책임은 Control Plane이 아니라 요청 audit 범위로 제한한다.

### Domain Agent Summary 계약

- `EdgeCapabilitySummary`: `kind`, `available`, `status`, `summary`
- `EdgeDomainAgentSummary`: `agent_kind`, `available`, `lifecycle_state`, `active_command_id`, `summary`

OTO/build-deploy는 여기에서 `agent_kind`와 상태 요약으로만 드러난다. artifact, build logs, deploy history 원본은 Edge 또는 domain agent 쪽에 남긴다.

### Ownership Guard

테스트는 proto 텍스트 또는 descriptor를 읽어 다음 계열의 필드명이 Control Plane 소유 모델로 들어오지 않는지 확인한다.

- `edge_config_source`, `node_registry_source`, `artifact_store`, `build_log_store`
- Control Plane registry struct가 Edge 설정 원본 map을 갖지 않는지 확인한다.

## 검증

- `make proto`
- `make proto-dart`
- `go test ./apps/control-plane/internal/wire ./apps/edge/internal/controlplane`
- proto 생성물이 Dart client와 맞물리는 경우 `cd apps/client && flutter test`

## 완료 조건

- 새 native 메시지가 Go/Dart 생성물과 parser map에 반영된다.
- Control Plane이 Edge 실질 데이터 원본을 소유하지 않는다는 가드 테스트가 있다.
- 후속 plan에서 Edge 구현과 Control Plane API가 참조할 수 있는 command/status/capability contract가 안정적으로 존재한다.
