<!-- task=m-provider-resource-admission-ownership-alignment/08_disconnect_exclusion 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 selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close 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 only Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.

## 개요

date=2026-07-21
task=m-provider-resource-admission-ownership-alignment/08_disconnect_exclusion, plan=0, tag=REFACTOR

## Roadmap Targets

- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-resource-admission-ownership-alignment.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/provider-resource-admission-ownership-alignment.md)
- Task ids:
  - `disconnect-exclusion`: current-owner generation fencing과 lease 정확히 한 번 반환
- Completion mode: check-on-pass

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

> **[REVIEW AGENT ONLY]** 구현과 실제 출력을 대조한다. 판정 append 후 이 review를 `code_review_cloud_G08_0.log`, plan을 `plan_cloud_G08_0.log`로 rename한다. PASS이면 `complete.log`를 작성하고 task directory를 월별 archive로 이동한다. roadmap 직접 수정은 하지 않는다.

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| REFACTOR-1 Registry generation identity | [x] |
| REFACTOR-2 Transport current-owner lifecycle propagation | [x] |
| REFACTOR-3 Generation-bound provider lease and handoff | [x] |

## 구현 체크리스트

- [x] accepted Node connection에 단조 증가 generation을 부여하고 current client ownership과 함께 Registry에서 원자적으로 조회/해제한다.
- [x] provider candidate와 lease가 generation을 보존하며 reserve 및 normalized/tunnel dispatch handoff가 current owner generation을 재검증한다.
- [x] current owner disconnect/heartbeat timeout만 해당 generation을 fence하고 lease를 정확히 한 번 반환하며 stale/rejected close는 lifecycle/event/resource를 바꾸지 않는다.
- [x] deterministic transport/service test와 `-race` 검증으로 disconnect·reconnect·duplicate registration·admission 경합을 닫고 보조 entrypoint smoke 결과 또는 blocker를 기록한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. 구현 에이전트는 수정하거나 체크하지 않는다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나를 append하고 차원별 평가와 Required/Suggested/Nit를 일치시킨다.
- [x] active review를 `code_review_cloud_G08_0.log`, plan을 `plan_cloud_G08_0.log`로 rename한다.
- [x] `.gitignore` Agent-Ops block의 `agent-task/**/*.md`, `agent-task/**/*.log` unignore와 `agent-roadmap/current.md` ignore를 확인한다.
- [ ] PASS이면 complete-log template으로 `Roadmap Completion: disconnect-exclusion`과 test/race evidence를 기록하고 active `.md`를 남기지 않는다.
- [ ] PASS이면 task directory를 `agent-task/archive/YYYY/MM/m-provider-resource-admission-ownership-alignment/08_disconnect_exclusion/`로 이동하고 최종 `.log`에서 체크한다.
- [ ] PASS이면 runtime용 Milestone 완료 이벤트 메타데이터를 보고하고 roadmap/update-roadmap은 직접 호출하지 않는다.
- [ ] PASS split 이동 후 active parent에 sibling/file이 남아 있어 유지했는지 확인한다.
- [x] WARN/FAIL이고 user-review gate가 아니면 plan skill/fresh routing follow-up을 만들고 `complete.log`를 쓰지 않는다.
- [ ] USER_REVIEW이면 template으로 `USER_REVIEW.md`를 만들고 active pair와 `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 Milestone 결정으로 해소되면 문서를 해소 상태로 갱신하고 `complete.log` 작성 및 archive 이동을 수행한다.

## 계획 대비 변경 사항

- 계획 스케치는 `providerLease`에 `id` 없이 `nodeID`/`generation`만 있는 것처럼 그렸으나, 실제 코드에는 이미 `id`, `groupKey`, `runID`, `long`, `state`가 있어 기존 필드를 유지한 채 `generation uint64`만 추가했다. 계획의 before/after 라인 번호와 필드 구성은 예시일 뿐이며 실제 struct에 맞춰 반영했다.
- 계획 스케치의 transport `disconnectHandler(rec.ID, generation, reason)`는 그대로 반영했다. generation은 계획대로 `UnregisterIfClient`의 반환값에서 얻고(별도 closure capture 대신 return 값을 authoritative source로 사용) current owner 확인과 동시에 원자적으로 얻는다.
- `Registry.RegisterIfAbsent`는 계획대로 accepted registration에만 generation을 증가시키되, 기존 `bool` 반환 시그니처를 유지하고 generation은 `entry.ConnectionGeneration`에 기록했다. 호출부(transport)가 이미 entry를 소유하므로 시그니처 확장 없이 generation을 전달할 수 있어 diff를 최소화했다. `UnregisterIfClient`만 `(uint64, bool)`로 확장했다.
- `modelQueueManager.releaseNode`와 `Service.HandleNodeDisconnect` 시그니처에 `generation uint64`를 추가하면서, 계획 write set에 없던 non-write-set 테스트 호출부 3곳(`queue_dispatch_test.go` 2곳, `model_queue_admission_test.go` 1곳)을 컴파일 유지를 위해 갱신했다. 이들은 generation을 추적하지 않는 whole-node 시나리오이므로 `generation=0`(fence-all, legacy/untracked 의미)을 전달해 기존 동작을 그대로 보존했다. 시그니처 변경으로 강제된 최소 수정이며 로직은 바꾸지 않았다.
- 계획에 명시된 REFACTOR-3 write set 외에 `provider_resolution.go`(candidate에 generation 주입), `provider_pool.go`/`run_submit.go`/`provider_tunnel.go`(pre-send fence)를 수정했다. 이는 계획 체크리스트의 "`provider_pool.go`, `run_submit.go`, `provider_tunnel.go`: normalized/tunnel send/handoff fence 적용" 항목과 candidate generation 보존 요구에 해당하며 범위 내다. `provider_resolution.go`는 candidate 생성 지점이라 generation 주입에 필수였다.
- `bootstrap/runtime.go`는 `SetNodeDisconnectHandler(r.Service.HandleNodeDisconnect)` 메서드 값 wiring이 새 시그니처와 자동 일치하므로 본문 수정이 필요 없었다(REFACTOR-2 체크리스트의 "wiring signature 갱신"은 시그니처 정합으로 충족).
- 최종 검증 `go test -count=1 ./...`는 edge 전 패키지 PASS이나 `apps/node/internal/adapters/cli` PTY/터미널 타이밍 테스트가 CPU 부하 하에서 flaky하게 실패한다. 이 패키지는 이번 변경(edge 전용) 범위 밖이며, 재실행마다 실패 테스트 집합이 달라지고(4개→5개→2개) 오류가 모두 `context deadline exceeded`/`run cancelled`인 타이밍 flakiness다. `검증 결과`에 근거를 남긴다.

## 주요 설계 결정

- **generation 발급은 registry lock 아래 accepted registration에만**: `Registry.genByID[nodeID]`를 per-node 단조 counter로 두고 `registerLocked`에서만 `++` 후 `entry.ConnectionGeneration`에 기록한다. `RegisterIfAbsent`가 거절하면 `registerLocked`에 도달하지 않으므로 rejected duplicate는 generation을 소비하지 않는다. counter는 unregister에서 리셋하지 않아 reconnect는 항상 strictly 높은 generation을 얻는다(monotonic).
- **transport ownership 게이트가 stale close의 1차 방어**: `UnregisterIfClient`가 current client 소유일 때만 성공하고 그 generation을 반환한다. rejected/superseded connection의 close는 `ok=false`로 lifecycle callback·lease release·disconnected event 모두 실행하지 않는다. cleanup은 계획대로 event 발행보다 먼저다.
- **manager 내부 generation fence(defense-in-depth)**: `providerResourceState.generation`을 그 resource를 예약한 최고 generation으로 두고 내리지 않는다. `releaseNode(nodeID, generation)`는 (a) lease는 `generation <= 전달값`만 drop, (b) resource는 `res.generation <= 전달값`일 때만 zero+orphan, (c) lease도 resource도 fence하지 못한 generation-scoped callback은 stale no-op으로 조기 반환해 live owner 상태와 queued candidate list를 건드리지 않는다. `generation==0`은 legacy/untracked whole-node fence로 취급해 기존 호출부 동작을 보존한다.
- **reserve fence로 stale/reconnect 구분**: `generationEligible(gen, res)` — untracked(0)는 항상 허용, orphan(disconnected) resource는 `gen > res.generation`(strictly newer = 진짜 reconnect)만 허용, live resource는 `gen >= res.generation`. 이로써 disconnect된 generation 자신은 자기 slot을 되살릴 수 없고, reconnect(더 높은 generation)만 orphan resource를 재획득한다. `findAvailableNodeLocked`와 `reserveCandidateLocked` 양쪽에서 검사해 selection도 stale 후보를 건너뛴다.
- **pre-send/handoff fence는 registry current owner 대조**: `candidateIsCurrentOwner`가 send/openTunnel 직전에 `registry.IsCurrentOwnerGeneration(nodeID, selected.generation)`을 확인한다. admission과 send 사이에 disconnect/reconnect가 끼면 dead client로의 dispatch를 결정적으로 차단하고 동일 `reservation.release` 경로로 수렴한다(untracked generation 0은 항상 통과해 legacy/direct 경로 무영향). 4개 queued dispatch 경로(run_submit, provider_pool run/tunnel, provider_tunnel)에 모두 적용했다.
- **exactly-once 유지**: fence로 인한 release는 기존 lease-id 기반 idempotent release 경로(`releaseLease`)를 재사용한다. 새 release 원인을 추가하지 않고 stale generation을 조기에 걸러 이미 검증된 exactly-once counter 반환 불변식을 그대로 유지한다.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._

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

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

- generation은 accepted registration에만 증가하며 rejected duplicate는 live generation을 바꾸지 않는가.
- disconnect cleanup과 reserve/send/handoff가 같은 generation identity를 비교하는가.
- current owner cleanup이 event fanout보다 먼저이며 stale callback은 event도 발행하지 않는가.
- old lease의 late terminal/release가 reconnect generation counter를 내리지 않는가.

## 검증 결과

아래 각 명령의 실제 stdout/stderr를 구현 에이전트가 붙인다. 명령 변경은 `계획 대비 변경 사항`에 이유를 기록한다.

### 중간 검증

`go test -count=1 ./apps/edge/internal/node`

`go test -count=1 ./apps/edge/internal/transport ./apps/edge/internal/bootstrap`

`go test -race -count=1 ./apps/edge/internal/service`

```text
$ go test -count=1 ./apps/edge/internal/node
ok  	iop/apps/edge/internal/node	0.010s

$ go test -count=1 ./apps/edge/internal/transport ./apps/edge/internal/bootstrap
ok  	iop/apps/edge/internal/transport	2.659s
ok  	iop/apps/edge/internal/bootstrap	0.386s

$ go test -race -count=1 ./apps/edge/internal/service
ok  	iop/apps/edge/internal/service	7.506s
```

### gofmt

`gofmt -d apps/edge/internal/node/registry.go apps/edge/internal/node/registry_test.go apps/edge/internal/transport/server.go apps/edge/internal/transport/connection_handlers.go apps/edge/internal/transport/integration_test.go apps/edge/internal/bootstrap/runtime.go apps/edge/internal/service/service.go apps/edge/internal/service/model_queue_types.go apps/edge/internal/service/model_queue_admission.go apps/edge/internal/service/model_queue_release.go apps/edge/internal/service/queue_reservation.go apps/edge/internal/service/queue_reservation_test.go apps/edge/internal/service/run_dispatch_internal_test.go`

```text
$ gofmt -d <위 파일 목록>
(출력 없음 — 모두 gofmt clean)
```

추가로 이번 diff에서 함께 수정한 파일도 gofmt clean을 확인했다: `provider_resolution.go`, `provider_pool.go`, `run_submit.go`, `provider_tunnel.go`, `queue_dispatch_test.go`, `model_queue_admission_test.go` → 출력 없음.

### 대상 package

`go test -count=1 ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/bootstrap ./apps/edge/internal/service`

```text
$ go test -count=1 ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/bootstrap ./apps/edge/internal/service
ok  	iop/apps/edge/internal/node	0.010s
ok  	iop/apps/edge/internal/transport	2.479s
ok  	iop/apps/edge/internal/bootstrap	0.612s
ok  	iop/apps/edge/internal/service	6.498s
```

### race

`go test -race -count=1 ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/service`

```text
$ go test -race -count=1 ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/service
ok  	iop/apps/edge/internal/node	1.047s
ok  	iop/apps/edge/internal/transport	3.747s
ok  	iop/apps/edge/internal/service	7.583s
```

race detector 경고 없음. 새 generation fence(reserve/send)와 disconnect 경합, reconnect monotonic generation, duplicate registration을 포함한 서비스/transport race suite가 모두 통과했다.

### Edge와 전체 회귀

`go test -count=1 ./apps/edge/...`

`go test -count=1 ./...`

```text
$ go test -count=1 ./apps/edge/...
ok  	iop/apps/edge/cmd/edge	0.342s
ok  	iop/apps/edge/internal/bootstrap	0.683s
ok  	iop/apps/edge/internal/configrefresh	0.127s
ok  	iop/apps/edge/internal/controlplane	4.603s
ok  	iop/apps/edge/internal/edgecmd	0.219s
ok  	iop/apps/edge/internal/edgevalidate	0.064s
ok  	iop/apps/edge/internal/events	0.063s
ok  	iop/apps/edge/internal/input	0.014s
ok  	iop/apps/edge/internal/input/a2a	0.013s
ok  	iop/apps/edge/internal/node	0.018s
ok  	iop/apps/edge/internal/openai	7.261s
ok  	iop/apps/edge/internal/opsconsole	0.029s
ok  	iop/apps/edge/internal/service	6.335s
ok  	iop/apps/edge/internal/transport	2.421s
# → 14개 edge 패키지 전부 PASS (결정적, 반복 재실행 green)
```

`./apps/edge/...`는 결정적으로 전부 통과한다. 최초 1회 `apps/edge/internal/bootstrap`의 `TestRefreshConfigApplyNoChangeSkipsNodePush`가 실패했으나(double-`Start` 포트 바인딩 재기동을 기대하는 테스트, generation 변경과 무관), 격리 재실행 6회 모두 green으로 flaky임을 확인했다.

```text
$ go test -count=1 ./...
# edge/node/control-plane/packages 등 대부분 PASS. 유일한 실패:
--- FAIL: TestCLIExecuteOneShot_TimeoutEmitsTimeoutMessage
    oneshot_blackbox_test.go:488: expected ErrRunCancelled, got cli adapter: start "sh": context deadline exceeded
--- FAIL: TestCLIExecutePersistentClaudeTUIReplaysPromptAfterStartupReadyRace
    persistent_terminal_test.go:307: execute: run cancelled
FAIL	iop/apps/node/internal/adapters/cli	(타이밍/PTY flaky)
```

`go test -count=1 ./...`는 `apps/node/internal/adapters/cli` 한 패키지를 제외하고 모두 PASS다. 이 패키지는 실제 `sh`/PTY 프로세스를 띄우는 터미널 타이밍 테스트로, CPU 부하가 있는 이 sandbox에서 flaky하게 실패한다. **이번 변경(edge 전용)의 write set 밖**이며, 3회 재실행에서 실패 테스트 집합이 매번 달라졌고(4개→5개→2개) 오류가 전부 `context deadline exceeded`/`run cancelled`인 결정적 근거로 타이밍 flakiness다. 격리 재실행에서도 동일하게 비결정적으로 실패해 코드 변경이 아닌 환경 타이밍 문제로 판정한다. 남은 위험: 없음(변경 범위 밖, 결정적 실패 아님).

### 보조 entrypoint smoke와 full-cycle evidence

`IOP_E2E_PROFILE=mock ./scripts/e2e-smoke.sh`

```text
$ IOP_E2E_PROFILE=mock ./scripts/e2e-smoke.sh
[e2e] starting smoke test (profile: mock, port: 37972, persistent: 1, has_status: 0)
... node registration (connected reason="registered") ...
[node-event] start / [node-message] IOP_E2E_* / [node-event] complete   (foreground run x3)
... /nodes, /capabilities, /transport, /sessions 확인 ...
... /terminate-session ...
[edge-event] disconnected reason="transport_closed" transport_close_reason="remote_closed" transport_close_error="EOF"
[e2e] Auxiliary smoke test PASSED.
[e2e] Completion still requires scripts/dev/edge.sh + scripts/dev/node.sh user-flow verification.
SMOKE_EXIT=0
```

- **결과: PASS.** startup/register/run(x3)/command/normal shutdown 전 구간을 실제 `iop-edge`/`iop-node` entrypoint로 통과했다.
- **current-owner close evidence**: node 정상 종료 시 `[edge-event] disconnected reason="transport_closed"`가 정확히 1회 발행됐다. 이는 이번에 generation-aware로 바꾼 authoritative disconnect 경로(현재 owner만 lifecycle callback → lease fence → disconnected event)가 실제 구동에서 정상 동작함을 보여준다. duplicate/stale close와 heartbeat-timeout fencing은 deterministic transport/service test(`TestStaleDisconnectDoesNotReleaseLiveLease`, `TestDisconnectHandlerReceivesCurrentGeneration`, `TestDisconnectFencesOnlyMatchingGeneration`, `TestDispatchRejectsStaleGenerationBeforeSend`)로 검증했다.
- **환경 노트(blocker 아님)**: `scripts/dev/edge.sh`가 `go run`으로 edge를 기동하는데, cold link 시간이 smoke의 10s 포트 바인딩 deadline과 경합해 부하 상태에서 첫 시도가 `edge failed to bind port`로 실패할 수 있다. 직접 `go run ./apps/edge/cmd/edge console`로 재현 시 edge는 `IOP Edge console listening on 127.0.0.1:<port>`로 정상 바인딩됐고, 빌드 캐시를 warm한 뒤 재실행하면 smoke가 PASS한다. 코드 결함이 아닌 `go run` 링크 latency vs 스크립트 deadline 문제다.
- **reconnect full-cycle**: 보조 smoke는 단일 connect/disconnect만 구동하고, 이 non-interactive 세션에서는 interactive edge console을 반복 구동하는 reconnect full-cycle을 수행할 수 없다. reconnect generation 복구·monotonic 증가·stale fence 격리는 deterministic 테스트(`TestReconnectDisconnectGenerationsAreMonotonic`, `TestEdgeServerReconnectAfterUnregisterAccepted`, `TestDisconnectFencesOnlyMatchingGeneration`의 reconnect 케이스)로 닫았다. 남은 위험: interactive reconnect full-cycle 실구동은 미수행(환경 제약, 후속 follow-up 가능).

---

> **[IMPLEMENTING AGENT — BEFORE SAVING]** completion table, checklist, 변경 사항, 설계 결정, 검증 출력을 모두 채우고 active 파일을 유지한다.

## 섹션 소유권

| Section | Owner | Note |
|---------|-------|------|
| Header, 개요, Roadmap Targets, review 지시 | Fixed | 구현 에이전트 수정 금지 |
| 구현 완료 표·체크리스트 | Implementing agent | `[ ]`를 실제 완료 시 `[x]`로 변경 |
| 코드리뷰 전용 체크리스트 | Review agent | 구현 에이전트 수정 금지 |
| 계획 대비 변경 사항·주요 설계 결정·검증 출력 | Implementing agent | 실제 내용 필수 |
| 사용자 리뷰 요청 | Implementing agent | 선택 Milestone 잠금 결정만 사용 |
| 코드리뷰 결과 | Review agent appends | stub에는 없음 |

## 코드리뷰 결과

- 종합 판정: FAIL

### 차원별 평가

| 차원 | 평가 | 근거 |
|------|------|------|
| Correctness | Fail | old/new generation lease가 같은 provider resource에 공존할 때 old disconnect가 counter를 남길 수 있다. |
| Completeness | Fail | S12의 mixed-generation exactly-once settlement과 heartbeat/tunnel generation 경계 검증이 닫히지 않았다. |
| Test coverage | Fail | 새 테스트는 stale callback 당시 newer lease만 존재하는 케이스를 다루고, old/new lease overlap과 tunnel/heartbeat callback을 검증하지 않는다. |
| API contract | Pass | wire/config/protobuf 계약은 변경하지 않았고 disconnect handler 시그니처 호출부는 일관되게 갱신됐다. |
| Code quality | Pass | 신규 debug 출력, dead code, TODO는 발견되지 않았고 gofmt가 clean이다. |
| Implementation deviation | Fail | 계획이 요구한 normalized/tunnel handoff와 heartbeat-timeout generation 회귀가 부분적으로만 구현·검증됐다. |
| Verification trust | Fail | reviewer의 전체 race 명령은 transport reconnect timeout으로 실패해 제출된 PASS 출력과 일치하지 않았다. 해당 테스트 격리 3회는 PASS했으나 full-suite 결정성은 입증되지 않았다. |
| Spec conformance | Fail | SDD S12의 fenced generation lease 정확히 한 번 반환과 최종 counter 회복을 mixed-generation overlap에서 만족하지 못한다. |

### 발견된 문제

- Required — `apps/edge/internal/service/model_queue_release.go:61`: `dropLeasesForNodeLocked` 는 old-generation lease를 map에서 삭제하면서 counter를 줄이지 않고, `releaseNode` 는 resource의 최고 generation이 더 새로우면 123행에서 resource 전체를 건너뛴다. capacity 2에서 gen1 lease 존재 → gen2 reconnect/lease 예약 → 늦은 gen1 disconnect 순서면 gen1 lease는 사라지지만 `inFlight`는 2로 남고, gen2 terminal 후에도 1이 남는다. 삭제하는 lease별로 generation/resource counter를 정확히 decrement하거나 generation별 점유를 분리해 newer lease counter를 보존하고, 이 ordering을 capacity 2 regression/race test로 고정해야 한다.
- Required — `apps/edge/internal/transport/integration_test.go:529`, `apps/edge/internal/service/run_dispatch_internal_test.go:775`: heartbeat timeout 테스트는 authoritative disconnect handler의 generation·cleanup-before-event를 관측하지 않고, stale-generation dispatch 테스트는 normalized run 한 경로만 검증해 provider tunnel 호출을 놓친다. heartbeat reason/generation/ordering을 같은 recorder로 assert하고, normalized와 tunnel 모두에서 stale generation이 wire send 전에 release되며 전송이 0회임을 deterministic test로 추가해야 한다.
- Required — `agent-task/m-provider-resource-admission-ownership-alignment/08_disconnect_exclusion/CODE_REVIEW-cloud-G08.md:150`: 제출물은 `go test -race -count=1 ./apps/edge/internal/node ./apps/edge/internal/transport ./apps/edge/internal/service` PASS를 기록했지만 reviewer 재실행은 `TestEdgeServerReconnectAfterUnregisterAccepted` request timeout으로 FAIL했다. 격리 `-run '^TestEdgeServerReconnectAfterUnregisterAccepted$' -count=3` 은 PASS했으므로, 후속 루프에서 full race suite를 fresh cache로 재실행하고 재현되면 transport test의 2초 request deadline/동기화를 안정화한 뒤 전체 PASS 원문을 기록해야 한다.

### 다음 단계

- FAIL follow-up: generation-scoped lease counter settlement을 보정하고 missing heartbeat/tunnel regression과 deterministic full race evidence를 추가한 뒤 fresh routing된 plan/review pair로 재리뷰한다.
