<!-- task=m-provider-resource-admission-ownership-alignment/14+10_offline_provider_tuple plan=0 tag=REFACTOR -->

# Offline provider effective tuple

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

선행 10의 matching active/archive `complete.log`를 확인한 뒤 구현한다. 구현 에이전트는 코드·테스트 변경, 구현 검증, active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성까지만 수행한다. 모두 채운 뒤 active pair를 유지하고 구현 실행을 종료한다. 이 저장 상태가 runtime의 `review-ready` 신호이며 별도 handoff 파일·사용자 메시지를 만들지 않는다. code-review 실행, 역할 전환, 판정, review 전용 섹션 수정, archive/log rename, `USER_REVIEW.md`·`complete.log` 작성, 후속 pair 생성·구현은 금지한다. runtime은 사용자에게 묻거나 준비 완료를 보고하지 않고 별도 review-agent instance를 즉시 배정하며 같은 agent의 새 turn·resume·compaction으로 대체하지 않는다. 선택 Milestone의 `구현 잠금 > 결정 필요`만 linked evidence와 함께 review stub의 `사용자 리뷰 요청`에 기록하고 같은 file-state handoff를 사용한다.

## 배경

선행 child가 configured catalog와 current connectivity를 `NodeSnapshot`에 결합한다. 이 child는 disconnected Node의 provider catalog를 유지하면서 effective status/health/counters를 offline tuple로 내리고, reconnect generation에서 configured capacity와 admission eligibility를 복구한다.

## 분석 결과

### 세분화 경계

- 이 child는 service 내부 provider snapshot tuple과 disconnect→offline→reconnect 수렴만 소유한다.
- catalog ordering과 `Connected` 산출은 `10+08_configured_snapshot_catalog`의 완료 계약을 소비한다.
- Control Plane wire `connected`와 config redaction은 `15+14_controlplane_connected_mapping`이 담당한다.

### 변경 범위

- `apps/edge/internal/service/model_queue_snapshot.go`
- `apps/edge/internal/service/status_provider_test.go`

## 구현 체크리스트

- [ ] offline provider를 unavailable/offline/effective counter 0으로 노출하며 disabled와 offline 상태를 구분하고 catalog entry를 유지한다.
- [ ] disconnect→offline→same identity reconnect→available/new admission service 흐름을 검증한다.
- [ ] service race 결과 또는 blocker를 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우고 active pair를 유지한 채 구현 실행을 종료한다. 이 저장 상태가 runtime의 review-ready 신호이며 별도 handoff 파일·사용자 메시지를 만들지 않는다. 구현 에이전트는 code-review를 실행하거나 review 전용 작업을 수행하지 않는다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REFACTOR-1] Connectivity-aware provider snapshot

문제: `apps/edge/internal/service/model_queue_snapshot.go:20-87`은 enabled provider를 항상 `status=available`과 configured capacity로 표현한다.

해결 방법: current connectivity가 false인 Node에서는 provider admin enabled/disabled 원본을 삭제하지 않되 effective tuple을 `unavailable/offline/0/0/0/0/0/0`으로 만든다. reconnect generation에서는 configured capacity와 lease-backed current counters를 다시 읽으며 old orphan counters는 노출하지 않는다.

```go
// connected=false이면 catalog는 유지하고 effective 값만 내린다.
Status:              effectiveStatus(connected),
Health:              effectiveHealth(connected, prov.Health),
Capacity:            effectiveCount(connected, capVal),
InFlight:            effectiveCount(connected, inflight),
Queued:              effectiveCount(connected, queued),
```

수정 파일 및 체크리스트:

- [ ] `apps/edge/internal/service/model_queue_snapshot.go`: connectivity-aware catalog snapshot helper.
- [ ] `apps/edge/internal/service/status_provider_test.go`: disabled vs offline, 일반/long/queued 0, reconnect/new admission.

테스트 작성: enabled/disabled provider fixture에서 exact offline tuple, model catalog/provider id 유지, orphan counter 제거, reconnect generation의 configured capacity와 신규 admission 복구를 검증한다.

## 의존 관계 및 구현 순서

- `10+08_configured_snapshot_catalog`의 matching `complete.log`가 필요하다.
- offline tuple과 reconnect service 회귀를 닫은 뒤 `15+14_controlplane_connected_mapping`이 wire closure를 수행한다.

## 최종 검증

- `gofmt -d apps/edge/internal/service/model_queue_snapshot.go apps/edge/internal/service/status_provider_test.go` → 출력 없음.
- `go test -count=1 ./apps/edge/internal/service -run 'Snapshot|Offline|Reconnect'` → PASS.
- `go test -race -count=1 ./apps/edge/internal/service` → PASS.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우고 active pair를 유지한 채 구현 실행을 종료한다. 이 저장 상태가 runtime의 `review-ready` 신호이며 별도 handoff 파일·사용자 메시지를 만들지 않는다. 구현 에이전트는 code-review를 실행하거나 review 전용 작업을 수행하지 않고, runtime은 별도 review 에이전트 instance에 즉시 인계한다.
