<!-- task=m-control-plane-edge-wire-baseline/02+01_edge_connector plan=1 tag=REVIEW_EDGE_CONN -->

# Code Review Reference - REVIEW_EDGE_CONN

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.

## 개요

date=2026-05-30
task=m-control-plane-edge-wire-baseline/02+01_edge_connector, plan=1, tag=REVIEW_EDGE_CONN

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.

1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G08.md` -> `code_review_cloud_G08_N.log`, `PLAN-cloud-G08.md` -> `plan_cloud_G08_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-control-plane-edge-wire-baseline/02+01_edge_connector/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
4. PASS이고 task group이 `m-control-plane-edge-wire-baseline`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_EDGE_CONN-1] Lost disconnect and reconnect loop fix | [x] |
| [REVIEW_EDGE_CONN-2] Meaningful reconnect test | [x] |

## 구현 체크리스트

- [x] [REVIEW_EDGE_CONN-1] Control Plane connector의 disconnect listener 등록과 close/done 처리 순서를 고쳐 hello 수락 직후 disconnect에서도 reconnect loop가 멈추지 않게 한다.
- [x] [REVIEW_EDGE_CONN-2] Reconnect regression tests를 실제 두 번째 hello/connection assertion으로 보강하고 listener race 케이스를 검증한다.
- [x] 중간/최종 검증 명령을 실행하고 출력이 기대와 일치하는지 확인한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G08_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G08_M.log`로 아카이브한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리 `agent-task/m-control-plane-edge-wire-baseline/02+01_edge_connector/`를 `agent-task/archive/YYYY/MM/m-control-plane-edge-wire-baseline/02+01_edge_connector/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-control-plane-edge-wire-baseline/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G08.md`와 `CODE_REVIEW-cloud-G08.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.

## 계획 대비 변경 사항

- **`TestConnectorStopPreventsReconnect` 추가**: PLAN에는 없었으나 Stop 이후 추가 hello가 발생하지 않는지 bounded window로 확인하는 테스트를 추가했다. 이는 PLAN의 "Stop() 후에는 reconnect loop가 추가 hello를 보내지 않는지도 짧은 bounded window로 확인한다" 요건을 직접 구현한 것이다.
- **`startFakeCPServerWithClientAccess` helper 추가**: reconnect 테스트에서 서버측 client close 타이밍을 직접 제어해야 하므로 서버측 client를 channel로 노출하는 helper를 추가했다.
- **hello 실패/거절 경로의 `<-done` 대기**: `cl.Close()` 이후 disconnect listener가 완료될 때까지 `<-done`으로 기다린다. listener가 선등록되어 있으므로 close 후 반드시 done이 닫힌다. 이로써 hello 오류 반환 전 client 상태가 완전히 정리된다.

## 주요 설계 결정

1. **disconnect listener 선등록 (핵심 수정)**: `DialTcp` 직후, hello 전송 전에 `done` 채널과 disconnect listener를 등록한다. `proto-socket`의 `notifyDisconnected()`는 disconnect 당시 등록된 listener만 호출하고 목록을 비우므로, 선등록이 없으면 hello 직후 disconnect를 놓친다.

2. **`sync.Once`로 `done` 정확히 한 번 닫기**: disconnect listener가 여러 경로에서 불릴 수 있는 상황(hello 실패 시 `cl.Close()` 호출 → listener fire → 이미 닫힌 채널 닫기 시도)을 `sync.Once`로 안전하게 처리한다.

3. **`c.client == cl` guard**: disconnect listener에서 `c.client`를 nil로 정리할 때 `c.client == cl`인 경우에만 정리한다. Stop()이 이미 `c.client = nil`로 설정한 뒤 listener가 뒤늦게 불리는 경우 다른 client를 잘못 nil로 만들지 않는다.

4. **hello 수락 후 `select <-done default` 조기 감지**: hello response를 받은 직후 done non-blocking select로 이미 disconnect된 연결을 감지한다. 이 경우 `StateConnected`로 올리지 않고 nil을 반환해 reconnect loop가 즉시 재시도한다.

5. **hello 실패/거절 경로 `<-done` 대기**: `cl.Close()` 후 `<-done`으로 listener 완료를 기다린다. 선등록 불변식에 의해 반드시 done이 닫히므로 영구 대기가 발생하지 않는다.

6. **`TestConnectorReconnectsAfterAcceptedThenClosed`**: hello 수락 직후 서버측이 연결을 닫는 race 시나리오를 직접 재현한다. 이전 구현의 버그(listener 후등록)가 있으면 connector가 `<-done`에서 영구 대기해 이 테스트가 timeout으로 실패한다.

## 사용자 리뷰 요청

- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음

## 리뷰어를 위한 체크포인트

- Disconnect listener가 `EdgeHelloRequest` 전송 전에 등록되어 accepted 직후 disconnect를 놓치지 않는지 확인한다.
- Hello 실패/거절/수락 직후 close/Stop 경로에서 `done` 대기가 영구 대기하지 않는지 확인한다.
- Reconnect tests가 두 번째 hello 또는 두 번째 connection을 직접 assert하고, state-only/sleep-only 테스트에 머무르지 않는지 확인한다.
- Stop 후에는 reconnect loop가 추가 hello를 보내지 않는지 확인한다.
- 최종 검증 출력이 실제 명령의 stdout/stderr와 일치하는지 확인한다.

## 검증 결과

필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.

### REVIEW_EDGE_CONN-1 중간 검증
```
$ go test -count=1 ./apps/edge/internal/controlplane -run 'TestConnector(ReconnectsAfterAcceptedThenClosed|ReconnectsAfterDisconnect|StopClosesConnection)' -v
=== RUN   TestConnectorStopClosesConnection
--- PASS: TestConnectorStopClosesConnection (0.00s)
=== RUN   TestConnectorReconnectsAfterDisconnect
--- PASS: TestConnectorReconnectsAfterDisconnect (1.00s)
=== RUN   TestConnectorReconnectsAfterAcceptedThenClosed
--- PASS: TestConnectorReconnectsAfterAcceptedThenClosed (1.00s)
PASS
ok      iop/apps/edge/internal/controlplane     2.008s
```

### REVIEW_EDGE_CONN-2 중간 검증
```
$ go test -count=1 ./apps/edge/internal/controlplane -v
=== RUN   TestConnectorDisabledNoOp
--- PASS: TestConnectorDisabledNoOp (0.00s)
=== RUN   TestConnectorEmptyAddrNoOp
--- PASS: TestConnectorEmptyAddrNoOp (0.00s)
=== RUN   TestConnectorSendsEdgeHello
--- PASS: TestConnectorSendsEdgeHello (0.00s)
=== RUN   TestConnectorRejectsRejectedHello
--- PASS: TestConnectorRejectsRejectedHello (0.40s)
=== RUN   TestConnectorStopClosesConnection
--- PASS: TestConnectorStopClosesConnection (0.00s)
=== RUN   TestConnectorReconnectsAfterDisconnect
--- PASS: TestConnectorReconnectsAfterDisconnect (1.00s)
=== RUN   TestConnectorReconnectsAfterAcceptedThenClosed
--- PASS: TestConnectorReconnectsAfterAcceptedThenClosed (1.00s)
=== RUN   TestConnectorStopPreventsReconnect
--- PASS: TestConnectorStopPreventsReconnect (2.00s)
=== RUN   TestHeartbeatWaitExceedsInterval
--- PASS: TestHeartbeatWaitExceedsInterval (0.00s)
PASS
ok      iop/apps/edge/internal/controlplane     4.411s
```

### 최종 검증
```
$ go test -count=1 ./apps/edge/internal/controlplane ./apps/edge/internal/bootstrap && go test -race -count=1 ./apps/edge/internal/controlplane
ok      iop/apps/edge/internal/controlplane     4.412s
ok      iop/apps/edge/internal/bootstrap        0.009s
ok      iop/apps/edge/internal/controlplane     5.426s
```

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

## 코드리뷰 결과

- 종합 판정: PASS
- 차원별 평가
  - correctness: Pass
  - completeness: Pass
  - test coverage: Pass
  - API contract: Pass
  - code quality: Pass
  - plan deviation: Pass
  - verification trust: Pass
- 발견된 문제: 없음
- 다음 단계: PASS이므로 `complete.log`를 작성하고 task directory를 archive로 이동한다.
