<!-- task=kotlin_wss_crosstest plan=0 tag=KOTLIN_WSS_CROSSTEST -->

# Code Review Reference - KOTLIN_WSS_CROSSTEST

## 개요

date=2026-04-27
task=kotlin_wss_crosstest, plan=0, tag=KOTLIN_WSS_CROSSTEST

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

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료 후 반드시 아래 순서로 아카이브하세요.

1. `CODE_REVIEW.md` → `code_review_0.log` (N = 기존 code_review_*.log 수)
2. `PLAN.md` → `plan_0.log` (M = 기존 plan_*.log 수)
3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [KOTLIN_WSS_CROSSTEST-1] kotlin_dart.kt — WSS phase 추가 | [x] |
| [KOTLIN_WSS_CROSSTEST-2] kotlin_go.kt — WSS phase 추가 | [x] |
| [KOTLIN_WSS_CROSSTEST-3] kotlin_python.kt — WSS phase 추가 | [x] |
| [KOTLIN_WSS_CROSSTEST-4] kotlin_typescript.kt — WSS phase 추가 | [x] |

## 계획 대비 변경 사항

- 계획은 `WsServer(..., sslContext = serverCtx)`가 이미 존재한다고 전제했으나, 실제 `WsServer`에는 TLS 인자가 없었다. WSS crosstest 컴파일을 위해 `kotlin/src/main/kotlin/com/tokilabs/toki_socket/WsServer.kt`에 `SSLContext?` 인자와 `DefaultSSLWebSocketServerFactory` 설정을 추가했다.
- `WsServer.start()`는 Java-WebSocket 내부 시작 실패가 `onError(null, ex)`로 전달되고 호출자에는 숨겨질 수 있어, 실제 `onStart` 또는 시작 오류를 기다리도록 보강했다. 반복 검증 중 fixed port 재사용 실패를 줄이기 위해 `setReuseAddr(true)`도 추가했다.
- 기존 `TlsWsTest`가 `server.port()`를 사용하고 있어 `WsServer.port()` 호환 메서드를 추가했다.
- TypeScript WSS는 단일 Java-WebSocket TLS 서버에서 `npx tsx` 클라이언트가 연속 접속할 때 다음 handshake가 간헐적으로 멈췄다. `kotlin_typescript.kt`의 WSS phase만 TLS TCP처럼 `send-push`와 `requests`에 서버 인스턴스를 분리했다.
- TypeScript `WsClient.close()`가 `ws.close()` 호출 후 close 완료를 기다리지 않아 WSS 재접속 안정성이 떨어졌다. `typescript/src/ws_client.ts`에서 `close/error` 이벤트 또는 1초 timeout까지 기다리도록 보강했다.

## 주요 설계 결정

- Dart/Go/Python orchestrator는 계획대로 단일 WSS 서버 인스턴스를 `send-push`와 `requests`에 재사용했다.
- TypeScript orchestrator만 클라이언트 close/handshake 특성 때문에 WSS 서버를 phase별로 분리했다. 외부 동작은 동일하게 `mode=wss`, 동일 포트 `29800`, 동일 시나리오 set을 검증한다.
- `WsServer` TLS 지원은 기존 WS 호출과 호환되도록 nullable 기본 인자(`sslContext: SSLContext? = null`)로 추가했다.

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

- **포트 충돌 없음**: 각 파일의 `WSS_PORT`가 같은 파일 내 기존 TCP/WS/TLS_TCP 포트와 겹치지 않는지 확인 (dart=29496, go=29396, python=29400, typescript=29800).
- **WsServer sslContext 전달**: `WsServer(..., sslContext = serverCtx)` 형태로 생성하는지 확인. 기존 `runWs()`의 WsServer 생성과 비교.
- **서버 인스턴스 수**: Dart/Go/Python은 `runWss()` 안에서 서버를 한 번 start하고 `runWssSendPush` → `runWssRequests` 순으로 재사용하는지 확인. TypeScript는 계획 대비 변경 사항에 기록한 이유로 WSS phase별 서버를 사용한다.
- **client runner 인수**: `runDartClient("wss", WSS_PORT, ..., certPath)` 처럼 mode=`"wss"` + certPath를 전달하는지 확인.
- **PASS 메시지 순서**: `main()`에서 `runWss()`가 `runTlsTcp()` 다음, PASS 출력 직전에 위치하는지 확인.
- **4개 파일 일관성**: `runWssSendPush` / `runWssRequests`의 검증 내용과 client runner 인수가 4개 파일에서 동일한 패턴을 따르는지 확인. TypeScript의 서버 lifetime만 예외다.

## 검증 결과

_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._

### 중간 검증 (컴파일)

```
$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle ./gradlew compileCrosstestKotlin
> Task :compileCrosstestKotlin

BUILD SUCCESSFUL in 2s
9 actionable tasks: 1 executed, 8 up-to-date
```

### 최종 검증

```
$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle ./gradlew run -PmainClass=com.tokilabs.toki_socket.crosstest.KotlinDartKt
INFO typeName kotlin=TestData
INFO typeName dart=TestData
PASS scenario=1 detail=fire-and-forget sent
SERVER_RECEIVED index=101 message=fire from dart client
PASS scenario=2 detail=received push from kotlin server
INFO typeName dart=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
INFO typeName dart=TestData
PASS scenario=1 detail=fire-and-forget sent
SERVER_RECEIVED index=101 message=fire from dart client
PASS scenario=2 detail=received push from kotlin server
INFO typeName dart=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
INFO typeName dart=TestData
PASS scenario=1 detail=fire-and-forget sent
SERVER_RECEIVED index=101 message=fire from dart client
PASS scenario=2 detail=received push from kotlin server
INFO typeName dart=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
INFO typeName dart=TestData
PASS scenario=1 detail=fire-and-forget sent
SERVER_RECEIVED index=101 message=fire from dart client
PASS scenario=2 detail=received push from kotlin server
INFO typeName dart=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
PASS all kotlin-server/dart-client crosstests passed

BUILD SUCCESSFUL in 7s

$ cd kotlin && env PATH=/config/go-sdk/go/bin:$PATH JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle GOCACHE=/tmp/go-build GOMODCACHE=/tmp/go-mod ./gradlew run -PmainClass=com.tokilabs.toki_socket.crosstest.KotlinGoKt
INFO typeName kotlin=TestData
INFO typeName go=TestData
PASS scenario=1 detail=fire-and-forget sent
SERVER_RECEIVED index=101 message=fire from go client
PASS scenario=2 detail=received push from kotlin server
INFO typeName go=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
INFO typeName go=TestData
PASS scenario=1 detail=fire-and-forget sent
SERVER_RECEIVED index=101 message=fire from go client
PASS scenario=2 detail=received push from kotlin server
INFO typeName go=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
INFO typeName go=TestData
PASS scenario=1 detail=fire-and-forget sent
SERVER_RECEIVED index=101 message=fire from go client
PASS scenario=2 detail=received push from kotlin server
INFO typeName go=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
INFO typeName go=TestData
PASS scenario=1 detail=fire-and-forget sent
SERVER_RECEIVED index=101 message=fire from go client
PASS scenario=2 detail=received push from kotlin server
INFO typeName go=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
PASS all kotlin-server/go-client crosstests passed

BUILD SUCCESSFUL in 6s

$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle ./gradlew run -PmainClass=com.tokilabs.toki_socket.crosstest.KotlinPythonKt
INFO typeName kotlin=TestData
SERVER_RECEIVED index=101 message=fire from python client
INFO typeName python=TestData
PASS scenario=1 detail=fire-and-forget sent
PASS scenario=2 detail=received push from kotlin server
INFO typeName python=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
SERVER_RECEIVED index=101 message=fire from python client
INFO typeName python=TestData
PASS scenario=1 detail=fire-and-forget sent
PASS scenario=2 detail=received push from kotlin server
INFO typeName python=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
SERVER_RECEIVED index=101 message=fire from python client
INFO typeName python=TestData
PASS scenario=1 detail=fire-and-forget sent
PASS scenario=2 detail=received push from kotlin server
INFO typeName python=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
SERVER_RECEIVED index=101 message=fire from python client
INFO typeName python=TestData
PASS scenario=1 detail=fire-and-forget sent
PASS scenario=2 detail=received push from kotlin server
INFO typeName python=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
PASS all kotlin-server/python-client crosstests passed

BUILD SUCCESSFUL in 4s

$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle ./gradlew run -PmainClass=com.tokilabs.toki_socket.crosstest.KotlinTypescriptKt
INFO typeName kotlin=TestData
INFO typeName ts=TestData
PASS scenario=1 detail=fire-and-forget sent
SERVER_RECEIVED index=101 message=fire from typescript client
PASS scenario=2 detail=received push from kotlin server
INFO typeName ts=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#noProviders for further details.
INFO typeName ts=TestData
PASS scenario=1 detail=fire-and-forget sent
SERVER_RECEIVED index=101 message=fire from typescript client
PASS scenario=2 detail=received push from kotlin server
INFO typeName ts=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
INFO typeName ts=TestData
PASS scenario=1 detail=fire-and-forget sent
SERVER_RECEIVED index=101 message=fire from typescript client
PASS scenario=2 detail=received push from kotlin server
INFO typeName ts=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
INFO typeName ts=TestData
PASS scenario=1 detail=fire-and-forget sent
SERVER_RECEIVED index=101 message=fire from typescript client
PASS scenario=2 detail=received push from kotlin server
INFO typeName ts=TestData
PASS scenario=3 detail=single request response matched
PASS scenario=4 detail=concurrent request responses matched
PASS all kotlin-server/typescript-client crosstests passed

BUILD SUCCESSFUL in 7s
```

### 추가 검증

```
$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle ./gradlew test --tests com.tokilabs.toki_socket.TlsWsTest
> Task :test

BUILD SUCCESSFUL in 9s
11 actionable tasks: 4 executed, 7 up-to-date

$ cd typescript && npm run check
> toki-socket@1.0.5 check
> tsc --noEmit
```
