<!-- task=ts_kotlin_wss_crosstest plan=0 tag=TEST -->

# TypeScript→Kotlin 크로스테스트 WSS 페이즈 추가

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

각 체크리스트 항목을 완료 후 `[x]`로 체크하고, 중간 검증과 최종 검증 명령을 실제로 실행한 뒤 출력을 `CODE_REVIEW.md`의 `검증 결과` 섹션에 붙여넣으세요. 계획과 다르게 구현한 부분이 있으면 `계획 대비 변경 사항`에 이유와 함께 기록하세요.

## 배경

크로스테스트 매트릭스 점검 결과, `typescript/crosstest/typescript_kotlin.ts`(TypeScript 서버 × Kotlin 클라이언트)에 WSS 페이즈가 누락된 것이 확인됐다. 동일 파일의 TLS/TCP 페이즈는 구현돼 있고, Kotlin 클라이언트 측(`kotlin/crosstest/typescript_kotlin_client.kt` 100-103줄)은 `wss` 모드를 이미 완전히 지원한다. 같은 매트릭스의 `typescript_dart.ts`·`typescript_go.ts`·`typescript_python.ts`는 모두 WSS 페이즈를 포함한다. TypeScript 오케스트레이터 파일 한 곳에만 추가하면 갭이 해소된다.

---

## [TEST-1] typescript_kotlin.ts에 WSS 페이즈 추가

### 문제

`typescript/crosstest/typescript_kotlin.ts`
- 29줄: `TLS_TCP_PORT = 29814` 다음에 `WSS_PORT` 상수가 없다.
- 37-48줄: `main()`이 `runTcp()`, `runWs()`, `runTls()`만 호출하고 `runWss()`를 호출하지 않는다.
- `runWss()`, `runWssSendPush()`, `runWssRequests()` 함수가 존재하지 않는다.

### 해결 방법

`typescript_dart.ts`와 `typescript_go.ts`의 WSS 패턴을 그대로 적용한다. 변경은 `typescript_kotlin.ts` 한 파일만이다.

**1) WSS_PORT 상수 추가 — 29~30줄 사이**

Before (29-30줄):
```ts
const TLS_TCP_PORT = 29814;
const WS_PATH = "/";
```

After:
```ts
const TLS_TCP_PORT = 29814;
const WSS_PORT = 29816;
const WS_PATH = "/";
```

포트 29816은 `typescript_python.ts`의 `WS_PORT`와 번호가 같지만, 두 스위트는 동시에 실행되지 않으므로 기존 크로스테스트 관행과 동일하다.

**2) main()에 runWss() 호출 추가 — 42줄 다음**

Before (37-48줄):
```ts
async function main(): Promise<void> {
  console.log(`INFO typeName ts=${TestDataSchema.typeName}`);
  try {
    await runTcp();
    await runWs();
    await runTls();
    console.log("PASS all typescript-server/kotlin-client crosstests passed");
  } catch (err) {
    console.error(`FAIL crosstest error=${errorMessage(err)}`);
    process.exitCode = 1;
  }
}
```

After:
```ts
async function main(): Promise<void> {
  console.log(`INFO typeName ts=${TestDataSchema.typeName}`);
  try {
    await runTcp();
    await runWs();
    await runTls();
    await sleep(150);
    await runWss();
    console.log("PASS all typescript-server/kotlin-client crosstests passed");
  } catch (err) {
    console.error(`FAIL crosstest error=${errorMessage(err)}`);
    process.exitCode = 1;
  }
}
```

**3) runWss(), runWssSendPush(), runWssRequests() 추가 — 214줄(runTlsTcpRequests 끝) 바로 뒤에 삽입**

```ts
async function runWss(): Promise<void> {
  await runWssSendPush();
  await sleep(150);
  await runWssRequests();
}

async function runWssSendPush(): Promise<void> {
  let resolveReceived: ((value: boolean) => void) | null = null;
  const received = new Promise<boolean>((resolve) => {
    resolveReceived = resolve;
  });
  const server = new WsServer(
    HOST,
    WSS_PORT,
    WS_PATH,
    (ws) => new WsClient(ws, 0, 0, parserMap()),
    serverTlsOptions(),
  );
  server.onClientConnected = (client) => {
    addListenerTyped(client.communicator, TestDataSchema, (data) => {
      console.log(`SERVER_RECEIVED index=${data.index} message=${data.message}`);
      const valid = data.index === 101 && data.message === "fire from kotlin client";
      if (resolveReceived !== null) {
        resolveReceived(valid);
        resolveReceived = null;
      }
      if (valid) {
        void client.communicator.send(
          create(TestDataSchema, {
            index: 200,
            message: "push from typescript server",
          }),
        );
      }
    });
  };
  await withServer(server, async () => {
    await runKotlinClient("wss", WSS_PORT, "send-push", new Set(["1", "2"]), CERT_PATH);
    const ok = await withTimeout(received, SERVER_OBSERVATION_MS, "WSS send-push server did not receive expected data");
    if (!ok) {
      throw new Error("WSS send-push server received unexpected data");
    }
  });
}

async function runWssRequests(): Promise<void> {
  const server = new WsServer(
    HOST,
    WSS_PORT,
    WS_PATH,
    (ws) => new WsClient(ws, 0, 0, parserMap()),
    serverTlsOptions(),
  );
  server.onClientConnected = (client) => {
    addRequestListenerTyped(client.communicator, TestDataSchema, (req) =>
      create(TestDataSchema, {
        index: req.index * 2,
        message: `echo: ${req.message}`,
      }),
    );
  };
  await withServer(server, () =>
    runKotlinClient("wss", WSS_PORT, "requests", new Set(["3", "4"]), CERT_PATH),
  );
}
```

### 수정 파일 및 체크리스트

- [x] `typescript/crosstest/typescript_kotlin.ts`
  - [x] 29줄 다음에 `const WSS_PORT = 29816;` 추가
  - [x] `main()` 안 `await runTls();` 다음에 `await sleep(150); await runWss();` 추가
  - [x] `runTlsTcpRequests()` 끝(214줄) 다음에 `runWss()`, `runWssSendPush()`, `runWssRequests()` 삽입

### 테스트 작성

별도 테스트 파일을 작성하지 않는다. 이번 변경 자체가 크로스테스트 케이스 추가이며, `runWssSendPush()`와 `runWssRequests()`가 시나리오 1-4를 WSS 전송으로 검증한다.

### 중간 검증

TypeScript 타입 검사만으로 컴파일 오류 없음을 확인한다:

```bash
cd typescript && npx tsc --noEmit
```

기대 결과: 출력 없이 종료 코드 0.

---

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `typescript/crosstest/typescript_kotlin.ts` | TEST-1 |

---

## 최종 검증

Kotlin 빌드 환경(JDK + Gradle)이 필요하다. 환경이 없으면 타입 검사(`tsc --noEmit`)만 실행하고 검증 결과에 환경 미비 사유를 명시한다.

```bash
cd typescript && npx tsx crosstest/typescript_kotlin.ts
```

기대 결과:
```
INFO typeName ts=TestData
PASS scenario=1 ...
PASS scenario=2 ...
PASS scenario=3 ...
PASS scenario=4 ...
... (TCP / WS / TLS / WSS 각 페이즈 반복)
PASS all typescript-server/kotlin-client crosstests passed
```
