<!-- task=m-provider-resource-admission-ownership-alignment/05+03,04_provider_pool_policy_admission plan=3 tag=REVIEW_PROVIDER_POLICY_LEGACY_ISOLATION -->

# Provider-pool Legacy Pending 격리 증거 후속 계획

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

구현과 검증을 완료한 뒤 `CODE_REVIEW-local-G07.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/service blocker, 일반 범위 조정, 후속 에이전트가 해소할 수 있는 evidence gap은 사용자 리뷰 요청이 아니다.

## 배경

provider-pool admission 구현과 나머지 SDD S06 회귀는 focused/race/Edge 전체 테스트를 통과한다. 그러나 legacy pending 격리 테스트가 실제 `ProviderPool=false` 해석에서는 생성되지 않는 `providerID` 보유 candidate를 사용해, 통과 결과가 production-reachable legacy queue 격리 증거가 되지 못한다. 이 후속은 production 코드를 바꾸지 않고 해당 fixture와 검증 출력만 truthful하게 복구한다.

## 사용자 리뷰 요청 흐름

선택된 Milestone lock 결정만 active `CODE_REVIEW-local-G07.md`의 `사용자 리뷰 요청`에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식을 사용한다. 구현 중 직접 사용자 prompt는 금지하며 code-review가 요청 검증과 실제 `USER_REVIEW.md` 작성을 소유한다.

## Archive Evidence Snapshot

- Prior task path: `agent-task/m-provider-resource-admission-ownership-alignment/05+03,04_provider_pool_policy_admission`
- Prior plan: `agent-task/m-provider-resource-admission-ownership-alignment/05+03,04_provider_pool_policy_admission/plan_local_G05_2.log`
- Prior review: `agent-task/m-provider-resource-admission-ownership-alignment/05+03,04_provider_pool_policy_admission/code_review_local_G06_2.log`
- Verdict: FAIL
- Findings: Required 1, Suggested 0, Nit 0.
- Affected files: `apps/edge/internal/service/provider_pool_admission_test.go`.
- Verification evidence:
  - exact focused 세 테스트, `-race -count=10`, `go test -count=1 ./apps/edge/...`, gofmt와 diff check는 reviewer fresh 실행에서 PASS했다.
  - legacy test는 `providerPool=false`이면서 `providerID`가 있는 candidate가 provider resource를 채우는 production-unreachable 혼합 상태를 사용했다.
  - anchored focused 명령 아래 잘못 복사된 extra-test 출력은 reviewer fresh stdout으로 보정했지만, fixture 자체는 S06 evidence로 인정할 수 없다.
  - live preflight는 config check PASS 후 외부 `/models`와 Control Plane status endpoint 접근 불가로 rc=3이었다.
- Roadmap carryover: `queue-ownership`은 미완료이며 승인된 SDD S06의 production-reachable legacy pending 격리 evidence가 필요하다.
- 구현에 추가 세부가 필요할 때 위 prior plan/review 두 파일만 좁게 다시 읽는다. 다른 `agent-task/archive/**`를 탐색하지 않는다.

## 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:
  - `queue-ownership`: Edge 공통 provider-pool queue policy와 전체 pending 상한
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- 규칙/스킬: `AGENTS.md`, `agent-ops/rules/project/rules.md`, `agent-ops/rules/common/rules-roadmap.md`, `agent-ops/rules/common/rules-agent-spec.md`, `agent-ops/rules/project/domain/edge/rules.md`, `agent-ops/rules/project/domain/platform-common/rules.md`, `agent-ops/rules/project/domain/testing/rules.md`, `agent-ops/skills/common/router.md`, `agent-ops/skills/common/code-review/SKILL.md`, `agent-ops/skills/common/plan/SKILL.md`, `agent-ops/skills/common/finalize-task-routing/SKILL.md`
- 로드맵/SDD: `agent-roadmap/current.md`, `agent-roadmap/phase/operational-observability-provider-management/PHASE.md`, `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-resource-admission-ownership-alignment.md`, `agent-roadmap/sdd/operational-observability-provider-management/provider-resource-admission-ownership-alignment/SDD.md`
- 구현 스펙/계약: `agent-spec/index.md`, `agent-spec/runtime/provider-pool-config-refresh.md`, `agent-spec/runtime/edge-node-execution.md`, `agent-contract/index.md`, `agent-contract/inner/edge-config-runtime-refresh.md`, `agent-contract/inner/edge-node-runtime-wire.md`
- 구현: `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/run_submit.go`, `apps/edge/internal/service/provider_pool.go`
- 테스트: `apps/edge/internal/service/provider_pool_admission_test.go`
- 테스트 규칙: `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/platform-common-smoke.md`, `agent-test/local/testing-smoke.md`
- 루프/선행 근거: `agent-task/m-provider-resource-admission-ownership-alignment/05+03,04_provider_pool_policy_admission/plan_local_G06_1.log`, `agent-task/m-provider-resource-admission-ownership-alignment/05+03,04_provider_pool_policy_admission/code_review_local_G07_1.log`, `agent-task/m-provider-resource-admission-ownership-alignment/05+03,04_provider_pool_policy_admission/plan_local_G05_2.log`, `agent-task/m-provider-resource-admission-ownership-alignment/05+03,04_provider_pool_policy_admission/code_review_local_G06_2.log`, `agent-task/archive/2026/07/m-provider-resource-admission-ownership-alignment/03+01,02_cross_group_wakeup/complete.log`, `agent-task/archive/2026/07/m-provider-resource-admission-ownership-alignment/04_provider_pool_policy_config/complete.log`

### SDD 기준

- SDD: `agent-roadmap/sdd/operational-observability-provider-management/provider-resource-admission-ownership-alignment/SDD.md`
- 상태: `[승인됨]`, SDD 잠금 해제.
- 대상: Acceptance Scenario `S06` / Milestone Task `queue-ownership`.
- Evidence Map: S06은 Edge provider resource/queue deterministic tests와 race 검증을 요구하고 최종 `complete.log`의 `Roadmap Completion`에 `queue-ownership`과 명령 evidence를 남겨야 한다.
- 따라서 구현은 production과 같은 `providerID` 없는 legacy candidate의 group-local pending, 별도 provider-pool resource holder/waiter, 전역 count/cap 격리를 직접 만드는 fixture로 제한한다. exact focused/race/Edge 전체와 preflight 결과를 최종 evidence로 사용한다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/platform-common-smoke.md`, `agent-test/local/testing-smoke.md`를 읽었다. 구조적 공백과 `<확인 필요>` 값은 없다.
- fresh 실행을 위해 Go 검증에 `-count=1`과 `/tmp` GOCACHE를 사용하고 concurrency 회귀는 `-race -count=20` 또는 최종 `-race -count=10`을 사용한다.
- 프리플라이트: runner=`/config/workspace/iop`, branch=`main`, HEAD=`546a3f8a123cd2fb6f6faf3dbd9c655f71c3d0fd`, Linux/aarch64, Go 1.26.2, dirty shared worktree다. `bash`, `go`, `rg`, routing formatter와 long-context smoke script가 존재한다.
- config=`configs/edge.yaml`; live `/models`와 Control Plane status endpoint는 현재 접근 불가다. preflight rc=3이면 exact output과 out-dir를 blocker evidence로 기록하고 mixed scenario는 실행하지 않는다.

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

- production legacy 해석은 `ProviderPool=false`에서 `providerID`가 빈 candidate를 만들며 group-local slot을 사용한다.
- 현재 `TestProviderPoolMaxQueueIgnoresLegacyPending`는 `providerID`가 있는 candidate로 legacy item이 provider resource를 점유하게 만들어 production-reachable 경로를 실행하지 않는다.
- candidate-order AB/BA, explicit cancel/independent timeout, global cap, explicit zero, initial/apply handoff, runtime triple snapshot은 기존 focused/race 검증이 실제 경로를 실행하며 reviewer fresh PASS했다.

### 심볼 참조

- 변경하거나 제거할 production symbol은 없다.
- 테스트 함수명 `TestProviderPoolMaxQueueIgnoresLegacyPending`은 유지한다.

### 분할 판단

- split decision policy를 먼저 평가했다. 이번 후속은 동일 테스트 함수 한 개의 fixture/assertion과 검증 출력 정합화로 끝나는 단일 evidence unit이다.
- API/foundation, 도메인, 검증 전략, 독립 완료 경계가 추가되지 않으며 같은 focused/race 명령으로 판정되므로 추가 split은 인위적이다.
- 서브태스크 의존성 `03`, `04`는 각각 `agent-task/archive/2026/07/m-provider-resource-admission-ownership-alignment/03+01,02_cross_group_wakeup/complete.log`, `agent-task/archive/2026/07/m-provider-resource-admission-ownership-alignment/04_provider_pool_policy_config/complete.log`로 충족됐다.

### 범위 결정 근거

- `apps/edge/internal/service/provider_pool_admission_test.go`의 legacy isolation fixture와 이미 보정한 timeout tolerance 메시지만 이 후속 범위다.
- production `apps/edge/internal/service/*.go`, bootstrap/config/configrefresh, contract/spec 문서는 수정하지 않는다.
- 활성 sibling `06+05_refresh_consistency`, `07+03_snapshot_source`와 shared worktree의 다른 변경은 제외한다.
- repo root의 untracked `0`, `service.test`는 이 task 소유권이 없으므로 수정·삭제하지 않는다.

### 최종 라우팅

- evaluation_mode: `isolated-reassessment`
- build closures: scope/context/verification/evidence/ownership/decision 모두 `true`.
- build grade scores: scope_coupling=0, state_concurrency=2, blast_irreversibility=0, evidence_diagnosis=2, verification_complexity=2.
- build: lane=`local`, grade=`G06`, filename=`PLAN-local-G06.md`.
- review closures: scope/context/verification/evidence/ownership/decision 모두 `true`.
- review grade scores: scope_coupling=1, state_concurrency=2, blast_irreversibility=0, evidence_diagnosis=2, verification_complexity=2.
- review: lane=`local`, grade=`G07`, filename=`CODE_REVIEW-local-G07.md`.

## 구현 체크리스트

- [ ] `TestProviderPoolMaxQueueIgnoresLegacyPending`를 production-reachable legacy group holder/pending과 별도 provider-pool holder/pending fixture로 고치고, 전역 pending count/cap, cancellation/error identity, lease/resource 회복을 검증한 뒤 focused/race/Edge 전체/preflight 검증을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_PROVIDER_POLICY_LEGACY_ISOLATION-1] Production-reachable legacy pending 격리 복구

#### 문제

- `apps/edge/internal/service/provider_pool_admission_test.go:371-380`은 `providerID`가 있는 candidate를 `providerPool=false` admission에 전달해 legacy item이 provider resource를 점유하게 한다.
- `apps/edge/internal/service/provider_pool_admission_test.go:392-438`은 같은 synthetic candidate를 legacy pending과 provider-pool waiter에 재사용하므로 실제 legacy group-local queue와 provider-pool resource queue의 격리를 검증하지 않는다.
- `apps/edge/internal/service/provider_resolution.go:15-72`의 production legacy 해석은 `providerID`가 빈 candidate만 만들고, `providerID`는 `apps/edge/internal/service/provider_resolution.go:443-449`의 provider-pool 해석에서만 채운다.

#### 해결 방법

legacy와 provider-pool candidate/holder를 분리한다. legacy holder와 waiter는 동일 group key와 `providerID` 없는 candidate를 사용하고, provider-pool holder와 waiter는 별도 `providerID` candidate를 사용한다.

```go
legacyCandidates := []candidateNode{{entry: entry, capacity: 1}}
providerCandidates := []candidateNode{{
	entry: entry, capacity: 1, providerID: "prov-Legacy",
}}

legacyHolder, err := m.admit(t.Context(), "legacy-group", "", "", legacyCandidates, groupPolicy{}, nil, false, false)
// same legacy group: enqueue a providerPool=false waiter

providerHolder, err := m.admit(t.Context(), "provider-holder", "", "", providerCandidates, groupPolicy{}, nil, false, true)
// enqueue providerPool=true waiter, require pp pending == 1
// another provider-pool admission must return errQueueFull
```

두 waiter가 모두 pending임을 lock-safe barrier로 확인하고 legacy item이 `pendingProviderPoolCountLocked()`에 포함되지 않음을 직접 assert한다. 명시 cancel 뒤 두 waiter가 `context.Canceled`로 끝나는지 확인하고 holder lease를 release해 pending, lease, legacy group in-flight와 provider resource in-flight가 모두 0으로 회복되는지 검증한다.

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

- [ ] `apps/edge/internal/service/provider_pool_admission_test.go`: legacy candidate에서 `providerID`를 제거하고 같은 legacy group의 holder/pending을 만든다.
- [ ] `apps/edge/internal/service/provider_pool_admission_test.go`: 별도 provider-pool holder/pending을 만들고 legacy pending이 global count에 포함되지 않으며 다음 provider-pool request만 `errQueueFull`이 되는지 확인한다.
- [ ] `apps/edge/internal/service/provider_pool_admission_test.go`: waiter cancellation/error identity와 holder release 뒤 pending/lease/group/resource counter가 0인지 확인한다.
- [ ] `apps/edge/internal/service/provider_pool_admission_test.go`: 검증 출력에는 exact anchored `-run`이 실제 실행한 테스트만 포함되도록 stdout/stderr를 그대로 기록한다.

#### 테스트 작성

- 수정: `TestProviderPoolMaxQueueIgnoresLegacyPending` — production-reachable legacy group-local pending과 별도 provider-pool pending/cap 격리를 검증한다.
- 신규 production test나 production source 변경은 없다.

#### 중간 검증

```bash
gofmt -d apps/edge/internal/service/provider_pool_admission_test.go
```

출력 없음.

```bash
GOCACHE=/tmp/iop-go-cache-provider-policy-legacy-isolation go test -v -count=1 ./apps/edge/internal/service -run '^TestProviderPoolMaxQueueIgnoresLegacyPending$'
```

해당 테스트만 실제 `=== RUN`으로 실행되고 PASS한다.

```bash
GOCACHE=/tmp/iop-go-cache-provider-policy-legacy-isolation go test -race -count=20 ./apps/edge/internal/service -run '^TestProviderPoolMaxQueueIgnoresLegacyPending$'
```

PASS하고 race warning과 간헐 실패가 없다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/internal/service/provider_pool_admission_test.go` | REVIEW_PROVIDER_POLICY_LEGACY_ISOLATION-1 |

## 최종 검증

fresh 실행이 필요하므로 Go test cache 결과만 재사용하지 않고 `-count=1` 또는 명시 반복 count를 사용한다.

```bash
gofmt -d apps/edge/internal/service/provider_pool_admission_test.go
git diff --check
GOCACHE=/tmp/iop-go-cache-provider-policy-legacy-isolation go test -v -count=1 ./apps/edge/internal/service -run '^TestProviderPool(CommonQueueTimeoutAcrossCandidateOrders|MaxQueueIgnoresLegacyPending|PendingCountRecoversAfterCancelAndTimeout)$'
GOCACHE=/tmp/iop-go-cache-provider-policy-legacy-isolation go test -race -count=10 ./apps/edge/internal/service -run '^TestProviderPool(CommonQueueTimeoutAcrossCandidateOrders|MaxQueueIgnoresLegacyPending|PendingCountRecoversAfterCancelAndTimeout)$'
GOCACHE=/tmp/iop-go-cache-provider-policy-legacy-isolation go test -count=1 ./apps/edge/...
GOCACHE=/tmp/iop-go-cache-provider-policy-legacy-isolation scripts/e2e-long-context-admission-smoke.sh --preflight --out-dir /tmp/iop-long-admission-smoke-provider-policy-legacy-isolation
```

- gofmt/diff/focused/race/Edge 전체는 PASS해야 한다.
- anchored focused stdout에는 정확히 세 테스트와 candidate-order의 AB/BA subtest만 있어야 한다.
- preflight rc=0이면 필요한 live scenario를 이어서 실행한다. rc=3이면 exact blocker output과 evidence out-dir를 기록하고 성공으로 바꾸지 않는다.

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