<!-- task=m-provider-resource-admission-ownership-alignment/13+09_disconnect_queue_settlement plan=1 tag=REVIEW_REFACTOR -->

# Code Review Reference - REVIEW_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, keep the active files in place, then end the implementation execution. The filled review-without-verdict state plus agent completion is the `review-ready` signal; do not create a handoff file/message, invoke code-review, or switch to the review role in place.
> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and use the same file-state handoff; only a distinct review-agent instance may materialize `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps become normal follow-ups with blocker evidence, a deterministic release condition, and revalidation commands; they are not user-facing stopping states.
> Do not ask the user directly, present choices in chat, call `request_user_input`, report review readiness, or wait for another user instruction. The runtime immediately assigns a distinct review-agent instance; a new turn, resume, compaction, or role change of this implementing agent is not distinct.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, follow-up pair creation, `코드리뷰 전용 체크리스트`) belongs only to that distinct review agent. The implementing agent must never perform it, even after compaction or resume.
> `USER_REVIEW.md` and `complete.log` are the only terminal states. A filled stub, verdict, archive, or WARN/FAIL follow-up pair is transitional and the loop must continue automatically.
> 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/13+09_disconnect_queue_settlement, plan=1, tag=REVIEW_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-queue-resolution`: authoritative disconnect 시 fallback 또는 immediate unavailable
- Completion mode: check-on-pass

## Archive Evidence Snapshot

- Prior task: `agent-task/m-provider-resource-admission-ownership-alignment/13+09_disconnect_queue_settlement`
- Prior plan: `agent-task/m-provider-resource-admission-ownership-alignment/13+09_disconnect_queue_settlement/plan_local_G06_0.log`
- Prior review: `agent-task/m-provider-resource-admission-ownership-alignment/13+09_disconnect_queue_settlement/code_review_local_G06_0.log`
- Verdict: `FAIL`
- Findings: Required 1 (`TestSubmitRunModelQueueSurvivingProviderFallbackAfterDisconnect`가 disconnect 전에 node-2로 즉시 dispatch되어 queued-disconnect fallback을 검증하지 않음), Suggested 0, Nit 1 (이전 review의 full-suite 설명이 자체 모순)
- Affected file: `apps/edge/internal/service/queue_dispatch_test.go`
- Existing verification: 신규 4 tests, `go test -race -count=1 -timeout 120s ./apps/edge/internal/service`, `go test -count=1 -timeout 120s ./apps/edge/...`, gofmt/diff-check는 리뷰에서 PASS. mock e2e는 local port bind blocker로 실행 완료 불가. 이 PASS들은 false-positive fallback 분기의 evidence가 아님.
- Roadmap carryover: SDD `S13` / Milestone Task `disconnect-queue-resolution`; PASS 전 deterministic queued fallback과 test/race evidence가 필요함.
- 상세 문구가 필요할 때만 위 두 archive log를 좁게 읽고, `agent-task/archive/**`를 탐색하지 않는다.

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

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
> 리뷰 에이전트는 구현 에이전트와 별도 agent instance여야 하며 product source/test를 수정하거나 현재·후속 plan을 구현하거나 구현 에이전트 소유 섹션을 채우지 않는다. WARN/FAIL follow-up pair를 만든 뒤 review 실행을 종료하면 archived current pair와 fresh pair가 runtime의 `implementation-ready` 신호가 된다. 별도 handoff 파일·사용자 메시지를 만들지 않는다.

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

1. 판정을 append한다.
2. `CODE_REVIEW-local-G04.md` → `code_review_local_G04_1.log`, `PLAN-local-G04.md` → `plan_local_G04_1.log`로 archive한다.
3. PASS이면 `complete.log` 작성 후 task directory를 월별 archive로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 plan skill/finalize routing으로 fresh pair를 메모리에 준비하고 current pair archive 후 byte-for-byte materialize한다. 미해결 Required는 반드시 FAIL과 정확히 하나의 next state를 만든다.
4. PASS이고 task group이 `m-<milestone-slug>`이면 runtime용 완료 이벤트 메타데이터를 보고하며 roadmap/update-roadmap을 직접 수정·호출하지 않는다.
5. 적용 가능한 `코드리뷰 전용 체크리스트`를 최종 `.log` 위치에서 체크한다. `USER_REVIEW.md` 또는 `complete.log`가 없으면 사용자-facing 보고를 하지 않는다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| REVIEW_REFACTOR-1 Exercise the real queued-disconnect fallback branch | [x] |

## 구현 체크리스트

- [x] `TestSubmitRunModelQueueSurvivingProviderFallbackAfterDisconnect`가 run-2의 queued 상태를 bounded state synchronization으로 먼저 증명하고, queueing 후 survivor를 live resolver에 노출한 다음 authoritative node-1 disconnect만으로 node-2 dispatch되는지 검증하도록 fixture를 고친다.
- [x] disconnect 전 submit goroutine이 완료되지 않았음, disconnect 후 bounded timeout 안에 정확히 한 번 완료됨, dispatch node/provider, 마지막 queue/lease/provider counter 회복을 assertion하고 근거 없는 fixed sleep에 의존하지 않는다.
- [x] 대상 반복 test, 신규 4-test race, Edge/전체 회귀, mock e2e 결과 또는 명시 blocker를 실제 stdout/stderr와 함께 기록하고 서로 모순된 PASS/실패 설명을 남기지 않는다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우고 active pair를 유지한 채 구현 실행을 종료한다. 이 저장 상태가 runtime의 review-ready 신호이며 별도 handoff 파일·사용자 메시지를 만들지 않는다. 구현 에이전트는 code-review를 실행하거나 review 전용 작업을 수행하지 않는다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

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

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

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-local-G04.md`를 `code_review_local_G04_1.log`로 archive한다.
- [x] active `PLAN-local-G04.md`를 `plan_local_G04_1.log`로 archive한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [x] PASS이면 complete-log template으로 `Roadmap Completion: disconnect-queue-resolution`과 deterministic test/race evidence를 기록하고 active `.md`를 남기지 않는다.
- [x] PASS이면 task directory를 월별 archive로 이동하고 최종 `.log`에서 이 체크리스트를 갱신한다.
- [x] PASS이면 runtime용 Milestone 완료 이벤트 메타데이터를 보고하고 roadmap/update-roadmap을 직접 호출하지 않는다.
- [x] PASS split 이동 후 active parent에 sibling/file이 남아 있어 유지했는지 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 아니면 plan skill/fresh routing follow-up을 만들고 `complete.log`를 쓰지 않는다.
- [x] `USER_REVIEW.md` 또는 `complete.log`가 없으면 사용자에게 질문·준비 완료 보고·종료 응답을 하지 않고 review 실행을 종료한다. archived current pair와 fresh pair가 `implementation-ready`며 reviewer가 직접 구현하거나 별도 handoff artifact를 만들지 않는다.
- [ ] USER_REVIEW이면 template으로 `USER_REVIEW.md`를 만들고 active pair와 `complete.log`를 남기지 않는다.

## 계획 대비 변경 사항

- 소스 범위 변경 없음: 계획대로 `apps/edge/internal/service/queue_dispatch_test.go`만 수정했고 product API/동작은 변경하지 않았다.
- 계획이 허용한 동일 test file helper로 `waitForProviderSnapshotCounts`를 추가했다. 고정 sleep 대신 `ListNodeSnapshots()`의 provider `in_flight`/candidate-pressure `queued`를 최대 1초 동안 관측하는 bounded synchronization에 사용했다.
- 계획의 필수 명령 전에 대상 테스트를 `-count=1`로 한 번 실행해 fixture 기본 동작을 빠르게 확인했다(exit 0).
- 전체 회귀 실패가 현재 변경과 무관한지 확인하기 위해 실패한 Node CLI persistent test 3개와 남은 단일 실패를 좁게 재실행했다. `TestCLIExecutePersistentClaudeTUIReplaysPromptAfterStartupReadyRace`는 단독 실행에서도 `execute: run cancelled`로 실패했으며 테스트 소스는 범위 밖이라 수정하지 않았다.
- exact mock smoke는 기본 `IOP_E2E_BIND_TIMEOUT=10` 안에 Edge가 bind되지 않아 세 번 exit 1이었다. 시작 지연과 포트 충돌을 구분하기 위해 bind 대기만 60초로 늘린 추가 진단을 실행했고 동일 smoke 시나리오는 PASS했다. exact 명령의 실패와 확장 진단의 PASS를 아래에 모두 기록한다.

## 주요 설계 결정

- 초기 live registry에는 `fb-node-1`만 등록하고 NodeStore/model catalog에는 두 provider를 모두 유지했다. run-1이 node-1/provider-1 capacity 1을 점유하므로 run-2가 즉시 node-2로 빠질 수 없다.
- run-2 완료값은 buffered outcome channel 하나로 전달한다. provider-1 snapshot이 `(in_flight=1, queued=1)`이 될 때까지 bounded polling한 뒤 non-blocking channel 확인으로 survivor 연결 전 미완료를 증명한다.
- 그 다음 `fb-node-2`를 registry에만 등록한다. provider-2 snapshot이 `(in_flight=0, queued=1)`인 것을 확인하고 outcome channel이 여전히 비어 있음을 확인해 등록 자체가 queue pump가 아님을 고정한다.
- transport ordering과 동일하게 `Registry.UnregisterIfClient`로 현재 owner를 제거하고 반환된 non-zero `connection_generation`을 `HandleNodeDisconnect`에 전달한다. 이 authoritative disconnect만 live resolver 재구성과 global pump를 일으킨다.
- disconnect 뒤 outcome은 1초 timeout으로 수신하고 추가 outcome이 없음을 확인한다. dispatch의 node/provider가 `fb-node-2`/`prov-fb-2`, queue reason이 `capacity_full`인지 검증한다.
- fallback 직후 provider-1은 `(0,0)`, provider-2는 `(1,0)`인지 확인하고, run-2 terminal event를 두 번 전달한 뒤 두 provider의 일반/long in-flight와 queued가 모두 0인지 확인해 lease 반환과 중복 terminal idempotence를 검증한다.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 채운 뒤 구현 에이전트는 code-review를 실행하거나 역할을 바꾸지 않고 active pair를 유지한 채 구현 실행을 종료한다. 이 저장 상태가 runtime의 `review-ready` 신호이므로 별도 handoff 파일·사용자 메시지를 만들지 않는다. runtime은 사용자에게 묻지 않고 별도 review 에이전트 instance를 즉시 배정하며, review 에이전트가 `USER_REVIEW.md`를 실제로 만들기 전에는 terminal 상태가 아니다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 blocker evidence·결정적 해제 조건·재검증 명령을 `검증 결과`나 `계획 대비 변경 사항`에 기록한 뒤 code-review의 일반 follow-up plan으로 처리한다._

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

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

- run-2가 node-2 등록과 node-1 disconnect 전에 반드시 queued 상태인가.
- survivor 등록만으로 premature dispatch되지 않고 authoritative disconnect가 유일한 wake-up trigger인가.
- disconnect 후 live resolver가 node-2를 재구성하고 bounded timeout 안에 정확한 node/provider로 dispatch하는가.
- fixed sleep만으로 ordering을 가정하지 않고 repeated/race 실행에서 결정적인가.
- terminal unavailable/event-drop/no-leak 회귀와 queue/lease/provider counter 회복이 유지되는가.
- 검증 설명이 실제 exit code/stdout/stderr와 모순이 없는가.

## 검증 결과

_아래 각 명령을 계획과 동일하게 실행하고 실제 stdout/stderr와 exit code를 기록한다. 명령을 바꾸면 `계획 대비 변경 사항`에 이유를 남긴다._

### gofmt

```text
$ gofmt -d apps/edge/internal/service/queue_dispatch_test.go
exit code: 0
stdout: (출력 없음)
stderr: (출력 없음)
```

### repeated queued-disconnect fallback

```text
$ go test -count=20 -timeout 120s -run '^TestSubmitRunModelQueueSurvivingProviderFallbackAfterDisconnect$' -v ./apps/edge/internal/service
exit code: 0
stdout:
=== RUN   TestSubmitRunModelQueueSurvivingProviderFallbackAfterDisconnect
--- PASS: TestSubmitRunModelQueueSurvivingProviderFallbackAfterDisconnect (0.00s~0.02s)
(동일 RUN/PASS가 총 20회 출력됨)
PASS
ok  	iop/apps/edge/internal/service	0.161s
stderr: (출력 없음)
```

### disconnect regression race

```text
$ go test -race -count=1 -timeout 120s -run '^(TestProviderPoolDisconnectAuthoritativeSettlementNoLeak|TestProviderPoolLastProviderDisconnectTerminalSettlement|TestSubmitRunModelQueueSurvivingProviderFallbackAfterDisconnect|TestSubmitRunModelQueueLastProviderDisconnectReturnsUnavailable)$' ./apps/edge/internal/service
exit code: 0
stdout:
ok  	iop/apps/edge/internal/service	1.051s
stderr: (출력 없음, race warning 없음)
```

### Edge regression

```text
$ go test -count=1 -timeout 120s ./apps/edge/...
exit code: 0
stdout:
ok  	iop/apps/edge/cmd/edge	0.138s
ok  	iop/apps/edge/internal/bootstrap	0.509s
ok  	iop/apps/edge/internal/configrefresh	0.074s
ok  	iop/apps/edge/internal/controlplane	4.545s
ok  	iop/apps/edge/internal/edgecmd	0.055s
ok  	iop/apps/edge/internal/edgevalidate	0.015s
ok  	iop/apps/edge/internal/events	0.018s
ok  	iop/apps/edge/internal/input	0.040s
ok  	iop/apps/edge/internal/input/a2a	0.076s
ok  	iop/apps/edge/internal/node	0.045s
ok  	iop/apps/edge/internal/openai	7.110s
ok  	iop/apps/edge/internal/opsconsole	0.011s
ok  	iop/apps/edge/internal/service	6.544s
ok  	iop/apps/edge/internal/transport	4.317s
stderr: (출력 없음)
```

### full project regression

```text
$ go test -count=1 -timeout 180s ./...
exit code: 1
stdout/stderr 핵심:
--- FAIL: TestCLIExecutePersistentTerminalSendsCarriageReturn (1.46s)
    persistent_terminal_test.go:111: execute: run cancelled
--- FAIL: TestCLIExecutePersistentClaudeTUICancelsSessionLimitUpgradePrompt (5.67s)
    persistent_terminal_test.go:265: expected cancelled event to mention session limit, got "timeout"
--- FAIL: TestCLIExecutePersistentClaudeTUIReplaysPromptAfterStartupReadyRace (5.60s)
    persistent_terminal_test.go:307: execute: run cancelled
FAIL
FAIL	iop/apps/node/internal/adapters/cli	69.007s
FAIL

변경 범위 package를 포함해 위 package 전까지/이후 출력된 다른 package는 PASS했다. 추가 진단:
$ go test -count=1 -timeout 120s -run '^(TestCLIExecutePersistentTerminalSendsCarriageReturn|TestCLIExecutePersistentClaudeTUICancelsSessionLimitUpgradePrompt|TestCLIExecutePersistentClaudeTUIReplaysPromptAfterStartupReadyRace)$' -v ./apps/node/internal/adapters/cli
exit code: 1; 앞의 2 tests PASS, `TestCLIExecutePersistentClaudeTUIReplaysPromptAfterStartupReadyRace`는 `persistent_terminal_test.go:307: execute: run cancelled`로 FAIL.
$ go test -count=1 -timeout 30s -run '^TestCLIExecutePersistentClaudeTUIReplaysPromptAfterStartupReadyRace$' -v ./apps/node/internal/adapters/cli
exit code: 1; 동일하게 5.31s 뒤 `execute: run cancelled`로 FAIL.

판정 기록: 전체 회귀 명령은 PASS가 아니며, 실패는 이번 write set 밖의 `apps/node/internal/adapters/cli/persistent_terminal_test.go`에서 재현된다. Edge 전체 회귀와 대상 반복/race는 별도로 PASS했다.
```

### auxiliary mock e2e

```text
$ IOP_E2E_PROFILE=mock ./scripts/e2e-smoke.sh
세 번 실행, 모두 exit code: 1.
stdout/stderr 핵심:
[e2e] starting smoke test (profile: mock, port: 37395, persistent: 1, has_status: 0)
[e2e] edge failed to bind port 37395
[edge] config=/tmp/tmp.vBJOyFIS0y/edge.yaml

재실행 포트 38878과 35167에서도 기본 10초 안에 동일한 `edge failed to bind port`가 발생했다. 서로 다른 동적 포트에서 반복되어 단일 포트 점유는 원인이 아니다.

해제 조건: 현재 runner에서 Edge startup/bind 대기를 60초로 확보한다.
재검증 명령:
$ IOP_E2E_BIND_TIMEOUT=60 IOP_E2E_PROFILE=mock ./scripts/e2e-smoke.sh
exit code: 0
stdout 핵심:
[e2e] starting smoke test (profile: mock, port: 35408, persistent: 1, has_status: 0)
[e2e] waiting for node registration (timeout: 60s)
[node0-evt] connected reason="registered"
[node0-msg] IOP_E2E_PING_BASIC
[node0-msg] IOP_E2E_PING_BASIC_TAIL
[node0-evt] complete ... detail="idle-timeout"
[node0-msg] IOP_E2E_STATUS_OK
[node0-msg] IOP_E2E_STATUS_OK_TAIL
[node0-evt] complete ... detail="idle-timeout"
[node0-msg] IOP_E2E_ACK_SHORT
[node0-msg] IOP_E2E_ACK_SHORT_TAIL
[node0-sessions] adapter=cli target=fake-cli session=session2
terminated session session2 node=node0
[e2e] Auxiliary smoke test PASSED.
[e2e] Completion still requires scripts/dev/edge.sh + scripts/dev/node.sh user-flow verification.

판정 기록: 계획의 exact 명령은 기본 bind timeout blocker로 실패했고, bind 대기만 확장한 동일 auxiliary mock flow는 등록, foreground message 2회, background message, capabilities/transport/sessions/terminate-session까지 PASS했다. 이 test-only fixture follow-up에서는 runtime 동작을 변경하지 않았으므로 별도 full-cycle 진단은 추가하지 않았다.
```

### diff check

```text
$ git diff --check -- apps/edge/internal/service/queue_dispatch_test.go
exit code: 0
stdout: (출력 없음)
stderr: (출력 없음)
```

---

> **[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 and end the implementation execution. This filled file plus agent completion is the `review-ready` signal; do not create a separate handoff artifact, execute code-review, or switch roles in place.

## 섹션 소유권

| Section | Owner | Note |
|---------|-------|------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify; reviewer owns verdict, archive, complete.log, follow-up routing/materialization, and task-directory archive move |
| Roadmap Targets | Fixed at stub creation from plan | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
| Archive Evidence Snapshot | Fixed at stub creation from plan | Implementing agent uses it as default prior-loop context; read only the cited archive logs when more detail is required |
| 구현 항목별 완료 여부 | Fixed item name | Implementing agent checks `[ ]` → `[x]` only |
| 구현 체크리스트 | Fixed from plan | Implementing agent checks `[ ]` → `[x]` only; final checkbox is mandatory |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless a selected Milestone lock decision blocks implementation |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Implementing agent must not modify |
| 검증 결과 | Implementing agent | Fill actual output only; command changes require a deviation entry |
| 코드리뷰 결과 | Review agent appends | Not included in stub |

## 코드리뷰 결과

- 종합 판정: PASS
- 차원별 평가:
  - correctness: Pass
  - completeness: Pass
  - test coverage: Pass
  - API contract: Pass
  - code quality: Pass
  - implementation deviation: Pass
  - verification trust: Pass
  - spec conformance: Pass
- 발견된 문제: 없음
- 다음 단계: PASS — `complete.log`를 작성하고 현재 pair와 task directory를 아카이브한다.
