<!-- task=m-provider-resource-admission-ownership-alignment/01_provider_resource_state plan=3 tag=REVIEW_REVIEW_REVIEW_REFACTOR -->

# Orphan Provider Release Queue Pump 회귀 보완 계획

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

구현과 검증을 완료한 뒤 `CODE_REVIEW-local-G03.md`의 구현 에이전트 소유 섹션에 실제 변경 내용과 stdout/stderr를 채우고 active 파일을 그대로 둔 채 리뷰 준비 상태를 보고한다. 종결 판정, log rename, `complete.log`, archive 이동은 code-review 전용이다. 선택된 Milestone의 `구현 잠금 > 결정 필요`가 실제 구현을 막을 때만 review stub의 `사용자 리뷰 요청`에 연결 근거와 실행 명령을 기록하고 멈춘다. 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하거나 `USER_REVIEW.md`·archive log·`complete.log`를 만들지 않는다. 환경/secret/서비스 차단, 일반 범위 변경, 후속 에이전트가 보완할 수 있는 evidence 공백은 사용자 리뷰 요청이 아니다.

## 배경

orphan resource를 live `NodeStore` eligibility에서 제외하는 구현과 기존 검증 명령은 통과했다. 그러나 추가된 `TestProviderResourceRemovedProviderDoesNotReadmitQueuedCandidate`는 두 번째 요청을 대기열에 넣기 전에 provider를 제거하고 마지막 slot을 반환한 뒤에야 두 번째 admission을 시작한다. 따라서 이전 FAIL의 핵심인 `releaseSlotLocked`가 이미 대기 중인 stale candidate를 대상으로 `tryDispatchLocked`를 호출하는 경로를 검증하지 못한다.

## 사용자 리뷰 요청 흐름

선택된 Milestone lock 결정만 active `CODE_REVIEW-local-G03.md`의 `사용자 리뷰 요청` 템플릿에 기록한다. 구현 중 직접 사용자 prompt는 금지하며 code-review가 요청의 정당성 검증과 실제 `USER_REVIEW.md` 작성을 소유한다.

## Archive Evidence Snapshot

- Prior plan: `agent-task/m-provider-resource-admission-ownership-alignment/01_provider_resource_state/plan_local_G05_2.log`
- Prior review: `agent-task/m-provider-resource-admission-ownership-alignment/01_provider_resource_state/code_review_local_G05_2.log`
- Verdict: `FAIL`
- Findings: Required 1, Suggested 0, Nit 0
- Affected files: `apps/edge/internal/service/model_queue_admission_test.go`; 회귀가 구현 결함을 드러낼 때만 `model_queue_types.go`, `model_queue_admission.go`, `model_queue_release.go`
- Verification evidence:
  - PASS: `git diff --check`와 계획된 Go 파일 `gofmt -d`
  - PASS: orphan/provider-state/long-state targeted race를 `-count=10`으로 재실행
  - PASS: 정확한 두 `packages/go/config` long-context validation 테스트
  - PASS: `GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/edge/...`
  - GAP: 현재 orphan 회귀는 release 전에 두 번째 요청이 queue에 없어서 `releaseSlotLocked` → `tryDispatchLocked` → stale candidate 전이를 실행하지 않음
- Roadmap carryover: `provider-state`/S01과 `long-state`/S04 완료 evidence를 유지하며 Task id를 확대하지 않는다.
- Allowed narrow reread: 위 prior plan/review log 두 파일만 필요할 때 읽는다.

## 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:
  - `provider-state`: provider 전역 capacity/in-flight
  - `long-state`: provider 전역 long-context slot과 정적 total-context 검증 유지
- Completion mode: check-on-pass

## 분석 결과

### SDD 기준

- 승인된 SDD S01/S04와 Evidence Map은 provider 전역 일반·long counter의 deterministic/race evidence를 요구한다.
- 이번 보완은 제거된 provider의 마지막 release가 stale queued candidate를 다시 활성화하지 않는다는 provider-state lifecycle 전제의 실제 queue-pump 증거를 복구한다.

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

- 현재 테스트는 `provider 제거 → 마지막 release → 두 번째 admit` 순서라 release 시 queue가 비어 있다.
- 필요한 순서는 `첫 요청 admit → 두 번째 admit goroutine 시작 → queue 길이 1 확인 → provider 제거 → 마지막 release → 두 번째 요청 timeout/cancel 및 resource 미재생성 확인`이다.

### 분할 판단

- 단일 deterministic 회귀의 arrange 순서와 assertion을 고치는 작업이므로 기존 `01_provider_resource_state` 후속 루프에서 유지한다.

### 범위 결정 근거

- production 구현은 코드 검사상 live store/orphan을 admission에서 제외한다. 먼저 정확한 회귀를 추가하고, 그 테스트가 실패할 때만 `resourceForCandidateLocked`와 admission/release의 최소 동작 수정으로 범위를 넓힌다.
- lease identity FSM, cross-group global scheduler, disconnect generation, queue ownership과 offline snapshot은 sibling subtask 범위이므로 수정하지 않는다.

### 빌드 등급

- `local-G03`: 한 테스트의 deterministic arrange 순서와 제한된 eligibility 경로만 다루며 외부 환경이나 제품 판단이 필요 없다.

## 구현 체크리스트

- [ ] capacity 1 첫 요청이 실행 중일 때 두 번째 요청을 실제 queue에 넣고, provider 제거 후 마지막 release가 queue pump를 실행해도 stale candidate가 dispatch되거나 resource를 재생성하지 않는 회귀로 수정한다.
- [ ] 정확한 orphan queue-pump 회귀를 반복 실행하고 기존 provider-state/long-state race, config validation, Edge 전체 fresh 검증을 재실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_REVIEW_REFACTOR-1] Release-triggered stale queue pump 실제 검증

#### 문제

`apps/edge/internal/service/model_queue_admission_test.go:671-683`은 “Queue a second request”라고 설명하지만 실제로는 첫 slot을 반환한 뒤 두 번째 `admitWithReason`을 동기 호출한다. 이 때문에 `releaseSlotLocked`가 대기 중 stale candidate를 pump하는 핵심 회귀가 빠져 있다.

#### 해결 방법

첫 요청으로 capacity를 채운 뒤 두 번째 admission을 goroutine에서 시작하고 `waitForQueueLen(t, m, "g-stale", 1)`로 queue 진입을 확인한다. 그 다음 provider를 store에서 제거해 resource를 orphan으로 만들고 첫 slot을 release한다. 두 번째 admission이 성공해서는 안 되며 bounded timeout/cancel 결과를 반환해야 하고, queue 정리 뒤 resource map에 해당 key가 활성 상태로 재생성되지 않았음을 확인한다.

정확한 회귀가 production 결함을 드러내면 live store에 없는 provider 또는 orphan resource를 `findAvailableNodeLocked`, immediate reserve, queued dispatch 어디에서도 선택하지 않도록 최소 수정한다.

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

- [ ] `apps/edge/internal/service/model_queue_admission_test.go`: release 전에 두 번째 요청이 실제 queue에 있음을 확인하고 dispatch 부재, terminal 결과, resource 미재생성을 assertion한다.
- [ ] 필요할 때만 `apps/edge/internal/service/model_queue_types.go`, `model_queue_admission.go`, `model_queue_release.go`: 정확한 회귀가 드러낸 eligibility/release 결함을 최소 수정한다.

#### 테스트 작성

- `TestProviderResourceRemovedProviderDoesNotReadmitQueuedCandidate`: capacity 1 첫 실행, 두 번째 요청 queue 확인, provider 제거, 마지막 release, 두 번째 요청 미-dispatch와 resource 미재생성을 순서대로 검증한다.

#### 중간 검증

```bash
GOCACHE=/tmp/iop-go-cache go test -race -count=10 ./apps/edge/internal/service -run '^TestProviderResourceRemovedProviderDoesNotReadmitQueuedCandidate$'
```

기대 결과: 실제 release-triggered queue pump를 10회 반복해도 제거된 provider가 dispatch되지 않고 resource가 활성 상태로 재생성되지 않는다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/internal/service/model_queue_admission_test.go` | REVIEW_REVIEW_REVIEW_REFACTOR-1 |
| `apps/edge/internal/service/model_queue_types.go` | REVIEW_REVIEW_REVIEW_REFACTOR-1, 테스트 실패 시에만 |
| `apps/edge/internal/service/model_queue_admission.go` | REVIEW_REVIEW_REVIEW_REFACTOR-1, 테스트 실패 시에만 |
| `apps/edge/internal/service/model_queue_release.go` | REVIEW_REVIEW_REVIEW_REFACTOR-1, 테스트 실패 시에만 |

## 최종 검증

```bash
git diff --check
gofmt -d 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/model_queue_admission_test.go
GOCACHE=/tmp/iop-go-cache go test -race -count=10 ./apps/edge/internal/service -run '^TestProvider(Resource(RemovedProviderDoesNotReadmitQueuedCandidate|LongReleaseAfterCapacityDisabled|LongReservationUsesEffectiveRefreshState|OrphanRemovedAfterLastRelease|CapacitySharedAcrossModelGroups)|LongCapacitySharedAcrossModelGroups|SnapshotReadsAdmissionResourceState)$'
GOCACHE=/tmp/iop-go-cache go test -count=1 ./packages/go/config -run '^TestLoadEdge(ProviderLongContextCapacityValidation|RejectsProviderLongContextBudgetBelowModelWindow)$'
GOCACHE=/tmp/iop-go-cache go test -count=1 ./apps/edge/...
```

기대 결과: diff/gofmt 이상 없음, 실제 orphan queue-pump 회귀와 기존 S01/S04 race PASS, config validation 실제 실행 PASS, Edge 전체 fresh PASS. Go test cache 결과는 허용하지 않는다.

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