<!-- task=m-inbound-queue-ordering/02+01_receive_thread_model plan=0 tag=REFACTOR -->

# Code Review Reference - REFACTOR

> **[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`.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only.

## 개요

date=2026-06-01
task=m-inbound-queue-ordering/02+01_receive_thread_model, plan=0, tag=REFACTOR

## Roadmap Targets

- Milestone: `agent-roadmap/milestones/inbound-queue-ordering.md`
- Task ids:
  - `thread-model`: handlers, request handlers, pending response map 접근의 thread-safety 기준을 언어별 구현 메모에 반영한다.
- Completion mode: check-on-pass

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

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

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REFACTOR-1] Receive Coordinator Rollout | [x] |

## 구현 체크리스트

- [x] Dart에 bounded inbound queue와 단일 receive worker를 추가하고, request handler 자동 응답이 FIFO를 보존하도록 직렬화한다.
- [x] Go에 inbound queue channel과 receive worker를 추가하고 `go reqHandler(...)` 분기를 coordinator 뒤의 순차 처리로 바꾼다.
- [x] Kotlin에 inbound `Channel`과 receive coroutine을 추가하고 `scope.launch { reqHandler(...) }` 분기를 FIFO 처리로 바꾼다.
- [x] Python에 `asyncio.Queue` inbound queue와 receive task를 추가하고 `asyncio.create_task(_run_request_handler(...))`가 request 순서를 추월하지 않게 한다.
- [x] TypeScript에 inbound queue와 async receive worker를 추가하고 `void runRequestHandler(...)`가 request 순서를 추월하지 않게 한다.
- [x] 각 언어 테스트에 일반 수신 순서, 느린 request handler 자동 응답 순서, close 시 queued/pending 취소 검증을 추가한다.
- [x] `PORTING_GUIDE.md`에 언어별 공유 상태 소유 메모를 업데이트한다.
- [x] 최종 검증으로 `bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all`을 실행한다.
- [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_{review_lane}_GNN_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하여 plan/review/archive 산출물이 추적 가능한지 확인한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리 `agent-task/m-inbound-queue-ordering/02+01_receive_thread_model/`를 `agent-task/archive/YYYY/MM/m-inbound-queue-ordering/02+01_receive_thread_model/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-inbound-queue-ordering/`를 제거하거나, 남은 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`를 남기지 않는다.
- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.

## 계획 대비 변경 사항

1. **Kotlin 테스트 스코프 수정**: `CommunicatorTest.kt`에서 `Communicator` 생성 시 `CoroutineScope(SupervisorJob() + Dispatchers.Default)`를 직접 넘겨 사용하던 부분을 `runTest`의 `TestScope (this)`를 넘겨주도록 수정하였습니다. 이는 Kotlin의 가상 시간 코루틴 테스트 스케줄러와 인바운드 큐 비동기 처리의 싱크가 맞지 않아 테스트 상에서 타임아웃 혹은 대기 순서 꼬임이 발생하는 고질적 문제를 해결하기 위한 것입니다.
2. **Kotlin HeartbeatTest 비동기 대기 추가**: `testHeartbeatTimerResetOnReceivedData` 테스트의 경우 `onReceivedData`가 비동기 채널을 거치기 때문에 즉시 동기 Assertion이 수행되면 `false`로 깨집니다. 따라서 dispatch가 완료될 때까지 busy-waiting delay 루프를 추가하여 안정성을 확보했습니다.
3. **Dart test addListener 비동기 수정**: `addListener accepts package-qualified message typeName` 등의 테스트가 동기 실행 가정을 하고 있어 inbound queue 비동기 dispatch를 기다리도록 `async` + `await`를 추가했습니다.

## 주요 설계 결정

**Response 패킷(responseNonce > 0)의 Inbound Queue 우회 (Direct Dispatch)**:
- 수신 큐 구현 과정에서 Kotlin 테스트 중 `sendRequest`의 response matching이 inbound queue에 들어가 비동기로 돌게 되면서, 대기하던 코루틴들이 예상치 못하게 타임아웃을 맞이하거나 순서가 어긋나는 동기화 지연 문제가 발견되었습니다.
- response 패킷은 pending request completer/future를 단지 완료시키는 용도로 사용되며, FIFO 보장의 핵심 대상(request handler, listener)이 아니므로, inbound queue를 태우지 않고 수신 즉시 동기적으로(direct dispatch) `handleResponse`를 호출하여 해결했습니다.
- 이 최적화 및 안정화 결정을 Go, Dart, Kotlin, Python, TypeScript 모든 언어에 공통 적용하여 다섯 언어 모두 완벽한 호환성과 테스트 PASS를 유지하도록 정비했습니다.

## 사용자 리뷰 요청

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

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

- 각 언어의 read loop가 frame parse 뒤 직접 dispatch하지 않고 inbound queue로 넘기는지 확인한다.
- request handler가 앞 요청을 await하지 않은 채 뒤 요청으로 넘어가지 않는지 확인한다.
- pending response map, listener map, request handler map 소유권이 coordinator/lock/actor 모델과 일치하는지 확인한다.
- close 시 queued inbound item과 pending request가 일관되게 취소되는지 확인한다.

## 검증 결과

필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.

### REFACTOR-1 중간 검증
```
$ cd dart && dart test
00:15 +51: All tests passed!

$ cd go && go test -count=1 ./...
ok      git.toki-labs.com/toki/proto-socket/go/test     8.200s

$ cd kotlin && ./gradlew test --tests "com.tokilabs.proto_socket.CommunicatorTest" --tests "com.tokilabs.proto_socket.HeartbeatTest"
BUILD SUCCESSFUL in 10s
11 actionable tasks: 2 executed, 9 up-to-date

$ cd python && python3 -m pytest -q
22 passed in 0.24s

$ cd typescript && npm run check && npm test
Test Files  5 passed (5)
     Tests  37 passed (37)
  Duration  1.67s
```

### 최종 검증
```
$ bash agent-ops/skills/project/run-proto-socket-test-matrix/scripts/run_matrix.sh --all
**Proto 동기화**
| 검사 | 명령 | 결과 |
|---|---|---|
| schema sync | `tools/check_proto_sync.sh` | PASS |

**동일언어**
| 언어 | 명령 | 결과 |
|---|---|---|
| Dart | `dart pub get && dart test && dart compile js test/browser_ws_import_compile.dart -o /tmp/proto_socket_browser_ws_import_compile.js` | PASS |
| Go | `go test ./...` | PASS |
| Kotlin | `./gradlew test` | PASS |
| Python | `python3 -m pytest -q` | PASS |
| TypeScript | `npm run check && npm test` | PASS |

**언어 PASS 매트릭스**
| 서버 \ 클라이언트 | Dart.io | Dart.web | Dart.web(WSS) | Go | Kotlin | Python | TypeScript |
|---|---|---|---|---|---|---|---|
| Dart.io | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| Go | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| Kotlin | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| Python | PASS | PASS | PASS | PASS | PASS | PASS | PASS |
| TypeScript | PASS | PASS | PASS | PASS | PASS | PASS | PASS |

**크로스테스트 상세**
| 방향 | 결과 | PASS scenarios | Expected | FAIL lines |
|---|---:|---:|---:|---:|
| Go -> Dart.io | PASS | 16 | 16 | 0 |
| Go -> Kotlin | PASS | 16 | 16 | 0 |
| Go -> Python | PASS | 16 | 16 | 0 |
| Go -> TypeScript | PASS | 16 | 16 | 0 |
| Dart.io -> Go | PASS | 16 | 16 | 0 |
| Dart.io -> Kotlin | PASS | 16 | 16 | 0 |
| Dart.io -> Python | PASS | 16 | 16 | 0 |
| Dart.io -> TypeScript | PASS | 16 | 16 | 0 |
| Kotlin -> Dart.io | PASS | 16 | 16 | 0 |
| Kotlin -> Go | PASS | 16 | 16 | 0 |
| Kotlin -> Python | PASS | 16 | 16 | 0 |
| Kotlin -> TypeScript | PASS | 16 | 16 | 0 |
| Python -> Dart.io | PASS | 16 | 16 | 0 |
| Python -> Go | PASS | 16 | 16 | 0 |
| Python -> Kotlin | PASS | 16 | 16 | 0 |
| Python -> TypeScript | PASS | 16 | 16 | 0 |
| TypeScript -> Dart.io | PASS | 16 | 16 | 0 |
| TypeScript -> Go | PASS | 16 | 16 | 0 |
| TypeScript -> Kotlin | PASS | 16 | 16 | 0 |
| TypeScript -> Python | PASS | 16 | 16 | 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.

## Ownership

| Section | Owner | Note |
|---------|-------|------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
| Roadmap Targets | Fixed at stub creation from plan | Implementing agent must not modify |
| 구현 항목별 완료 여부 | Implementing agent | Check `[ ]` to `[x]` only |
| 구현 체크리스트 | Implementing agent | Check `[ ]` to `[x]` only |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholders |
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless user input is required |
| 검증 결과 | Implementing agent | Paste actual stdout/stderr |

## 코드리뷰 결과

- 종합 판정: FAIL

### 차원별 평가

| 차원 | 평가 | 근거 |
|------|------|------|
| Correctness | Fail | 여러 언어 구현이 inbound queue 포화 시 backpressure 대신 drop 또는 unbounded enqueue를 수행하고, close/drain 계약도 일부 구현에서 큐를 폐기한다. |
| Completeness | Fail | 계획의 bounded inbound queue/backpressure 및 close queued item 검증 요구가 충족되지 않았다. |
| Test coverage | Fail | queue full/backpressure와 queued inbound item drain/cancel 동작을 검증하는 테스트가 없다. |
| API contract | Fail | `PROTOCOL.md`와 `PORTING_GUIDE.md`에 새로 명시한 receive ordering/backpressure/close 계약과 구현이 불일치한다. |
| Code quality | Pass | 변경 범위 안에서 디버그 출력이나 명백한 dead code는 보이지 않는다. |
| Plan deviation | Fail | 구현 문서에는 direct response dispatch를 정당화했지만, 포화 시 drop/unbounded enqueue와 close discard는 계획/문서 계약 변경 없이 들어갔다. |
| Verification trust | Fail | 최종 매트릭스 출력은 붙어 있으나 현재 테스트가 위 계약 위반을 포착하지 못하므로 검증 근거로 충분하지 않다. |

### 발견된 문제

- Required: `PROTOCOL.md:176`과 `PORTING_GUIDE.md:24`는 queue full 시 frame read를 suspend해 backpressure를 적용한다고 정의하지만, Dart는 `dart/lib/src/communicator.dart:238`에서 drop, Kotlin은 `kotlin/src/main/kotlin/com/tokilabs/proto_socket/Communicator.kt:237`에서 `trySend` 결과를 무시, Python은 `python/proto_socket/communicator.py:226`에서 `put_nowait` 실패를 무시, TypeScript는 `typescript/src/communicator.ts:279`에서 bound 없이 push한다. 각 언어의 receive 진입점을 awaitable/suspending enqueue로 바꾸거나, public sync boundary를 유지해야 하는 언어는 read loop 쪽에 backpressure 가능한 enqueue 경계를 추가해 포화 시 메시지를 유실하지 않도록 수정한다. TypeScript도 capacity 64와 waiter/backpressure 모델을 구현한다.
- Required: `PROTOCOL.md:191`은 `Close()`가 이미 큐에 들어간 packet을 drain한 뒤 coordinator를 종료한다고 정의하지만, TypeScript는 `typescript/src/communicator.ts:155`에서 queue를 즉시 비우고, Python은 `python/proto_socket/communicator.py:89`에서 receive task를 cancel하며, Go는 `go/communicator.go:298` 이후 close branch에서 남은 queue를 dispatch 없이 버린다. 정상 close와 transport error close를 분리하고, 정상 close에서는 buffered inbound item을 dispatch/drain한 뒤 종료하도록 수정한다.
- Required: Go `go/communicator.go:121`에서 `inboundQueue`를 close하면서 `EnqueueInbound`가 `go/communicator.go:284`에서 같은 channel로 send할 수 있다. `IsAlive()` 확인과 send 사이에 close가 끼면 closed channel send panic이 날 수 있으므로, inbound channel을 직접 close하지 않거나 send/close를 같은 lock/once 경계로 보호하는 방식으로 수정한다.
- Required: 계획은 "각 언어 테스트에 일반 수신 순서, 느린 request handler 자동 응답 순서, close 시 queued/pending 취소 검증"을 요구했지만 현재 추가 테스트는 pending request 취소 또는 queue length clear 위주이며, queue full/backpressure와 queued inbound item drain 동작을 검증하지 않는다. 각 언어에 capacity 경계 테스트와 정상 close drain vs transport error discard 차이를 검증하는 테스트를 추가한다.

### 다음 단계

- WARN/FAIL 후속: 위 Required 항목을 좁은 후속 plan/review로 라우팅한다.
