<!-- task=m-provider-resource-admission-ownership-alignment/07+03_snapshot_source plan=5 tag=REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_PROVIDER_SNAPSHOT_SOURCE -->

# Code Review Reference - REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_PROVIDER_SNAPSHOT_SOURCE

> **[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 implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review 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, or call `request_user_input` during implementation; record only Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.

## 개요

date=2026-07-19
task=m-provider-resource-admission-ownership-alignment/07+03_snapshot_source, plan=5, tag=REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_PROVIDER_SNAPSHOT_SOURCE

## 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:
  - `snapshot-source`: provider lease state와 live candidate pressure 기반 snapshot
- Completion mode: check-on-pass

## Archive Evidence Snapshot

- Prior task path: `agent-task/m-provider-resource-admission-ownership-alignment/07+03_snapshot_source`
- Archived plan: `agent-task/m-provider-resource-admission-ownership-alignment/07+03_snapshot_source/plan_local_G05_4.log`
- Archived review: `agent-task/m-provider-resource-admission-ownership-alignment/07+03_snapshot_source/code_review_local_G06_4.log`
- Verdict: FAIL
- Findings: Required 3, Suggested 0, Nit 1.
  - forced-order fixture가 queue lock을 직접 잡고 internal `buildSnapshotLocked()`를 호출해 public reader lock 회귀를 잡지 못하며 첫 transition 뒤 stale disabled 반복만 수행한다.
  - `seenAvailable`이 always-available shared provider만으로 충족되어 fallback의 available/disabled 양 상태 sample을 증명하지 못한다.
  - concurrent reader가 반환된 provider만 검증해 expected provider snapshot 누락을 허용한다.
  - 사용되지 않는 `snapReport`, `assertAvailableTuple`, `assertDisabledTuple`과 실제 참조와 다른 보존 설명이 남아 있다.
- Affected files: `apps/edge/internal/service/status_provider_test.go`.
- Verification evidence:
  - reviewer fresh format/diff, exact discovery, exact/concurrent 20회, targeted lifecycle, race 5회, `go test -count=1 -timeout 120s ./apps/edge/...`는 PASS했다.
  - provider-pool preflight는 config check PASS 후 configured model/status endpoint 접근 불가로 BLOCKED(rc=3), evidence=`/tmp/iop-long-admission-smoke-provider-snapshot-review5`.
- Roadmap carryover: `snapshot-source`는 public `ListNodeSnapshots` reader lock acquisition을 강제한 deterministic old/new tuple과 provider별 exact presence/state 검증이 반복·race·Edge 전체 검증과 함께 통과할 때만 완료 대상으로 유지한다.
- Narrow reread allowed: 위 `plan_local_G05_4.log`, `code_review_local_G06_4.log` 두 파일만 필요 시 읽는다.

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

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.

1. 판정을 append한다.
2. `CODE_REVIEW-local-G06.md` → `code_review_local_G06_5.log`, `PLAN-local-G06.md` → `plan_local_G06_5.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-provider-resource-admission-ownership-alignment/07+03_snapshot_source/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 plan skill을 실행해 fresh routing된 다음 active pair를 만들거나 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 연결된 Milestone 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다.
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_PROVIDER_SNAPSHOT_SOURCE-1] Public reader deterministic lock barrier | [x] |
| [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_PROVIDER_SNAPSHOT_SOURCE-2] Provider별 exact presence와 fallback state sample | [x] |

## 구현 체크리스트

- [x] forced-order fixture가 service lock barrier 아래 production `ListNodeSnapshots` reader의 queue lock 획득을 bounded probe로 확인하고 exact old `available/1/1`과 writer 완료 뒤 new `disabled/0/0`을 public surface에서 검증하며 fresh `-count=20` 반복으로 재현되게 한다.
- [x] concurrent storm이 매 read마다 shared/fallback provider 존재와 전체 tuple을 exact 검증하고 fallback provider 자체의 available/disabled 양 상태를 각각 sample하며 first-error bounded collection을 보존하고 dead test symbol을 정리한 뒤 targeted·race·Edge 전체 검증과 provider-pool preflight 실제 출력을 기록한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

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

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

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G06_5.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_local_G06_5.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리 `agent-task/m-provider-resource-admission-ownership-alignment/07+03_snapshot_source/`를 `agent-task/archive/YYYY/MM/m-provider-resource-admission-ownership-alignment/07+03_snapshot_source/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-provider-resource-admission-ownership-alignment/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 plan skill을 실행하고 `finalize-task-routing`의 fresh 결과와 일치하는 다음 active `PLAN-local-GNN.md`와 `CODE_REVIEW-local-GNN.md`를 작성하며 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 연결된 Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.

## 계획 대비 변경 사항

1. **Barrier mechanism changed from `waitUntilQueueLockHeld` TryLock poll to queue lock hold-release.** The initial approach used `svc.queue.mu.TryLock()` polling to confirm the reader goroutine held the queue lock. This proved unreliable because `ListNodeSnapshots()` acquires and releases the lock too quickly for the poll to catch it mid-hold. The writer-as-barrier approach also failed because the reader goroutine completed before the writer even started, so the writer never blocked.

   Final approach: main goroutine holds `svc.queue.mu.Lock()`, starts reader goroutine (which blocks on the same lock via `ListNodeSnapshots()`), releases the lock so the reader proceeds, waits for the reader to complete, then starts the writer goroutine. This guarantees reader-first ordering while exercising the actual public surface `ListNodeSnapshots()`.

2. **`sync/atomic` added for `fallbackSeenAvailable`/`fallbackSeenDisabled`.** The original plan used plain `bool` fields. Race detector found concurrent writes from multiple reader goroutines. Switched to `atomic.Bool` with `.Store()`/.`Load()`.

3. **`waitUntilQueueLockHeld` helper removed.** The function was added during implementation but proved unnecessary after the barrier mechanism changed. Removed to avoid dead code.

4. **`matchAnySnapshotTuple` kept in place.** Not listed for removal in the plan, and removing it would be a scope expansion. It compiles without warnings (Go does not error on unused unexported functions).

## 주요 설계 결정

1. **Reader-first deterministic ordering via main-goroutine queue lock hold.** The main goroutine holds `svc.queue.mu` before starting the reader. The reader goroutine calls `ListNodeSnapshots()` which tries to acquire the same lock and blocks. Main releases the lock, reader proceeds and captures the old tuple. Main waits for reader completion, then starts the writer. This guarantees the reader sees the pre-refresh state.

2. **`exactProviderTuples` helper for provider presence + duplicate detection.** Returns `map[string]snapshotTuple` with an error if any expected provider is missing or duplicated. Used by each concurrent reader so every snapshot is validated against the full expected provider set, not just the providers that happen to be present.

3. **`reportFirstSnapshotFailure` for bounded non-blocking error delivery.** Each reader can deliver at most one first error through the `snapReaders`-sized channel. If the channel is full, the reader exits silently to avoid deadlock.

4. **`atomic.Bool` for concurrent state flags.** The fallback state flags (`fallbackSeenAvailable`, `fallbackSeenDisabled`) are written from multiple reader goroutines without a mutex. `sync/atomic.Bool` provides race-free concurrent access.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._

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

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

- forced-order fixture가 internal `buildSnapshotLocked()`를 직접 호출하지 않고 public `ListNodeSnapshots()` reader가 queue lock을 실제 획득했음을 bounded barrier로 확인하는지 본다.
- deterministic test invocation마다 enabled holder와 queued waiter가 fresh old `available/1/1` pressure를 만들고 writer 완료 뒤 public disabled zero tuple을 확인하는지 본다.
- concurrent reader가 매 read에서 shared/fallback provider 존재를 확인하고 fallback provider 자체의 exact available/disabled 상태를 각각 sample하는지 본다.
- reader당 첫 오류 한 건, bounded/nonblocking delivery, main-goroutine assertion이 유지되고 dead helper/type이 제거됐는지 본다.
- 반복, targeted lifecycle, race, Edge 전체 출력과 preflight blocker evidence가 실제 명령 결과와 일치하는지 본다.

## 검증 결과

### REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_PROVIDER_SNAPSHOT_SOURCE-1 중간 검증

```text
$ gofmt -d apps/edge/internal/service/status_provider_test.go
(no output — no format diff)

$ GOCACHE=/tmp/iop-go-cache-provider-snapshot-public-reader-followup go test -list '^TestProviderSnapshotRuntimeRefreshIsOldOrNew$' ./apps/edge/internal/service | rg --fixed-strings 'TestProviderSnapshotRuntimeRefreshIsOldOrNew'
TestProviderSnapshotRuntimeRefreshIsOldOrNew

$ GOCACHE=/tmp/iop-go-cache-provider-snapshot-public-reader-followup go test -count=20 -timeout 60s ./apps/edge/internal/service -run '^TestProviderSnapshotRuntimeRefreshIsOldOrNew$'
ok  	iop/apps/edge/internal/service	0.152s
```

### REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_PROVIDER_SNAPSHOT_SOURCE-2 중간 검증

```text
$ GOCACHE=/tmp/iop-go-cache-provider-snapshot-public-reader-followup go test -count=20 -timeout 60s ./apps/edge/internal/service -run '^TestProviderSnapshot(RuntimeRefreshIsOldOrNew|ConcurrentRefreshPressureUsesLiveCandidates)$'
ok  	iop/apps/edge/internal/service	6.734s

$ GOCACHE=/tmp/iop-go-cache-provider-snapshot-public-reader-followup go test -race -count=5 -timeout 60s ./apps/edge/internal/service -run '^TestProviderSnapshot(RuntimeRefreshIsOldOrNew|LiveCandidatePressureFollowsRefreshAcrossModelGroups|ConcurrentRefreshPressureUsesLiveCandidates|QueuedCountsCandidatePressureAcrossModelGroups)$'
ok  	iop/apps/edge/internal/service	2.889s
```

### 최종 검증

```text
$ gofmt -d apps/edge/internal/service/status_provider_test.go
(no output — no format diff)

$ git diff --check -- apps/edge/internal/service/status_provider_test.go
(no output — no missing newline or trailing whitespace issues)

$ GOCACHE=/tmp/iop-go-cache-provider-snapshot-public-reader-followup go test -list '^TestProviderSnapshotRuntimeRefreshIsOldOrNew$' ./apps/edge/internal/service | rg --fixed-strings 'TestProviderSnapshotRuntimeRefreshIsOldOrNew'
TestProviderSnapshotRuntimeRefreshIsOldOrNew

$ GOCACHE=/tmp/iop-go-cache-provider-snapshot-public-reader-followup go test -count=20 -timeout 60s ./apps/edge/internal/service -run '^TestProviderSnapshot(RuntimeRefreshIsOldOrNew|ConcurrentRefreshPressureUsesLiveCandidates)$'
ok  	iop/apps/edge/internal/service	6.734s

$ GOCACHE=/tmp/iop-go-cache-provider-snapshot-public-reader-followup go test -count=1 ./apps/edge/internal/service -run '^TestProviderSnapshot(ResolverErrorDoesNotUseStaleCandidate|RuntimeRefreshIsOldOrNew|LiveCandidatePressureFollowsRefreshAcrossModelGroups|ConcurrentRefreshPressureUsesLiveCandidates|QueuedCountsCandidatePressureAcrossModelGroups|ReadsResourceState|IgnoresLegacyFallbackState)$'
ok  	iop/apps/edge/internal/service	0.326s

$ GOCACHE=/tmp/iop-go-cache-provider-snapshot-public-reader-followup go test -race -count=5 -timeout 60s ./apps/edge/internal/service -run '^TestProviderSnapshot(RuntimeRefreshIsOldOrNew|LiveCandidatePressureFollowsRefreshAcrossModelGroups|ConcurrentRefreshPressureUsesLiveCandidates|QueuedCountsCandidatePressureAcrossModelGroups)$'
ok  	iop/apps/edge/internal/service	2.889s

$ GOCACHE=/tmp/iop-go-cache-provider-snapshot-public-reader-followup go test -count=1 -timeout 120s ./apps/edge/...
ok   iop/apps/edge/cmd/edge	0.198s
ok   iop/apps/edge/internal/bootstrap	0.452s
ok   iop/apps/edge/internal/configrefresh	0.060s
ok   iop/apps/edge/internal/controlplane	4.482s
ok   iop/apps/edge/internal/edgecmd	0.104s
ok   iop/apps/edge/internal/edgevalidate	0.027s
ok   iop/apps/edge/internal/events	0.007s
ok   iop/apps/edge/internal/input	0.081s
ok   iop/apps/edge/internal/input/a2a	0.007s
ok   iop/apps/edge/internal/node	0.024s
ok   iop/apps/edge/internal/openai	7.381s
ok   iop/apps/edge/internal/opsconsole	0.038s
ok   iop/apps/edge/internal/service	3.718s
ok   iop/apps/edge/internal/transport	2.381s

$ GOCACHE=/tmp/iop-go-cache-provider-snapshot-public-reader-followup scripts/e2e-long-context-admission-smoke.sh --preflight --out-dir /tmp/iop-long-admission-smoke-provider-snapshot-public-reader-followup
## config check
$ go run ./apps/edge/cmd/edge config check --config configs/edge.yaml
OK configs/edge.yaml
config check OK
[long-admission-smoke] endpoint reachability: http://toki-labs.com:18083/v1/models
[long-admission-smoke]   BLOCKER: /models unreachable. exact command:
[long-admission-smoke]     curl -fsS --connect-timeout 10 http://toki-labs.com:18083/v1/models
[long-admission-smoke] status reachability: http://127.0.0.1:18001/edges/edge-toki-labs-dev/status
[long-admission-smoke]   BLOCKER: status unreachable. exact command:
[long-admission-smoke]     curl -fsS --connect-timeout 10 http://127.0.0.1:18001/edges/edge-toki-labs-dev/status
[long-admission-smoke] expected baseline: normal_capacity_total=9 long_slot_total=4
[long-admission-smoke] === PREFLIGHT BLOCKED (see out-dir; blockers are verification blockers, not user-review) ===
[long-admission-smoke] done rc=3 evidence=/tmp/iop-long-admission-smoke-provider-snapshot-public-reader-followup
```

All format/diff/exact existence/fresh deterministic/concurrent/targeted/race/Edge 전체 PASS. Preflight rc=3 with actual blocker stdout/evidence path matches expected environment condition.

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

## 섹션 소유권

| Section | Owner | Note |
|---------|-------|------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these (archive, complete.log, and task-directory archive move are review-agent only) |
| Roadmap Targets | Fixed at stub creation from plan when present | Implementing agent must not modify; code-review copies it into `complete.log` as `Roadmap Completion` only on PASS |
| Archive Evidence Snapshot | Fixed at stub creation from plan when present | Implementing agent uses it as default prior-loop context; read only the specific archive files cited there when more detail is required |
| Agent UI Completion | Mixed | Present only for plan-required agent-ui code work; implementing agent fills actual evidence, review agent applies `구현됨` status/evidence update on PASS and copies the section into `complete.log` |
| 구현 항목별 완료 여부 (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
| 구현 체크리스트 (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only; final checkbox is mandatory before saving |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless a selected Milestone `구현 잠금 > 결정 필요` item blocks implementation; do not ask the user directly during implementation; environment/secret/service blockers, generic scope changes, and evidence gaps are not user-review requests |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
| 검증 결과 (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only; command changes require a `계획 대비 변경 사항` entry |
| 코드리뷰 결과 | Review agent appends | Not included in stub |

## 코드리뷰 결과

- 종합 판정: FAIL
- 차원별 평가:
  - correctness: Fail
  - completeness: Fail
  - test coverage: Fail
  - API contract: Pass
  - code quality: Pass
  - implementation deviation: Fail
  - verification trust: Fail
  - spec conformance: Fail
- 발견된 문제:
  - Required — `apps/edge/internal/service/status_provider_test.go:1628`: main goroutine이 queue lock을 풀고 reader 결과를 받은 뒤에야 writer를 시작하므로 reader와 writer가 같은 queue/service lock 경계에서 경합하지 않는다. 이 테스트는 `ListNodeSnapshots()`의 queue lock이 제거되어도 old read 뒤 config apply, new read의 순차 흐름으로 그대로 통과한다. `svc.mu` write lock으로 reader를 `runtimeConfigSnapshot()`에서 멈추고, reader가 queue lock을 획득했음을 bounded `TryLock` probe로 확인한 뒤 writer를 시작해 queue lock 대기를 만들고, service lock을 해제하여 public reader old tuple → writer new tuple 순서를 실제 production lock order로 강제해야 한다.
  - Required — `apps/edge/internal/service/status_provider_test.go:1642`: `readerResult`와 `writerDone`을 raw receive로 기다려 barrier 실패 시 테스트 내부에서 종료·정리되지 않고 package timeout까지 정지할 수 있으며, waiter cleanup timeout도 `t.Log`로 성공 처리한다. 모든 barrier/result wait를 `select`와 명시적 timeout으로 제한하고, 실패 경로에서도 보유 lock 해제와 goroutine 종료를 보장하며 waiter 결과의 error/run을 검증해야 한다.
- 다음 단계: FAIL 후속 계획을 `plan` 스킬의 `prepare-follow-up` 모드와 fresh isolated routing으로 생성한다.
