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

# Code Review Reference - 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 every implementation-owned section, keep 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, keep the active pair in place, and end the implementation execution; that file state is the same `review-ready` signal. Only the distinct review-agent instance decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves.
> 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, `코드리뷰 전용 체크리스트`) is distinct-review-agent-only, even after compaction/resume. The implementing agent must never perform it.
> `USER_REVIEW.md` and `complete.log` are the only overall loop terminal states. `review-ready`, `implementation-ready`, a verdict, an archive, or a WARN/FAIL pair is internal transitional state and must not trigger a user-facing question or intermediate report.
> 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/10+08_configured_snapshot_catalog, plan=0, tag=REFACTOR

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

> **[HARD ACTOR BOUNDARY]** 리뷰 에이전트는 구현 에이전트와 별도 agent instance여야 한다. 새 turn·resume·compaction·역할 전환으로 같은 agent를 재사용할 수 없다. product source/test를 수정하거나 현재·후속 plan을 구현하거나 구현 에이전트 소유 섹션을 채우지 않는다. WARN/FAIL 후속 pair를 만든 뒤 review 실행을 종료하면 archived current pair와 fresh pair가 runtime의 `implementation-ready` 신호가 된다. 별도 handoff 파일·사용자 메시지를 만들지 않는다.
> **[FOLLOW-UP MANDATE]** 미해결 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할 수 있다. reviewer는 후속 pair까지만 materialize하고 직접 구현하지 않는다.

> **[REVIEW AGENT ONLY]** 판정 후 `code_review_local_G06_0.log`, `plan_local_G06_0.log`로 archive한다. PASS에서만 `complete.log`와 task directory archive를 수행한다.

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| REFACTOR-1 Configured catalog and connectivity join | [x] |

## 구현 체크리스트

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

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. 구현 에이전트는 수정하거나 체크하지 않는다.

- [x] predecessor 08의 `agent-task/archive/2026/07/m-provider-resource-admission-ownership-alignment/08_disconnect_exclusion/complete.log`와 Roadmap Completion evidence를 확인한다.
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나를 append하고 차원별 평가와 Required/Suggested/Nit를 일치시킨다.
- [x] active review를 `code_review_local_G06_0.log`, plan을 `plan_local_G06_0.log`로 rename한다.
- [x] `.gitignore` Agent-Ops block의 `agent-task/**/*.md`, `agent-task/**/*.log` unignore와 `agent-roadmap/current.md` ignore를 확인한다.
- [ ] PASS이면 complete-log template으로 catalog/connectivity test evidence를 기록하고 active `.md`를 남기지 않는다. 이 중간 child에는 `Roadmap Completion`을 기록하지 않는다.
- [ ] PASS이면 task directory를 월별 archive로 이동하고 최종 `.log`에서 체크한다.
- [ ] PASS이면 runtime용 Milestone 완료 이벤트 메타데이터를 보고하고 roadmap/update-roadmap은 직접 호출하지 않는다.
- [ ] PASS split 이동 후 active parent에 sibling/file이 남아 있어 유지했는지 확인한다.
- [x] WARN/FAIL이고 user-review gate가 아니면 plan skill/fresh routing follow-up을 만들고 `complete.log`를 쓰지 않는다.
- [x] WARN/FAIL follow-up을 만든 뒤 review 실행을 종료한다. archived current pair와 fresh pair 및 reviewer 실행 종료가 runtime의 `implementation-ready` 신호다. 리뷰 에이전트는 후속 구현이나 별도 handoff artifact 생성을 수행하지 않으며 runtime이 별도 구현 에이전트 instance를 즉시 배정한다.
- [ ] USER_REVIEW이면 template으로 `USER_REVIEW.md`를 만들고 active pair와 `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW의 해소 결정이 완료/PASS이면 문서를 해소 상태로 갱신하고 `complete.log` 작성 및 archive 이동을 수행한다.
- [ ] USER_REVIEW의 해소 결정이 새 구현을 요구하면 plan이 USER_REVIEW를 archive하고 fresh pair를 쓴 뒤 review 실행을 종료한다. 이 상태가 `implementation-ready`이며 runtime이 별도 구현-agent instance를 배정한다.

## 계획 대비 변경 사항

- `NodeSnapshot`에 `Connected bool` 필드 추가. configured NodeStore catalog를 snapshot identity 원본으로 사용.
- `apps/edge/internal/node/store.go`: `All()`이 `Index` 순서로 정렬된 slice를 반환하도록 변경 (deterministic ordering). `sort` import 추가.
- `apps/edge/internal/service/status_provider.go`:
  - `buildSnapshotNoQueue()` / `buildSnapshotLocked()`: `s.registry.All()` 대신 `s.nodeStore.All()`을 순회하고 `s.registry.Get(rec.ID)`로 current connectivity를 join.
  - `s.nodeStore == nil`일 때 `buildSnapshotFromRegistry()`로 fallback (기존 registry-only Service 사용호환).
  - `emptySnapshotForConfigured(rec)` 함수 추가: configured record에서 NodeSnapshot을 빌드하고 registry 매칭 시 `Connected: true` + registry metadata (Alias, Label, AgentKind, LifecycleState) 병합.
  - `configuredLabel(rec)`: connected 없는 offline record에 대해 index/alias/id 기반 label 생성.
  - `NodeEntrySnapshot(entry)`: `Connected: true` 명시.
  - `buildSnapshotLocked`: `getSnapshotForNodeLocked(rec.ID, r)` 호출 전 `if len(r.Providers) > 0` / `else` 중복 분기 제거 (양쪽이 동일 호출이므로 단일 호출로 정리).
- `apps/edge/internal/service/status_provider_test.go`: `TestListNodeSnapshotsIncludesConfiguredOfflineNodes` 추가 (noQueue, withQueue, reconnectPreservesIdentity 하위 테스트).

Control Plane connector (`apps/edge/internal/controlplane/connector.go`)는 이 child 범위 밖으로 `Connected: true` 하드코드를 유지한다. 연결 매핑 갱신은 `15+14_controlplane_connected_mapping`에서 처리한다.

## 주요 설계 결정

- **Identity source**: configured `NodeStore`가 NodeSnapshot의 원본 소스가 된다. `registry.All()`만 순회하던 기존 로직에서는 disconnect된 configured Node가 snapshot에서 완전히 사라지는 문제가 있었다. 이제 `NodeStore.All()`을 deterministic Index 순으로 순회하고 `Registry.Get(id)`로 current connectivity를 join한다.
- **Stable ordering**: `NodeStore.All()`이 `Index` 필드를 기준으로 정렬된 slice를 반환한다. `Registry.All()`도 이미 Index 정렬을 사용하므로 configured record의 Index는 registry의 Index와 호환된다.
- **Offline node label**: connected record에서는 `entry.DisplayLabel()`을 사용하므로 index 기반 label (`node0`, `node1`)이 적용된다. offline record는 configured Index, Alias, NodeID 순으로 fallback label을 생성한다 (`configuredLabel` 함수).
- **Nil store fallback**: `SetNodeStore`가 호출되지 않은 경우 (기존 registry-only Service 사용) `buildSnapshotFromRegistry()`로 fallback하여 기존 동작을 보존한다.
- **Provider catalog**: configured record가 connected이든 offline이든 `NodeRecord.Providers` catalog와 `Adapters` 설정은 항상 사용 가능. runtime config store lookup은 configured record 전체에 적용된다.
- **Connector 미변경**: Control Plane wire mapping은 후속 child(`15+14_controlplane_connected_mapping`)에서 처리한다.

## 사용자 리뷰 요청

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

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

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

- offline Node가 snapshot에서 사라지지 않고 configured identity/provider catalog를 보존하는가.
- connected 상태가 current Registry generation에서 파생되는가.
- no-queue/queue-backed 결과가 같은 stable order와 identity를 유지하는가.

## 검증 결과

### 중간 검증

`go test -count=1 ./apps/edge/internal/service -run 'Snapshot.*Offline|Offline.*Snapshot|Reconnect'`

```
ok  	iop/apps/edge/internal/service	0.007s
```

### gofmt

`gofmt -d apps/edge/internal/node/store.go apps/edge/internal/service/status_provider.go apps/edge/internal/service/status_provider_test.go`

```
(no output — clean)
```

### 대상

`go test -count=1 ./apps/edge/internal/service -run 'Snapshot.*Offline|Offline.*Snapshot|Reconnect'`

```
ok  	iop/apps/edge/internal/service	0.007s
```

### 전체 Edge 회귀

`go test -count=1 ./apps/edge/...`

```
ok   	iop/apps/edge/cmd/edge
ok   	iop/apps/edge/internal/bootstrap
ok   	iop/apps/edge/internal/configrefresh
ok   	iop/apps/edge/internal/controlplane
ok   	iop/apps/edge/internal/edgecmd
ok   	iop/apps/edge/internal/edgevalidate
ok   	iop/apps/edge/internal/events
ok   	iop/apps/edge/internal/input
ok   	iop/apps/edge/internal/input/a2a
ok   	iop/apps/edge/internal/node
ok   	iop/apps/edge/internal/openai
ok   	iop/apps/edge/internal/opsconsole
ok   	iop/apps/edge/internal/service
ok   	iop/apps/edge/internal/transport
```

### 변경 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| REFACTOR-1 Configured catalog and connectivity join | [x] |

---

> **[IMPLEMENTING AGENT — BEFORE SAVING]** completion table, checklist, 변경 사항, 설계 결정, 검증 stdout/stderr를 모두 채우고 active 파일을 유지한 채 구현 실행을 종료한다. 이 파일 상태와 실행 종료가 runtime의 `review-ready` 신호이므로 별도 handoff 파일·사용자 메시지를 만들지 않는다. code-review 실행, 역할 전환, 판정, review 전용 섹션 수정, archive/terminal/follow-up 처리는 금지한다.

## 섹션 소유권

| Section | Owner | Note |
|---------|-------|------|
| Header/개요/Roadmap Targets/checkpoints | Fixed | 수정 금지 |
| 구현 표/체크리스트/변경/설계/검증 | Implementing agent | 실제 내용 필수 |
| 리뷰 체크리스트/결과 | Review agent | 구현 에이전트 수정 금지 |
| 사용자 리뷰 요청 | Implementing agent | Milestone 잠금 결정만 |

## 코드리뷰 결과

- 종합 판정: FAIL
- 차원별 평가:
  - Correctness: Fail
  - Completeness: Fail
  - Test coverage: Fail
  - API contract: Pass
  - Code quality: Pass
  - Implementation deviation: Fail
  - Verification trust: Fail
- 발견된 문제:
  - Required — `apps/edge/internal/service/status_provider.go:57`: `runtimeConfigSnapshot()`으로 mutex 보호된 `store`를 얻은 뒤 `s.nodeStore`를 다시 직접 읽어 `SetRuntimeConfig()`의 write와 data race가 발생한다. `go test -race -count=1 ./apps/edge/internal/service`에서 `TestRuntimeConfigSnapshotConcurrentReplace` 경합이 `status_provider.go:57` read / `service.go:117` write로 재현됐다. 두 builder의 nil 판정과 `All()` 순회를 캡처한 local `store`로 일관하게 수행해 동일 runtime snapshot 정체성과 lock 경계를 유지해야 한다.
  - Required — `apps/edge/internal/service/status_provider_test.go:1809`: `noQueue` subtest가 non-nil event bus로 `Service`를 만들어 queue manager를 활성화하므로 실제로는 `buildSnapshotLocked()`만 검증한다. 단독 coverage 결과 `buildSnapshotNoQueue=0.0%`, `buildSnapshotLocked=92.9%`였다. nil event bus와 `svc.queue == nil` 불변식으로 실제 no-queue 경로를 타게 하고, 이 child 범위인 configured identity/provider catalog/ordering/connected 결과를 직접 assertion해야 한다.
  - Required — `apps/edge/internal/service/status_provider_test.go:1896`: `reconnectPreservesIdentity` fixture는 `node-b`가 한 번도 연결된 적 없는 상태에서 첫 `Register()`만 호출해 계획이 요구한 same-identity reconnect를 검증하지 않는다. 동일 Node ID를 먼저 연결한 뒤 unregister/disconnect로 configured snapshot과 stable position이 유지되며 `Connected=false`가 되는지 확인하고, 새 registry generation으로 다시 register해 동일 identity/position에서 `Connected=true`로 회복되는지 검증해야 한다.
- 다음 단계: Required 3건을 최소 범위로 해소하는 fresh-routed follow-up PLAN/CODE_REVIEW pair를 생성한다.
