# Control Plane-Edge Wire Contract

## 계약 메타

- id: `iop.control-plane-edge-wire`
- boundary: `inner`
- status: active-mvp
- 원본 경로:
  - `proto/iop/control.proto`
  - `apps/control-plane/internal/wire/wire.go`
  - `apps/control-plane/internal/wire/edge.go`
  - `apps/control-plane/internal/wire/edge_server.go`
  - `apps/control-plane/internal/credentialstore/projection.go`
  - `apps/control-plane/internal/credentiallease/service.go`
  - `apps/edge/internal/authprojection/cache.go`
  - `apps/edge/internal/controlplane/connector.go`
- human docs: `apps/control-plane/README.md`

## 읽는 조건

- Control Plane-Edge TCP proto-socket endpoint를 바꿀 때
- `EdgeHello*`, `EdgeStatus*`, `EdgeCommand*`, `EdgeNodeSnapshot`, `EdgeNodeEvent`를 바꿀 때
- `PrincipalProjection*`, `AcquireLease*`, the Edge projection cache, or credential binding fences change
- Edge status/command/event relay가 Edge-owned node registry나 runtime state를 어떻게 노출하는지 검토할 때

## 범위

이 계약은 Edge가 Control Plane으로 outbound 연결하는 내부 운영 wire다.
Control Plane은 Edge 연결 view와 제어 요청/결과를 관리하고, Edge는 자신의 Node registry와 runtime 상태를 소유한 채 snapshot과 event를 보고한다.

## 주요 흐름

- hello: Edge가 `EdgeHelloRequest`를 보내고 Control Plane이 `EdgeHelloResponse`로 enrollment를 승인 또는 거부한다.
- status: Control Plane이 `EdgeStatusRequest`를 보내고 Edge가 `EdgeStatusResponse`로 Edge-owned snapshot을 반환한다.
- command: Control Plane이 `EdgeCommandRequest`를 보내고 Edge가 `EdgeCommandResponse`와 `EdgeCommandEvent`로 처리 결과와 phase를 보고한다.
- lifecycle event: Edge는 `EdgeNodeEvent`로 node/edge lifecycle event를 relay한다.

## Managed principal projection and credential lease

When `credential_plane.enabled=true`, this wire is an authenticated and confidential mTLS channel. The Control Plane server validates the Edge certificate workload identity (`role=edge`, `name=edge_id`), and the Edge connector validates the Control Plane role/name plus its configured server name. Managed startup is rejected unless this TLS boundary is configured.

- Hello: the Control Plane builds the current secret-free `PrincipalProjection` and includes it in the accepted `EdgeHelloResponse`. A managed Edge rejects an accepted hello that omits the projection.
- Refresh: after a durable principal, slot, or route mutation, the Control Plane sends `PrincipalProjectionApplyRequest`; the Edge atomically accepts only a fresh valid generation and returns `PrincipalProjectionApplyResponse`.
- Authentication: the projection carries active token digests and safe principal/route/slot/revision metadata. It never carries raw IOP tokens, provider plaintext, ciphertext, keys, authorization headers, or lease material.
- Lease acquisition: after request authentication, projected route resolution, and provider-pool admission, the Edge sends `AcquireLeaseRequest` over this same authenticated connection. The binding includes exact principal, route, slot, profile, upstream target, Node recipient, credential revision, route revision, and projection generation.
- Fencing: the Edge validates the binding against its immutable request projection before acquisition and again immediately before Node dispatch. The Control Plane re-reads durable generation, route, and slot state before decrypting at-rest material. Any stale, revoked, disabled, mismatched, or cross-principal binding fails closed.
- Delivery: the Control Plane returns a short-lived signed lease sealed to the selected Node recipient public key. Lease cache limits and TTL are bounded; a rejected request receives no credential plaintext.

When managed mode is disabled, projection and lease operations are not installed and the existing status/command/event connector may operate according to legacy configuration. Legacy mode does not authorize credential-plane traffic.

## 필드 의미

- `EdgeHelloRequest.edge_id`: Control Plane connection registry의 Edge identity다. 비어 있으면 hello는 거부된다.
- `EdgeStatusResponse.nodes`: Edge가 소유한 node snapshot view다.
- `EdgeNodeSnapshot.connected`: accepted registration 여부가 아니라 Edge registry의 current dispatch-ready ownership을 뜻한다. configured Node가 initial connect 전이거나 disconnect/pending 상태여도 snapshot에서 사라지지 않고 `connected=false`로 남는다.
- `EdgeNodeSnapshot.config`: Node에 내려간 config payload의 관찰용 요약이다.
- `EdgeNodeSnapshot.provider_snapshots`: runtime `ProviderSnapshot` wire name을 재사용한 Node resource/provider snapshot이다. `category`가 API/local inference resource kind를 구분하며, Node address, token, transport internals는 싣지 않는다. online provider의 일반·long in-flight는 Edge provider lease state와 같고 queued 값은 Edge queue의 candidate pressure다. configured offline provider는 catalog identity를 유지한 채 `status=unavailable`, `health=offline`, capacity/in-flight/queued/long-context 관련 수치를 0으로 보고한다. reconnect ready 뒤에는 configured capacity와 admission eligibility가 함께 복구된다.
- `EdgeNodeEvent`: current owner의 authoritative ready/disconnect 전이가 완료된 뒤 관측용으로 relay된다. rejected duplicate나 stale connection close는 live Node의 disconnect event를 만들지 않으며, provider cleanup correctness는 event delivery 성공에 의존하지 않는다.
- `EdgeCommandRequest.operation`: Edge-owned operation 이름이다. Node 직접 scheduling 명령으로 사용하지 않는다.
- `EdgeCommandRequest.target_selector`: Edge 내부 operation이 해석할 대상 selector다. Node address나 token을 외부화하지 않는다.
- `metadata`: 필요한 Edge identity 또는 운영 보조 정보만 담는다. secret과 private endpoint 원문은 tracked 계약에 쓰지 않는다.
- `PrincipalProjection.generation`: durable Control Plane mutation generation;
  Edge accepts only a strictly higher fresh generation after managed mode begins.
- `PrincipalProjection.tokens`: active SHA-256 verifier digests and safe
  principal/token references only. Raw IOP tokens are forbidden.
- `PrincipalProjection.routes`: active principal route, credential slot/profile,
  upstream model, selector, and safe revision metadata only. Ciphertext,
  plaintext provider credentials, keys, and authorization headers are forbidden.
- `AcquireLeaseRequest.binding`: immutable dispatch scope; every identity, target, and revision field must exactly match the current projection and durable records.
- `AcquireLeaseRequest.recipient_public_key`: selected Node X25519 public key learned through its authenticated Edge-Node registration; it is not persisted as a provider credential.
- `AcquireLeaseResponse.lease`: signed, recipient-sealed envelope. Only the selected Node can open it, and only for the exact binding and validity interval.

## 금지 사항

- Control Plane에서 Node를 직접 연결하거나 직접 스케줄링하는 계약을 만들지 않는다.
- `ScheduleRequest`/`ScheduleResponse` legacy tombstone을 활성 계약으로 되살리지 않는다.
- Edge가 보고하지 않은 Node address, token, transport internals를 status/command 응답에 넣지 않는다.
- Control Plane을 Edge 설정, Node registry, runtime/automation 상태의 원본 저장소로 만들지 않는다.
- gRPC를 기본 Control Plane-Edge wire로 도입하지 않는다.
- Do not enable managed projection or lease traffic on an unauthenticated connector, accept an Edge hello identity that disagrees with the certificate, or acquire a lease on a secondary/plaintext channel.
- Do not use projection refresh acknowledgement as a substitute for the Edge pre-lease/pre-send binding fence or the Control Plane durable re-read.

## 변경 시 확인할 코드/테스트

- `proto/iop/control.proto`
- `apps/control-plane/internal/wire/edge_server_test.go`
- `apps/control-plane/internal/wire/edge_test.go`
- `apps/edge/internal/controlplane/connector_test.go`
- `apps/edge/internal/controlplane/heartbeat_test.go`
- `apps/control-plane/internal/credentialstore/projection_test.go`
- `apps/edge/internal/authprojection/cache_test.go`
- `apps/control-plane/internal/credentiallease/service_test.go`
- `apps/control-plane/cmd/control-plane/secure_delivery_integration_test.go`
- proto 변경 시 `make proto`
