<!-- task=m-protocol-evolution-compatibility/05+04_nonce_dart_go plan=0 tag=API -->

# Plan - API

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

`CODE_REVIEW-cloud-G06.md`의 구현 에이전트 소유 섹션 작성까지가 구현의 마지막 단계다. 검증을 실행하고 실제 출력/결과를 기록한 뒤 active 파일을 유지한 채 리뷰를 요청한다. 구현 중 사용자 결정, 사용자 소유 외부 환경, 범위 충돌이 생기면 직접 질문하지 말고 리뷰 stub의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 남긴다. 후속 에이전트가 재실행으로 해소할 수 있는 증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

Dart와 Go는 현재 `nonce` wrap 후 후보 값이 active pending request nonce인지 확인하지 않는다. 이 작업은 reference 역할을 하는 두 구현에서 pending skip 정책을 먼저 구현하고, 같은 실패 모드를 잡는 boundary regression test를 추가한다. 나머지 언어 rollout과 full matrix는 06번 작업에서 이어간다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active `CODE_REVIEW-cloud-G06.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 채팅으로 사용자에게 직접 질문하지 않으며, code-review가 해당 요청의 타당성을 판단하고 필요할 때 `USER_REVIEW.md`를 작성한다.

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/ROADMAP.md`
- `agent-roadmap/milestones/protocol-evolution-compatibility.md`
- `agent-ops/rules/project/domain/protocol/rules.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-test/local/rules.md`
- `agent-test/local/proto-socket-full-matrix.md`
- `dart/lib/src/communicator.dart`
- `dart/test/communicator_test.dart`
- `go/communicator.go`
- `go/communicator_nonce_test.go`
- `PROTOCOL.md`
- `VERSIONING.md`
- `PORTING_GUIDE.md`

### 테스트 환경 규칙

- test_env: `local`
- 적용 규칙: `agent-test/local/rules.md`를 읽었다.
- 매칭 프로필: `agent-test/local/proto-socket-full-matrix.md`
- 이 split의 직접 검증은 Dart/Go focused tests다. full matrix는 모든 언어 rollout이 끝나는 06번에서 실행한다.

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

- Dart: 기존 `nonce wraps after int32 max without emitting zero`, `sendRequest matches response at nonce wrap boundary`는 단순 wrap만 검증하고 active pending nonce skip은 검증하지 않는다.
- Go: 기존 `TestNonceWrapsAfterInt32MaxWithoutEmittingZero`, `TestSendRequestNonceWrapsAtInt32Max`는 단순 wrap만 검증하고 active pending nonce skip은 검증하지 않는다.
- Kotlin/Python/TypeScript는 06번에서 다룬다.

### 심볼 참조

- renamed/removed symbol: none
- Go에서 `nextNonce()` 시그니처를 변경한다면 call sites는 [go/communicator.go](/config/workspace/proto-socket/go/communicator.go:305), [go/communicator.go](/config/workspace/proto-socket/go/communicator.go:318), [go/communicator.go](/config/workspace/proto-socket/go/communicator.go:614)이다.

### 분할 판단

- split decision policy를 먼저 평가했다.
- 공유 task group: `agent-task/m-protocol-evolution-compatibility/`
- `04+03_nonce_contract`: 선행 계약 문서 작업.
- `05+04_nonce_dart_go`: 이 plan. predecessor `04`가 필요하며 계획 작성 시점에는 active plan만 있고 `complete.log`는 없다.
- `06+04,05_nonce_kotlin_python_ts`: 04/05 완료 후 실행.
- Dart/Go는 reference 구현 경계이고 Kotlin/Python/TypeScript와 테스트 러너가 달라 별도 리뷰 단위로 분리한다.

### 범위 결정 근거

- 이 plan은 Dart와 Go communicator 및 focused nonce tests만 수정한다.
- Kotlin/Python/TypeScript는 06번에서 수정한다.
- cross-language crosstest 기대값은 바꾸지 않는다. nonce 충돌은 동일 언어 unit boundary로 검증한다.

### 빌드 등급

- `cloud-G06`: protocol/concurrency 성격의 동작 변경이고 Go는 atomic + pending map 경계를 함께 검토해야 한다.

## 구현 체크리스트

- [ ] 04번 predecessor `complete.log`가 존재하는지 확인하고, 없으면 구현을 시작하지 않고 리뷰 stub에 차단 근거를 남긴다.
- [ ] Dart `Communicator.nextNonce()`가 active `_pendingRequests` 키와 충돌하는 후보 nonce를 건너뛰도록 수정한다.
- [ ] Dart `communicator_test.dart`에 wrap 후 `1`이 pending이면 다음 send/request nonce가 `2`가 되는 regression test를 추가한다.
- [ ] Go `Communicator`의 nonce 선택이 active `pendingRequests` 키와 충돌하는 후보 nonce를 건너뛰도록 수정한다.
- [ ] Go `communicator_nonce_test.go`에 wrap 후 pending nonce skip regression test를 추가한다.
- [ ] `cd dart && dart test test/communicator_test.dart`, `cd go && go test ./...`, `git diff --check`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

`05+04_nonce_dart_go`는 디렉터리명 기준 predecessor `04`를 요구한다. 구현 전 `agent-task/m-protocol-evolution-compatibility/04+03_nonce_contract/complete.log` 또는 matching archive complete log를 확인한다.

### [API-1] Dart pending nonce skip

#### 문제

[dart/lib/src/communicator.dart](/config/workspace/proto-socket/dart/lib/src/communicator.dart:69)는 max 뒤 `1`로 돌아가지만 `_pendingRequests`를 보지 않는다.

```dart
69 int nextNonce() {
70   if (_nonce >= maxNonce) {
71     _nonce = 0;
72   }
73   _nonce += 1;
74   return _nonce;
75 }
```

[dart/lib/src/communicator.dart](/config/workspace/proto-socket/dart/lib/src/communicator.dart:210)는 이 값을 바로 pending key로 등록한다.

```dart
210 final requestNonce = nextNonce();
213 _pendingRequests[requestNonce] = _PendingRequest(
```

#### 해결 방법

`nextNonce()`에서 후보 nonce를 만들고 `_pendingRequests.containsKey(candidate)`이면 계속 진행한다. 최대 `maxNonce`번 시도해도 후보가 없으면 `StateError('no available nonce: all positive int32 nonces are pending')`를 던진다. 이 메서드는 `send`, `sendRequest`, 자동 response가 공통으로 사용하므로 모든 outbound nonce가 active pending key를 피한다.

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

- [ ] `dart/lib/src/communicator.dart`: bounded candidate loop 추가
- [ ] `dart/test/communicator_test.dart`: pending skip regression 추가

#### 테스트 작성

`dart/test/communicator_test.dart`에 새 테스트를 추가한다. 절차: nonce를 `maxNonce`, pending request key `1`을 만든 뒤 응답하지 않은 상태에서 `send()` 또는 두 번째 `sendRequest()`를 호출해 새 packet nonce가 `2`인지 확인한다. 기존 pending request는 timeout이 길게 걸리지 않게 `closeForTest()`로 정리한다.

#### 중간 검증

```bash
cd dart && dart test test/communicator_test.dart
```

기대 결과: communicator tests PASS.

### [API-2] Go pending nonce skip

#### 문제

[go/communicator.go](/config/workspace/proto-socket/go/communicator.go:187)는 atomic counter만 보고 후보 nonce를 반환한다.

```go
187 func (c *Communicator) nextNonce() int32 {
188     for {
189         current := c.nonce.Load()
190         var next int32
191         if current >= MaxNonce {
192             next = 1
193         } else {
194             next = current + 1
195         }
196         if c.nonce.CompareAndSwap(current, next) {
197             return next
198         }
199     }
200 }
```

#### 해결 방법

Go는 `pendingRequests`가 mutex로 보호되므로 candidate 확정 시 pending map 확인도 안전하게 수행한다. 권장 구현은 `nextNonce() (int32, error)`로 바꾸고, 후보가 pending에 있으면 다음 후보를 계속 찾는다. `Send`, `SendRequest`, `AddRequestListenerTyped`의 response path는 새 error를 처리해야 한다.

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

- [ ] `go/communicator.go`: `nextNonce` pending skip 및 call site error 처리
- [ ] `go/communicator_nonce_test.go`: pending skip regression 추가

#### 테스트 작성

`go/communicator_nonce_test.go`에 `TestNextNonceSkipsActivePendingAfterWrap`를 추가한다. 절차: nonce를 `MaxNonce`, pending request key `1`을 만든 뒤 다음 send/request가 nonce `2`를 발행하는지 확인한다. 직접 map을 조작하는 대신 첫 `SendRequestTyped`를 시작해 pending `1`을 만들고 응답하지 않은 상태에서 `Send`를 호출한다.

#### 중간 검증

```bash
cd go && go test ./...
```

기대 결과: root/go test packages PASS.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `dart/lib/src/communicator.dart` | API-1 |
| `dart/test/communicator_test.dart` | API-1 |
| `go/communicator.go` | API-2 |
| `go/communicator_nonce_test.go` | API-2 |

## 최종 검증

```bash
cd dart && dart test test/communicator_test.dart
cd go && go test ./...
git diff --check
```

기대 결과: Dart communicator tests PASS, Go tests PASS, whitespace error 없음.

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