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

# Configured snapshot catalog and connectivity join

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

선행 08은 `agent-task/archive/2026/07/m-provider-resource-admission-ownership-alignment/08_disconnect_exclusion/complete.log`로 완료됐음을 확인하고 구현한다. 구현 에이전트는 코드·테스트 변경, 구현 검증, active review stub의 구현 에이전트 소유 섹션 작성까지만 수행한다. 모두 채운 뒤 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와 함께 `사용자 리뷰 요청`에 기록하고 같은 file-state handoff를 사용한다. 환경·secret·서비스·일반 scope 변경·follow-up으로 닫을 수 있는 evidence gap은 user-review 사유가 아니다.

## 배경

현재 `ListNodeSnapshots`는 live Registry entry만 순회하므로 disconnect된 configured Node/provider가 운영 snapshot에서 사라진다. 이 child는 Edge config catalog를 snapshot identity 원본으로 바꾸고 current Registry connectivity를 join한다. offline provider effective tuple과 Control Plane wire mapping은 후속 child가 담당한다.

## 사용자 리뷰 요청 흐름

선택 Milestone 잠금 결정만 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 active review stub에 기록한다. 구현 중 직접 user prompt는 금지되며 code-review가 요청 검증과 실제 `USER_REVIEW.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-ops/rules/project/domain/edge/rules.md`, `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/testing-smoke.md`
- `agent-contract/inner/control-plane-edge-wire.md`, `agent-contract/inner/edge-node-runtime-wire.md`
- `agent-spec/runtime/edge-node-execution.md`, `agent-spec/runtime/provider-pool-config-refresh.md`, `agent-spec/control/control-plane-operations.md`
- `apps/edge/internal/node/registry.go`, `store.go`
- `apps/edge/internal/service/status_provider.go`
- `apps/edge/internal/service/model_queue_snapshot.go`
- `apps/edge/internal/service/model_queue_types.go`, `model_queue_release.go`
- `apps/edge/internal/service/status_provider_test.go` — snapshot/candidate pressure/refresh 범위
- `apps/edge/internal/controlplane/connector.go` — 350-379 status mapping
- `apps/edge/internal/controlplane/connector_test.go` — 218-311, 756-808 status tests
- `proto/iop/control.proto:74-86`, `proto/iop/runtime.proto:171-203` — 기존 `connected`와 ProviderSnapshot field 확인

### SDD 기준

- SDD: `agent-roadmap/sdd/operational-observability-provider-management/provider-resource-admission-ownership-alignment/SDD.md`, 상태 `[승인됨]`, 잠금 `해제`.
- Acceptance Scenario S14 → `offline-snapshot`; Evidence Map은 exact offline tuple, reconnect convergence, Control Plane wire mapping, race와 `Roadmap Completion` evidence를 요구한다.
- offline은 `connected=false`, provider `status=unavailable`, `health=offline`, effective capacity/일반·long counter/queued 0이며 model catalog는 유지한다.
- reconnect 뒤 same resource identity의 new generation이 configured capacity와 admission eligibility를 복구해야 한다. 기존 wire field를 재사용하므로 proto 변경은 필요 없다.

### 테스트 환경 규칙

- `test_env=local`; `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/testing-smoke.md`를 읽고 Edge/testing profile을 적용한다. 대상 status/controlplane test, Edge 전체, race, 전체 Go 회귀를 fresh `-count=1`로 실행한다.
- 외부 service/secret/Docker는 필요 없다. Control Plane connector test는 local in-process TCP fixture다.

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

- current status tests는 connected Registry entry를 전제로 하고 offline configured record 유지 test가 없다.
- connector는 모든 Node를 connected로 직렬화해 false 전달을 검증할 수 없다.
- disconnect→offline tuple→same identity reconnect→available tuple과 새 admission eligibility의 한 흐름이 없다.

### 심볼 참조

- 변경 후보: `NodeSnapshot`, `ListNodeSnapshots`, `buildSnapshotNoQueue`, `buildSnapshotLocked`, `getSnapshotForNodeLocked`, `Connector.buildStatusResponse`.
- `NodeSnapshot` 소비자는 `apps/edge/internal/controlplane/connector.go`, service/control command/status tests다.

### 세분화 경계

- 이 child는 configured NodeStore catalog, stable order, current Registry connectivity join과 `NodeSnapshot.Connected`까지만 소유한다.
- predecessor는 기존과 동일한 `08_disconnect_exclusion`이다.
- offline effective tuple은 `14+10_offline_provider_tuple`, Control Plane mapping은 `15+14_controlplane_connected_mapping`이 담당한다.

### 범위 결정 근거

- proto에는 필요한 `connected`, status/health/counter field가 이미 있어 원본/생성물을 변경하지 않는다.
- 기존 waiter reconnect pump는 11로 제외한다. 이 task는 reconnect 후 snapshot과 신규 admission eligibility까지만 검증한다.
- active provider endpoint probe/health state machine은 Milestone 범위 제외다.

### 최종 라우팅

- `evaluation_mode=first-pass`.
- build closures 모두 true: service DTO, catalog/registry join, connector mapping과 local tests로 닫힌다. scores=`2,1,1,1,1`; `local/G06` → `PLAN-local-G06.md`.
- review closures 모두 true: exact tuple과 wire bool, reconnect/new admission test로 판정한다. scores=`2,1,1,1,1`; `local/G06` → `CODE_REVIEW-local-G06.md`.

## 구현 체크리스트

- [ ] Node snapshot source를 live Registry-only에서 configured NodeStore catalog + current Registry connectivity join으로 바꾸고 stable 순서를 유지한다.
- [ ] configured offline Node 유지와 stable order, same identity reconnect를 deterministic service test로 검증한다.
- [ ] 대상 검증 결과 또는 blocker를 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우고 active pair를 유지한 채 구현 실행을 종료한다. 이 저장 상태가 runtime의 review-ready 신호이며 별도 handoff 파일·사용자 메시지를 만들지 않는다. 구현 에이전트는 code-review를 실행하거나 review 전용 작업을 수행하지 않는다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REFACTOR-1] Configured catalog and connectivity join

문제: `apps/edge/internal/service/status_provider.go:35-80`의 두 snapshot builder가 `registry.All()`만 순회해 offline Node를 완전히 누락한다.

해결 방법:

```go
// Before: apps/edge/internal/service/status_provider.go:60
entries := s.registry.All()
for _, entry := range entries { ... }

// After
// NodeStore.All()을 stable Index 순으로 순회하고 registry.Get(id)로
// current generation connectivity를 join한다.
```

`NodeSnapshot`에 `Connected`를 추가한다. configured record가 offline이면 record identity/index로 label을 만들고 config payload는 기존 non-leak boundary에 맞춰 내부 DTO에만 유지한다.

수정 파일 및 체크리스트:

- [ ] `apps/edge/internal/service/status_provider.go`: catalog-first Node snapshot과 Connected bool.
- [ ] 필요 시 `apps/edge/internal/node/store.go`: deterministic record ordering을 안전하게 제공.
- [ ] `apps/edge/internal/service/status_provider_test.go`: offline Node 유지, stable order, reconnect identity.

테스트 작성: `apps/edge/internal/service/status_provider_test.go`에 `TestListNodeSnapshotsIncludesConfiguredOfflineNodes`를 추가한다. NodeStore에 connected/offline records를 두고 Registry에는 connected만 둔 fixture로 no-queue/queue-backed 결과의 identity와 stable order를 검증한다.

중간 검증: `go test -count=1 ./apps/edge/internal/service -run 'Snapshot.*Offline|Offline.*Snapshot|Reconnect'`가 통과한다.

## 의존 관계 및 구현 순서

- `agent-task/archive/2026/07/m-provider-resource-admission-ownership-alignment/08_disconnect_exclusion/complete.log`가 predecessor 08의 완료를 충족한다.
- 완료 후 `14+10_offline_provider_tuple`이 `Connected`와 catalog join 계약을 소비한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/edge/internal/service/status_provider.go`, 선택적 `apps/edge/internal/node/store.go` | REFACTOR-1 |
| `apps/edge/internal/service/status_provider_test.go` | REFACTOR-1 |

## 최종 검증

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

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