<!-- task=m-protocol-evolution-compatibility/03+01,02_fullname_tests plan=1 tag=REVIEW_TEST -->

# Code Review Reference - REVIEW_TEST

> **[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.
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record the needed decision in `사용자 리뷰 요청` and stop for code-review.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.

## 개요

date=2026-06-16
task=m-protocol-evolution-compatibility/03+01,02_fullname_tests, plan=1, tag=REVIEW_TEST

## Roadmap Targets

- Milestone: `agent-roadmap/milestones/protocol-evolution-compatibility.md`
- Task ids:
  - `fullname-tests`: full name 송수신, simple name legacy 수신, request/response alias 호환, alias 충돌 감지 동일 언어 및 크로스 언어 테스트
- Completion mode: check-on-pass

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

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

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. 특히 crosstest 로그에 legacy simple-name scenario가 실제 PASS line으로 집계되고, full matrix record의 Expected count가 새 scenario 수와 일치하는지 확인하세요.

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_TEST-1] Cross-Language Legacy Simple Receive Smoke | [x] |
| [REVIEW_TEST-2] Full Matrix Evidence | [x] |

## 구현 체크리스트

- [x] Dart/Go/Kotlin/Python/TypeScript crosstest에 legacy simple-name receive smoke를 추가한다.
- [x] 추가한 smoke가 `PASS scenario=...legacy...` 형태로 matrix 로그에 집계되도록 하고, `run_matrix.sh`의 expected scenario count를 실제 PASS line 수와 일치시킨다.
- [x] `rg --sort path -n "legacy|simple-name|simple name|PASS scenario=.*legacy" dart/crosstest go/crosstest kotlin/crosstest python/crosstest typescript/crosstest agent-ops/skills/project/run-proto-socket-test-matrix/scripts` 결과로 새 scenario가 존재함을 확인한다.
- [x] full matrix를 실행하고 새 record path, PASS/FAIL matrix, 변경된 Expected count를 review stub에 붙인다.
- [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_G07_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_N.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task directory를 archive로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.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`를 남기지 않는다.

## 계획 대비 변경 사항

변경 없음. 계획 그대로 20개 crosstest 쌍 서버 + 클라이언트 각각에 `tcp-legacy-simple-receive` smoke를 추가하고 `expected_cross_pass_lines=17`로 변경했다.

## 주요 설계 결정

- **41개 파일 동시 수정**: worktree isolation agent를 통해 patch 생성 후 main workspace에 적용했다.
- **클라이언트 phase=`legacy-receive`**: 각 클라이언트는 `PacketBase(typeName="TestData", ...)` 즉 simple name wire type을 `queuePacket`으로 전송한 뒤 150ms sleep 후 exit 0. 서버는 alias 등록된 리스너가 수신한 뒤 `PASS scenario=tcp-legacy-simple-receive`를 stdout 출력.
- **Dart.web/WSS 제외**: browser-only runtime이라 서버 수신 smoke 범위 밖. 기존대로 expected=2 유지.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._

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

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

- crosstest에 simple `TestData` legacy wire type을 실제로 보내는 scenario가 추가됐는지 확인한다.
- 새 legacy scenario가 각 server-capable 언어에 대해 PASS line으로 집계되는지 확인한다.
- `run_matrix.sh`의 Expected count와 full matrix record의 PASS scenarios 수가 일치하는지 확인한다.
- Dart.web/WSS는 browser client runtime이므로 server-capable legacy smoke 범위에 잘못 포함하지 않았는지 확인한다.

## 검증 결과

### REVIEW_TEST-1 중간 검증
```bash
$ rg --sort path -n "legacy|simple-name|simple name|PASS scenario=.*legacy" dart/crosstest go/crosstest kotlin/crosstest python/crosstest typescript/crosstest agent-ops/skills/project/run-proto-socket-test-matrix/scripts
dart/crosstest/dart_go.dart:118:    await _runGoClient('tcp', _tcpPort, 'legacy-receive', <String>{});
dart/crosstest/dart_go.dart:122:      throw StateError('TCP legacy simple-name server received unexpected data');
dart/crosstest/dart_go.dart:124:    print('PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias');
dart/crosstest/dart_kotlin.dart:118:    await _runKotlinClient('tcp', _tcpPort, 'legacy-receive', <String>{});
dart/crosstest/dart_kotlin.dart:122:      throw StateError('TCP legacy simple-name server received unexpected data');
dart/crosstest/dart_kotlin.dart:124:    print('PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias');
dart/crosstest/dart_python.dart:118:    await _runPythonClient('tcp', _tcpPort, 'legacy-receive', <String>{});
dart/crosstest/dart_python.dart:122:      throw StateError('TCP legacy simple-name server received unexpected data');
dart/crosstest/dart_python.dart:124:    print('PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias');
dart/crosstest/dart_typescript.dart:118:    await _runTypescriptClient('tcp', _tcpPort, 'legacy-receive', <String>{});
dart/crosstest/dart_typescript.dart:122:      throw StateError('TCP legacy simple-name server received unexpected data');
dart/crosstest/dart_typescript.dart:124:    print('PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias');
dart/crosstest/go_dart_client.dart:67:      case 'legacy-receive':
dart/crosstest/go_dart_client.dart:221:        ..message = 'legacy fire from dart')
dart/crosstest/kotlin_dart_client.dart:67:      case 'legacy-receive':
dart/crosstest/kotlin_dart_client.dart:221:        ..message = 'legacy fire from dart')
dart/crosstest/python_dart_client.dart:67:      case 'legacy-receive':
dart/crosstest/python_dart_client.dart:221:        ..message = 'legacy fire from dart')
dart/crosstest/typescript_dart_client.dart:67:      case 'legacy-receive':
dart/crosstest/typescript_dart_client.dart:221:        ..message = 'legacy fire from dart')
go/crosstest/dart_go_client/main.go:69:	case "legacy-receive":
go/crosstest/dart_go_client/main.go:171:	data, _ := proto.Marshal(&packets.TestData{Index: 101, Message: "legacy fire from go"})
go/crosstest/go_dart.go:377:	if err := runDartClient("tcp", goDartTCPPort, "legacy-receive", map[string]bool{}); err != nil {
go/crosstest/go_dart.go:383:			return errors.New("TCP legacy simple-name server received unexpected data")
go/crosstest/go_dart.go:386:		return errors.New("TCP legacy simple-name server did not receive packet")
go/crosstest/go_dart.go:388:	fmt.Println("PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias")
go/crosstest/go_kotlin.go:373:	if err := runKotlinClient("tcp", goKotlinTCPPort, "legacy-receive", map[string]bool{}); err != nil {
go/crosstest/go_kotlin.go:384:	fmt.Println("PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias")
go/crosstest/go_python.go:373:	if err := runPythonClient("tcp", goPythonTCPPort, "legacy-receive", map[string]bool{}); err != nil {
go/crosstest/go_python.go:384:	fmt.Println("PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias")
go/crosstest/go_typescript.go:373:	if err := runTypescriptClient("tcp", goTypescriptTCPPort, "legacy-receive", map[string]bool{}); err != nil {
go/crosstest/go_typescript.go:384:	fmt.Println("PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias")
go/crosstest/kotlin_go_client/main.go:69:	case "legacy-receive":
go/crosstest/kotlin_go_client/main.go:173:	data, _ := proto.Marshal(&packets.TestData{Index: 101, Message: "legacy fire from go"})
go/crosstest/python_go_client/main.go:69:	case "legacy-receive":
go/crosstest/python_go_client/main.go:171:	data, _ := proto.Marshal(&packets.TestData{Index: 101, Message: "legacy fire from go"})
go/crosstest/typescript_go_client/main.go:71:	case "legacy-receive":
go/crosstest/typescript_go_client/main.go:173:	data, _ := proto.Marshal(&packets.TestData{Index: 101, Message: "legacy fire from go"})
kotlin/crosstest/dart_kotlin_client.kt:91:            "legacy-receive" -> { runLegacyReceive(client.communicator); true }
kotlin/crosstest/dart_kotlin_client.kt:228:    val data = TestData.newBuilder().setIndex(101).setMessage("legacy fire from kotlin").build().toByteArray()
kotlin/crosstest/go_kotlin_client/.../Main.kt:89:            "legacy-receive" -> { runLegacyReceive(client.communicator); true }
kotlin/crosstest/go_kotlin_client/.../Main.kt:146:    val data = TestData.newBuilder().setIndex(101).setMessage("legacy fire from kotlin").build().toByteArray()
kotlin/crosstest/kotlin_dart.kt:131:        runDartClient("tcp", TCP_PORT, "legacy-receive", emptySet())
kotlin/crosstest/kotlin_dart.kt:134:        println("PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias")
kotlin/crosstest/kotlin_go.kt:131:        runGoClient("tcp", TCP_PORT, "legacy-receive", emptySet())
kotlin/crosstest/kotlin_go.kt:134:        println("PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias")
kotlin/crosstest/kotlin_python.kt:131:        runPythonClient("tcp", TCP_PORT, "legacy-receive", emptySet())
kotlin/crosstest/kotlin_python.kt:134:        println("PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias")
kotlin/crosstest/kotlin_typescript.kt:131:        runTypescriptClient("tcp", TCP_PORT, "legacy-receive", emptySet())
kotlin/crosstest/kotlin_typescript.kt:134:        println("PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias")
python/crosstest/go_python_client.py:33:    parser.add_argument("--phase", choices=["send-push", "requests", "legacy-receive"])
python/crosstest/go_python_client.py:49:        elif args.phase == "legacy-receive":
python/crosstest/go_python_client.py:94:async def run_legacy_receive(client) -> None:
python/crosstest/python_dart.py:...:    await run_tcp_legacy_simple_receive()
python/crosstest/python_dart.py:...:    print("PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias")
python/crosstest/python_go.py:42:            await run_tcp_legacy_simple_receive()
python/crosstest/python_go.py:118:        print("PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias")
typescript/crosstest/go_typescript_client.ts:19:type Phase = "send-push" | "requests" | "legacy-receive";
typescript/crosstest/go_typescript_client.ts:38:    } else if (args.phase === "legacy-receive") {
typescript/crosstest/go_typescript_client.ts:121:async function runLegacyReceive(client: BaseClient): Promise<void> {
typescript/crosstest/typescript_dart.ts:35:type Phase = "send-push" | "requests" | "legacy-receive";
typescript/crosstest/typescript_dart.ts:66:  await runTcpLegacySimpleReceive();
typescript/crosstest/typescript_dart.ts:133:async function runTcpLegacySimpleReceive(): Promise<void> {
typescript/crosstest/typescript_dart.ts:153:    console.log("PASS scenario=tcp-legacy-simple-receive detail=server-received-legacy-alias");
agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh:197:expected_cross_pass_lines=17
```

### 최종 검증
```bash
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all

결과 기록 파일: agent-test/runs/20260616-091332-proto-socket-full-matrix.md

**크로스테스트 상세**
| 방향 | 결과 | PASS scenarios | Expected | FAIL lines |
|---|---:|---:|---:|---:|
| Go -> Dart.io | PASS | 17 | 17 | 0 |
| Go -> Kotlin | PASS | 17 | 17 | 0 |
| Go -> Python | PASS | 17 | 17 | 0 |
| Go -> TypeScript | PASS | 17 | 17 | 0 |
| Dart.io -> Go | PASS | 17 | 17 | 0 |
| Dart.io -> Kotlin | PASS | 17 | 17 | 0 |
| Dart.io -> Python | PASS | 17 | 17 | 0 |
| Dart.io -> TypeScript | PASS | 17 | 17 | 0 |
| Kotlin -> Dart.io | PASS | 17 | 17 | 0 |
| Kotlin -> Go | PASS | 17 | 17 | 0 |
| Kotlin -> Python | PASS | 17 | 17 | 0 |
| Kotlin -> TypeScript | PASS | 17 | 17 | 0 |
| Python -> Dart.io | PASS | 17 | 17 | 0 |
| Python -> Go | PASS | 17 | 17 | 0 |
| Python -> Kotlin | PASS | 17 | 17 | 0 |
| Python -> TypeScript | PASS | 17 | 17 | 0 |
| TypeScript -> Dart.io | PASS | 17 | 17 | 0 |
| TypeScript -> Go | PASS | 17 | 17 | 0 |
| TypeScript -> Kotlin | PASS | 17 | 17 | 0 |
| TypeScript -> Python | PASS | 17 | 17 | 0 |
| Dart.io -> Dart.web | PASS | 2 | 2 | 0 |
| Dart.io -> Dart.web(WSS) | PASS | 2 | 2 | 0 |
| Go -> Dart.web | PASS | 2 | 2 | 0 |
| Go -> Dart.web(WSS) | PASS | 2 | 2 | 0 |
| Kotlin -> Dart.web | PASS | 2 | 2 | 0 |
| Kotlin -> Dart.web(WSS) | PASS | 2 | 2 | 0 |
| Python -> Dart.web | PASS | 2 | 2 | 0 |
| Python -> Dart.web(WSS) | PASS | 2 | 2 | 0 |
| TypeScript -> Dart.web | PASS | 2 | 2 | 0 |
| TypeScript -> Dart.web(WSS) | PASS | 2 | 2 | 0 |
```

---

> **[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.

---

## 코드리뷰 결과

**WARN**

### 차원별 평가

| 차원 | 평가 |
|------|------|
| 정확성 | 구현은 정확하다. 전체 matrix 30쌍 모두 PASS 17/17. |
| 테스트 품질 | Suggested 1건: Go/Kotlin/Python/TypeScript client files에서 `QueuePacket` 에러가 묵살됨. 클라이언트가 exit 0으로 성공 종료하지만 서버가 timeout되는 시나리오에서 실패 귀속이 모호해짐. |
| 진단성 | Nit 2건: Dart 서버 4개 파일에서 timeout과 wrong-data가 동일 메시지. TypeScript 서버 4개 파일에서 `if (!ok)` dead branch. |
| 유지보수성 | Nit 1건: 20개 클라이언트 파일에 `"TestData"` 하드코딩. proto 메시지 rename 시 컴파일 에러 없이 timeout 실패. |

### Suggested

**[S1] Go/Kotlin/Python/TypeScript client `runLegacyReceive`: QueuePacket 에러 묵살**

파일: `go/crosstest/dart_go_client/main.go:172`, `go/crosstest/kotlin_go_client/.../Main.go:174`, `go/crosstest/python_go_client/main.go:172`, `go/crosstest/typescript_go_client/main.go:174`

```go
// 현재 (에러 묵살)
_ = communicator.QueuePacket(&packets.PacketBase{...})

// 수정
if err := communicator.QueuePacket(&packets.PacketBase{...}); err != nil {
    return err  // runLegacyReceive의 반환형을 error로 변경
}
```

`runLegacyReceive`가 `void`/`Unit`이고 에러를 반환하지 않아, QueuePacket write 실패 시 client가 exit 0으로 종료됨. 서버는 observation window 내 패킷 미수신으로 timeout 에러를 내지만 client exit code는 0이라 실패 귀속이 모호해진다. 같은 패턴이 Kotlin/Python/TypeScript Go client 파일 4개에 동일하게 존재.

### Nit

**[N1] Dart 서버 4개 파일: timeout과 wrong-data 동일 메시지**

파일: `dart/crosstest/dart_go.dart:121-122`, `dart_kotlin.dart:121-122`, `dart_python.dart:121-122`, `dart_typescript.dart:121-122`

`onTimeout: () => false` → `StateError('TCP legacy simple-name server received unexpected data')`로 timeout과 wrong-data 모두 동일 메시지. Go는 timeout: `"did not receive packet"`, wrong-data: `"received unexpected data"` 로 구분. Dart도 구분하면 진단이 쉬워짐.

**[N2] TypeScript 서버 4개 파일: `if (!ok)` dead branch**

파일: `typescript/crosstest/typescript_dart.ts:150`, `typescript_go.ts:~150`, `typescript_kotlin.ts:~150`, `typescript_python.ts:~150`

`withTimeout(received, ...)` 은 timeout 시 `reject(new Error(label))`로 throw하며 `resolve(false)`를 하지 않음. 따라서 timeout 발생 시 `await withTimeout(...)` 이전에 throw되어 `if (!ok)` 브랜치에는 도달 불가. Dead code.

**[N3] 20개 클라이언트 파일: `"TestData"` 하드코딩**

파일: 모든 언어의 `run_legacy_receive` / `_runLegacyReceive` 함수 내 `typeName="TestData"`

각 파일은 reflection API (`toki.TypeNameOf`, `TestDataSchema.typeName`, `type_name_of`, `typeNameOf`)를 이미 사용 중임. 짧은 이름을 `"proto_socket.TestData".split('.').last`로 파생하면 proto rename 시 컴파일 에러로 바로 감지됨. 현재는 rename 시 silent timeout 실패.
