<!-- task=m-provider-resource-admission-ownership-alignment/09+08_terminal_unavailable_outcome 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/09+08_terminal_unavailable_outcome, plan=1, tag=REVIEW_REFACTOR

## Archive Evidence Snapshot

- 선행 task: `agent-task/m-provider-resource-admission-ownership-alignment/09+08_terminal_unavailable_outcome`
- 선행 판정: FAIL; Required 1, Suggested 0, Nit 1.
- archived pair:
  - `agent-task/m-provider-resource-admission-ownership-alignment/09+08_terminal_unavailable_outcome/plan_local_G06_0.log`
  - `agent-task/m-provider-resource-admission-ownership-alignment/09+08_terminal_unavailable_outcome/code_review_local_G06_0.log`
- Required: production `resolveProviderPoolCandidates` zero-candidate 경로가 일반 error를 반환해 queue의 `resolveResolverError` 분기에 머물며, 실제 disconnect 후 terminal unavailable이 발생하지 않는다. 추가된 no-candidate 테스트는 수동 `nil, nil` resolver로 production closure를 우회하고, full-candidate 테스트는 빈 store 때문에 capacity 검사 전에 후보가 탈락한다.
- Nit: `resolveResolverError`가 item을 제거·통지한다는 주석과 `TestModelQueueTerminalUnavailableOnResolverError` 이름이 실제 queue 유지 동작과 반대다.
- 영향 파일: `apps/edge/internal/service/model_queue_types.go`, `model_queue_admission.go`, `provider_resolution.go`, `model_queue_admission_test.go`.
- 검증 evidence: fresh `gofmt -d` 출력 없음, `go test -count=1 ./apps/edge/internal/service -run 'Disconnect|Unavailable|Fallback'` PASS, `go test -count=1 -race ./apps/edge/internal/service -run 'TestModelQueue'` PASS, `go test -count=1 ./apps/edge/...` PASS. 이 결과는 production no-live resolver 분기를 통과하는 테스트가 없어 Required를 해소하지 못한다.
- 세부 판정이 필요할 때만 위 `code_review_local_G06_0.log`를 좁게 재독한다.

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

> **[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-G06.md` → `code_review_local_G06_1.log`, `PLAN-local-G06.md` → `plan_local_G06_1.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-provider-resource-admission-ownership-alignment/09+08_terminal_unavailable_outcome/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 plan skill로 fresh-routed pair를 메모리에 준비하고, current pair를 archive한 후 code-review가 다음 active pair를 byte-for-byte materialize하거나 `USER_REVIEW.md`를 작성한다. 미해결 Required는 별도 후속 지시가 없어도 반드시 FAIL과 정확히 하나의 next state를 만든다. 기본 next state는 follow-up pair이고, 유효한 Milestone-lock gate만 그 pair 대신 `USER_REVIEW.md`를 만든다. 유효한 gate는 Required/FAIL로 분류한다. Suggested는 WARN follow-up을 만들고, Nit-only는 모든 차원이 Pass이면 PASS할 수 있다.
4. PASS이고 task group이 `m-provider-resource-admission-ownership-alignment`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한다. 사용자-facing 보고는 `USER_REVIEW.md` 또는 `complete.log`가 생성된 뒤에만 한다.
6. WARN/FAIL follow-up pair가 생성된 경우 사용자에게 보고하거나 묻지 않고 review 실행을 종료한다. archived current pair와 fresh pair 및 reviewer 실행 종료가 runtime의 `implementation-ready` 신호다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| REVIEW_REFACTOR-1 Production no-live candidate outcome | [x] |

## 구현 체크리스트

- [x] production provider-pool resolver의 no-live-candidate error만 typed unavailable로 식별하고 catalog/config/programming resolver error는 재평가 대기로 유지한다.
- [x] 실제 registry/store/catalog/`resolveQueueCandidatesClosure`를 사용해 마지막 live candidate disconnect의 즉시 typed unavailable, queue/candidate pressure 제거, 중복 terminal 없음을 검증한다.
- [x] remaining-full candidate와 production resolver fault/recovery를 실제 live fixture로 검증하고 오해를 유발하는 주석·테스트 이름을 현재 의미에 맞게 정리한다.
- [x] `gofmt -d`, targeted, race, `go test -count=1 ./apps/edge/...` 실제 출력을 기록한다.
- [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-*-G??.md`를 `code_review_local_G06_1.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_local_G06_1.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리 `agent-task/m-provider-resource-admission-ownership-alignment/09+08_terminal_unavailable_outcome/`를 `agent-task/archive/YYYY/MM/m-provider-resource-admission-ownership-alignment/09+08_terminal_unavailable_outcome/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-provider-resource-admission-ownership-alignment`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-provider-resource-admission-ownership-alignment/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 plan skill을 실행하고 `finalize-task-routing`의 fresh 결과와 일치하는 다음 active pair를 작성하며 `complete.log`를 작성하지 않는다.
- [ ] `USER_REVIEW.md` 또는 `complete.log`가 아직 없으면 사용자에게 질문·준비 완료 보고·종료 응답을 하지 않고 review 실행을 종료한다. archived current pair와 fresh pair가 `implementation-ready` 상태이며 리뷰 에이전트가 직접 구현하거나 별도 handoff artifact를 만들지 않는다.
- [ ] USER_REVIEW이면 template 기준으로 `USER_REVIEW.md`를 작성하고 active pair와 `complete.log`를 남기지 않는다.

## 계획 대비 변경 사항

- 계획의 단순 `len(candidates)==0` 전체 terminal 처리 대신, catalog에 매핑된 configured provider의 연결 owner가 하나도 없을 때만 `errProviderUnavailable`을 wrap했다. 연결된 provider가 `enabled=false`, unhealthy 또는 capacity/config filter로 일시 제외된 경우까지 terminal 처리하면 기존 live refresh waiter가 조기 종료되므로, 이 경로는 일반 resolver error와 재평가 대기로 유지했다.
- 계획의 targeted 명령은 그대로 재실행했고, 신규 test 이름 확인을 위해 같은 정규식을 `-v`로 한 번 추가 실행했다.
- 외부 provider/transport full-cycle은 이 child의 범위 밖이며 계획대로 실행하지 않았다. production registry/store/catalog closure와 Edge package 전체 회귀로 현 no-live outcome 범위를 검증했다.

## 주요 설계 결정

- `resolveProviderPoolCandidates`는 catalog entry 누락과 connected-but-filtered provider를 일반 resolver error로 유지하고, registry가 비었거나 mapped provider를 소유한 configured Node가 모두 disconnected인 경우만 기존 `errProviderUnavailable` sentinel을 wrap한다.
- queue tri-state는 `errors.Is(err, errProviderUnavailable)`만 `resolveNoCandidates`로 낮춘다. `resolveResolverError`는 item과 deadline을 유지해 이후 config/catalog 회복 pump가 같은 waiter를 dispatch할 수 있게 한다.
- 회귀 fixture는 실제 `Registry`, `NodeStore`, model catalog, root provider-pool policy, `Service.resolveQueueCandidatesClosure`를 공유한다. disconnect test는 unrelated Node를 연결 상태로 남겨 empty-registry shortcut이 아닌 production filtered-zero branch까지 통과한다.
- disconnect terminal 검증은 pending, provider candidate pressure, in-flight, lease가 모두 0인지와 재 pump 후 두 번째 terminal delivery가 없는지 함께 확인한다.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `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으로 처리한다._

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

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

- production `resolveQueueCandidatesClosure`가 no connected/live provider를 typed terminal unavailable로 낮추고 catalog/config/programming error는 queue에 유지하는가.
- 마지막 candidate disconnect 후 waiter가 `queue_timeout_ms` 전에 종료되고 pending/candidate pressure가 0이며 재 pump가 중복 terminal을 만들지 않는가.
- remaining-full 테스트가 실제 live Node/provider record로 capacity 분기를 타고, resolver fault 테스트가 production closure 회복을 검증하는가.
- outer OpenAI-compatible envelope/status/field와 transport hook, reconnect, offline snapshot에 계획 밖 변경이 없는가.

## 검증 결과

실제 stdout/stderr를 각 항목에 붙인다. 명령을 바꾸면 `계획 대비 변경 사항`에 대체 명령과 이유를 기록한다.

### cache directory

```text
$ mkdir -p /tmp/iop-gocache-terminal-unavailable-followup
(출력 없음, exit 0)
```

### gofmt

```text
$ gofmt -d apps/edge/internal/service/model_queue_types.go apps/edge/internal/service/model_queue_admission.go apps/edge/internal/service/provider_resolution.go apps/edge/internal/service/model_queue_admission_test.go
(출력 없음, exit 0)
```

### targeted

```text
$ GOCACHE=/tmp/iop-gocache-terminal-unavailable-followup go test -count=1 ./apps/edge/internal/service -run 'Disconnect|Unavailable|ResolverError|Fallback|AllCandidatesFull'
ok  	iop/apps/edge/internal/service	0.050s

신규 production regression 실행 확인(`-v` 보조 실행):
- `TestProviderPoolQueuedDisconnectReturnsUnavailableImmediately`
- `TestModelQueueStaysQueuedWhenAllCandidatesFull`
- `TestProviderPoolQueuedResolverErrorStaysQueuedAndRecovers`
```

### race

```text
$ GOCACHE=/tmp/iop-gocache-terminal-unavailable-followup go test -count=1 -race ./apps/edge/internal/service -run '^(TestProviderPoolQueuedDisconnectReturnsUnavailableImmediately|TestModelQueueStaysQueuedWhenAllCandidatesFull|TestProviderPoolQueuedResolverErrorStaysQueuedAndRecovers)$'
ok  	iop/apps/edge/internal/service	1.063s
```

### edge regression

```text
$ GOCACHE=/tmp/iop-gocache-terminal-unavailable-followup go test -count=1 ./apps/edge/...
ok  	iop/apps/edge/cmd/edge	0.178s
ok  	iop/apps/edge/internal/bootstrap	0.438s
ok  	iop/apps/edge/internal/configrefresh	0.073s
ok  	iop/apps/edge/internal/controlplane	4.573s
ok  	iop/apps/edge/internal/edgecmd	0.062s
ok  	iop/apps/edge/internal/edgevalidate	0.018s
ok  	iop/apps/edge/internal/events	0.013s
ok  	iop/apps/edge/internal/input	0.016s
ok  	iop/apps/edge/internal/input/a2a	0.015s
ok  	iop/apps/edge/internal/node	0.011s
ok  	iop/apps/edge/internal/openai	7.081s
ok  	iop/apps/edge/internal/opsconsole	0.062s
ok  	iop/apps/edge/internal/service	6.518s
ok  	iop/apps/edge/internal/transport	4.569s
```

---

> **[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 them; after filling its sections it ends its execution and runtime assigns a distinct review-agent instance |
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
| 구현 항목별 완료 여부 | Implementing agent | 항목 이름은 유지하고 `[ ]` → `[x]`만 변경 |
| 구현 체크리스트 | Implementing agent | 항목 텍스트/순서는 유지하고 `[ ]` → `[x]`만 변경; 마지막 항목 필수 |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | placeholder를 실제 내용으로 교체 |
| 사용자 리뷰 요청 | Implementing agent | Milestone lock decision만 사용; 직접 user prompt 금지 |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Implementing agent must not modify |
| 검증 결과 | Implementing agent | 명령 표제는 유지하고 실제 stdout/stderr를 채움 |
| 코드리뷰 결과 | Review agent appends | 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와 split task directory를 archive한다.
