 <!-- task=m-performance-hotspot-optimization/13_kotlin_ws_diagnostics plan=3 tag=REVIEW_REVIEW_REVIEW_TEST -->

# Plan - REVIEW_REVIEW_REVIEW_TEST

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

`CODE_REVIEW-local-G10.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수입니다. 구현 후 active 파일은 그대로 두고 리뷰 준비를 보고하세요. 사용자만 결정할 수 있는 범위 변경, 외부 환경, secret, 사용자 소유 장비가 막으면 리뷰 stub의 `사용자 리뷰 요청` 섹션에 근거를 기록하고 멈추세요. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 마세요. `USER_REVIEW.md`, 로그 아카이브, `complete.log` 작성은 code-review 전용입니다. 명령 재실행이나 산출물 수집으로 닫을 수 있는 증거 공백은 사용자 리뷰 요청이 아닙니다.

## 배경

G09 구현은 `TlsWsTest` 단독 실행을 통과했지만, review 재실행에서 `cd kotlin && ./gradlew test`가 다시 실패했다. 실패는 [TlsWsTest.kt](/config/workspace/proto-socket/kotlin/src/test/kotlin/com/tokilabs/proto_socket/TlsWsTest.kt:87)의 `dialWssWithRetry`에서 `java.io.IOException: websocket open timed out`로 나타났고, 전체 결과는 `41 tests completed, 1 failed`였다. 따라서 단독 WSS PASS만으로는 충분하지 않으며, full-suite 실행 순서에서 남는 자원, timeout cleanup, server readiness, TLS setup 중 실제 원인을 좁혀야 한다.

G09 변경은 [WsClient.kt](/config/workspace/proto-socket/kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt:237)의 open-timeout branch에서 `client.close()`만 호출한다. 동시에 `OkHttpWsConnection.close()`는 더 이상 dispatcher를 shutdown하지 않고 `onClosed`/`onFailure` callback에 cleanup을 맡긴다. `onOpen`/`onFailure`가 timeout 전에 오지 않는 케이스에서는 retry마다 OkHttp/WebSocket 자원이 남을 수 있으므로 이 경로를 우선 확인한다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록합니다. 직접 사용자 프롬프트는 금지이며, code-review가 요청 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유합니다.

## 분석 결과

### 읽은 파일

- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-task/m-performance-hotspot-optimization/13_kotlin_ws_diagnostics/plan_local_G09_2.log`
- `agent-task/m-performance-hotspot-optimization/13_kotlin_ws_diagnostics/code_review_local_G09_2.log`
- `agent-test/local/rules.md`
- `agent-test/local/kotlin-smoke.md`
- `agent-test/local/proto-socket-performance-baseline.md`
- `agent-ops/rules/project/domain/kotlin/rules.md`
- `kotlin/build.gradle.kts`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsServer.kt`
- `kotlin/src/test/kotlin/com/tokilabs/proto_socket/TlsWsTest.kt`
- `kotlin/src/test/kotlin/com/tokilabs/proto_socket/TestHelpers.kt`
- `kotlin/build/test-results/test/TEST-com.tokilabs.proto_socket.TlsWsTest.xml`

### 테스트 환경 규칙

`test_env=local`을 사용한다. Kotlin 변경이므로 `agent-test/local/kotlin-smoke.md`의 `cd kotlin && ./gradlew test`가 필수다. 이번 후속은 full-suite WSS failure를 닫는 작업이므로 `cleanTest test`로 stale test result를 배제한 뒤 일반 `./gradlew test`도 최종 확인한다.

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

새 테스트를 우선 추가하지 않는다. 기존 `TlsWsTest.testWssSendReceive`와 `testWssRequestResponse`가 WSS open/handshake/traffic regression test다. 필요한 경우에만 timeout branch cleanup이나 server readiness를 좁히는 테스트를 추가하고, 최종 PASS 기준은 기존 WSS 단독과 full Gradle test다.

### 심볼 참조

`getMetricsDiagnostics`는 G08/G09에서 `internal`로 복구되었고 이번 후속의 직접 대상이 아니다. 그래도 final verification에서 public API 노출이 재발하지 않았는지 다시 확인한다.

### 범위 결정 근거

수정 범위는 full-suite WSS open timeout 안정화와 그에 필요한 최소 lifecycle/test setup에 한정한다. Kotlin WS 성능 최적화, TCP behavior, protocol schema, roadmap 갱신은 제외한다. `java.security.egd` 또는 `NonBlockingSecureRandom` 유지 여부는 실제 full-suite PASS와 cleanup 근거로 판단한다.

### 빌드 등급

`local-G10`: 같은 WSS mandatory gate가 세 번째 review loop에서 실패했지만, 실패 조건과 파일 범위가 명확하고 로컬 명령으로 재현/검증 가능하다. loop-limit `USER_REVIEW` 기준은 아직 충족하지 않았으므로 같은 local lane에서 최대 grade로 올린다.

## 구현 체크리스트

- [ ] G09 review 실패 순서 또는 동등한 clean full-suite 재현으로 단독 PASS/전체 FAIL 차이를 식별하고 실제 stdout/stderr를 기록한다.
- [ ] WSS open timeout/failure/close 경로에서 OkHttp `WebSocket`, dispatcher, connection pool cleanup이 누락되지 않도록 repo-owned 원인을 수정한다.
- [ ] `cleanTest` 기반 `TlsWsTest` 단독과 full Gradle test를 PASS로 만든 뒤 일반 `./gradlew test`, `compileCrosstestKotlin`, focused stress, `getMetricsDiagnostics` internal boundary를 재확인한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_REVIEW_TEST-1] Fix Full-Suite WSS Open Timeout

문제: G09 review 재실행에서 focused `TlsWsTest`는 통과했지만 전체 `./gradlew test`가 `TlsWsTest.testWssSendReceive`의 `websocket open timed out`로 실패했다. [WsClient.kt](/config/workspace/proto-socket/kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt:237)의 timeout branch는 `client.close()` 이후 cleanup callback을 기다리는 구조라, callback이 오지 않는 timeout 시나리오에서 retry/full-suite 자원 잔류가 발생할 수 있다.

해결 방법:

- `./gradlew cleanTest test` 또는 필요한 focused ordering으로 full-suite-only failure를 먼저 재현하고, 실패 시 `kotlin/build/test-results/test/TEST-com.tokilabs.proto_socket.TlsWsTest.xml`의 실제 메시지를 검증 결과에 기록한다.
- timeout branch에서 `newWebSocket`으로 받은 `WebSocket`을 직접 `cancel()`하거나, `OkHttpWsConnection.close()`/전용 cleanup 함수가 timeout/failure/normal close 모두에서 idempotent하게 dispatcher shutdown과 connection pool eviction을 보장하도록 수정한다.
- `dialWssWithRetry`가 이전 실패 시도의 자원을 남기지 않고 다음 retry로 넘어가는지 확인한다.
- 원인이 server readiness 또는 TLS context setup으로 확인되면 [WsServer.kt](/config/workspace/proto-socket/kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsServer.kt), [TlsWsTest.kt](/config/workspace/proto-socket/kotlin/src/test/kotlin/com/tokilabs/proto_socket/TlsWsTest.kt), [TestHelpers.kt](/config/workspace/proto-socket/kotlin/src/test/kotlin/com/tokilabs/proto_socket/TestHelpers.kt)를 최소 수정하되, 단독 PASS만으로 종료하지 않는다.
- 임시 계측이나 debug print를 추가했다면 최종 제출 전에 제거한다.

수정 파일 및 체크리스트:

- [ ] `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt`: timeout/failure/close cleanup을 idempotent하고 누락 없이 보장한다.
- [ ] `kotlin/src/test/kotlin/com/tokilabs/proto_socket/TlsWsTest.kt`: full-suite-only 원인이 test lifecycle/readiness이면 최소 수정한다.
- [ ] `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsServer.kt`: SSL server readiness 또는 stop ordering이 원인이면 최소 수정한다.
- [ ] `kotlin/src/test/kotlin/com/tokilabs/proto_socket/TestHelpers.kt`: TLS random/context setup이 원인이면 테스트 helper 범위 안에서 최소 수정한다.
- [ ] `kotlin/build.gradle.kts`: test JVM property가 실제로 필요할 때만 유지하고, 유지 이유를 review file에 기록한다.

테스트 작성: 기존 WSS regression tests를 우선 사용한다. 새 테스트는 timeout cleanup 또는 readiness 문제를 더 작고 결정적으로 검증할 때만 추가한다.

중간 검증:

```bash
cd kotlin && ./gradlew cleanTest test --tests "com.tokilabs.proto_socket.TlsWsTest"
```

```bash
cd kotlin && ./gradlew cleanTest test
```

기대 결과: 두 명령 모두 `BUILD SUCCESSFUL`.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt` | REVIEW_REVIEW_REVIEW_TEST-1 |
| `kotlin/src/test/kotlin/com/tokilabs/proto_socket/TlsWsTest.kt` | REVIEW_REVIEW_REVIEW_TEST-1 if needed |
| `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsServer.kt` | REVIEW_REVIEW_REVIEW_TEST-1 if needed |
| `kotlin/src/test/kotlin/com/tokilabs/proto_socket/TestHelpers.kt` | REVIEW_REVIEW_REVIEW_TEST-1 if needed |
| `kotlin/build.gradle.kts` | REVIEW_REVIEW_REVIEW_TEST-1 if needed |

## 최종 검증

```bash
rg --sort path "fun getMetricsDiagnostics|getMetricsDiagnostics" kotlin
```

```bash
cd kotlin && ./gradlew cleanTest test --tests "com.tokilabs.proto_socket.TlsWsTest"
```

```bash
cd kotlin && ./gradlew cleanTest test
```

```bash
cd kotlin && ./gradlew test
```

```bash
cd kotlin && ./gradlew compileCrosstestKotlin
```

```bash
bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_stress.sh --quick --lang kotlin --transport ws --profile roundtrip,payload
```

기대 결과: `getMetricsDiagnostics`가 public API로 노출되지 않고, clean WSS 단독/full Gradle/일반 Gradle/crosstest compile은 `BUILD SUCCESSFUL`, focused stress는 Kotlin WS roundtrip/payload rows를 PASS로 기록한다.

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