<!-- task=m-runtime-reconnect-config-refresh/05+04_node_adapter_diff plan=4 tag=REVIEW_REVIEW_REVIEW_REVIEW_NCADAPT -->

# Plan - REVIEW_REVIEW_REVIEW_REVIEW_NCADAPT

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 후 검증을 실행하고, 실제 구현 내용과 실제 stdout/stderr를 리뷰 stub에 채운 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 최종 archive, `complete.log`, roadmap 완료 처리는 code-review/runtime 책임이다. 선택된 Milestone의 `구현 잠금 > 결정 필요` 항목 때문에 구현이 막힐 때만 리뷰 stub의 `사용자 리뷰 요청` 섹션에 정확한 근거를 적고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하거나, `USER_REVIEW.md`/`complete.log`를 직접 만들지 않는다. 환경/secret/service 차단, 일반 범위 조정, 후속 에이전트가 닫을 수 있는 evidence 공백은 사용자 리뷰 요청이 아니다.

## 배경

세 번째 NCADAPT follow-up은 production 동작을 새로 바꾸지 않고 두 regression test의 증거력을 보강했다. 리뷰 결과, 테스트가 `time.Sleep` 대신 request-start signal을 사용하도록 개선된 점은 확인됐지만, 신호 채널이 unbuffered라 handler가 먼저 도착하면 nonblocking send가 드롭될 수 있다. 이번 follow-up은 해당 latch를 유실 불가능하게 만들어 test evidence 신뢰성을 닫는다.

## 사용자 리뷰 요청 흐름

구현 중 사용자 리뷰 요청은 선택된 Milestone lock decision이 실제 구현을 막을 때만 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 해당 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 템플릿을 따른다. 구현 에이전트는 직접 사용자에게 질문하지 않으며, code-review가 요청 타당성을 검증하고 필요할 때만 `USER_REVIEW.md`를 작성한다.

## Archive Evidence Snapshot

- Prior active task: `agent-task/m-runtime-reconnect-config-refresh/05+04_node_adapter_diff`
- Archived plan: `agent-task/m-runtime-reconnect-config-refresh/05+04_node_adapter_diff/plan_local_G07_3.log`
- Archived review: `agent-task/m-runtime-reconnect-config-refresh/05+04_node_adapter_diff/code_review_local_G07_3.log`
- Verdict: FAIL
- Required summary:
  - `apps/node/internal/node/node_test.go:2179` and `apps/node/internal/node/node_test.go:2421` create unbuffered `startedChan` values while the httptest handlers use nonblocking sends. A handler can reach the send before the test goroutine starts receiving, drop the signal, then block on `blockChan` while the test blocks forever on `<-startedChan`.
- Suggested summary: 없음.
- Verification evidence from review:
  - `go test -count=1 ./apps/node/internal/node ./apps/node/internal/adapters ./apps/node/internal/router` PASS.
  - `go test -count=20 -timeout=10s -run 'TestNodeConfigRefresh(DoesNotStopUnchangedActiveAdapterWhenSiblingChanges|DecreasesExistingAdapterGateCapacity)$' ./apps/node/internal/node` PASS.
  - `go test -count=1 ./apps/node/...` PASS.
  - `git diff --check` PASS.
- Affected files: `apps/node/internal/node/node_test.go`.
- Roadmap carryover: keep `adapter-diff` target open; no `complete.log` was written.
- Additional archive reread allowed: only the two archived files listed above.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/update-plane-self-update-foundation/milestones/runtime-reconnect-config-refresh.md`
- Task ids:
  - `adapter-diff`: Node는 adapter instance 추가/수정/삭제, provider endpoint/header/capacity 변경을 diff로 적용한다. 검증: openai_compat provider capacity 변경과 endpoint 변경이 adapter registry에 반영된다.
- Completion mode: check-on-pass

## 분석 결과

### SDD 기준

- SDD: `agent-roadmap/sdd/update-plane-self-update-foundation/runtime-reconnect-config-refresh/SDD.md`
- 상태: 승인됨, SDD 잠금 해제.
- 대상 Acceptance Scenario: S13 / Milestone Task `adapter-diff`.
- Evidence Map: S13은 Node adapter registry diff test for openai_compat와 capacity/endpoint changes reflected evidence를 요구한다.
- 이번 follow-up은 S13 evidence 자체를 바꾸지 않고, 그 evidence를 뒷받침하는 두 regression test의 request-start synchronization을 deterministic하게 만든다.

### 테스트 환경 규칙

- test_env: `local`.
- Matched profile: `agent-test/local/node-smoke.md`.
- 적용 명령: focused two-test 반복 실행, targeted Node packages, full Node regression, `git diff --check`.

### 분할 판단

- split decision policy를 평가했다.
- 이 follow-up은 같은 subtask `05+04_node_adapter_diff`의 plan=4이며, 단일 테스트 동기화 패턴 수정만 다룬다.
- production behavior, proto, Edge, field smoke로 범위를 확장하지 않는다.

### 범위 결정 근거

- 포함: `apps/node/internal/node/node_test.go`의 두 `startedChan` latch를 buffered 또는 equivalent lossless signal로 바꾸는 수정.
- 선택 포함: stale capacity 회귀 시 test가 장시간 hang하지 않도록 두 번째 run에 짧은 context timeout assertion을 추가하는 보강.
- 제외: `apps/node/internal/node/node.go`, `run_manager.go`, router, adapter factory production behavior 변경.

### 빌드 등급

- lane/GNN: `local-G05`.
- 이유: 수정 범위는 두 테스트의 synchronization evidence에 한정되고 deterministic Go tests와 코드 inspection으로 검증 가능하다.

## 구현 체크리스트

- [ ] `TestNodeConfigRefreshDoesNotStopUnchangedActiveAdapterWhenSiblingChanges`의 request-start signal이 handler 선도착 시에도 유실되지 않도록 buffered channel 또는 equivalent latch로 수정한다.
- [ ] `TestNodeConfigRefreshDecreasesExistingAdapterGateCapacity`의 request-start signal이 handler 선도착 시에도 유실되지 않도록 buffered channel 또는 equivalent latch로 수정한다.
- [ ] focused 반복 검증, targeted Node packages, full Node regression, `git diff --check`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- 이 follow-up은 같은 subtask `05+04_node_adapter_diff` 안의 plan=4다.
- 구현 순서: first test latch fix -> second test latch fix -> focused repeated test -> targeted/full verification.

### [REVIEW_REVIEW_REVIEW_REVIEW_NCADAPT-1] Lossless Request-Start Signal

#### 문제

두 테스트의 `startedChan`이 unbuffered인데 handler send가 nonblocking이다. 수신자가 아직 대기하지 않는 아주 짧은 스케줄링 구간에서 signal이 드롭될 수 있고, 테스트는 `<-startedChan`에서 멈춘다.

#### 해결 방법

두 테스트의 `startedChan`을 `make(chan struct{}, 1)`로 만들거나 동일한 의미의 one-shot latch helper를 사용한다. 기존 `select { case startedChan <- struct{}{}: default: }` 패턴은 buffered channel과 함께 유지해도 된다. 이렇게 하면 handler가 test goroutine보다 먼저 도착해도 signal 하나가 저장되고, 이후 `<-startedChan`이 확정적으로 진행된다.

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

- [ ] `apps/node/internal/node/node_test.go`: `TestNodeConfigRefreshDoesNotStopUnchangedActiveAdapterWhenSiblingChanges`의 `startedChan` 신호 유실 가능성을 제거한다.
- [ ] `apps/node/internal/node/node_test.go`: `TestNodeConfigRefreshDecreasesExistingAdapterGateCapacity`의 `startedChan` 신호 유실 가능성을 제거한다.

#### 테스트 작성

- 새 테스트 추가는 필수 아님. 기존 두 regression test가 deterministic latch를 사용하도록 수정한다.
- 선택 보강: stale capacity 2 회귀 시 `run-2`가 block되어 긴 timeout에 의존하지 않도록 `context.WithTimeout`을 사용해 `ErrConcurrencyLimitExceeded`와 다른 결과가 즉시 실패하도록 만든다.

#### 중간 검증

```bash
go test -count=20 -timeout=10s -run 'TestNodeConfigRefresh(DoesNotStopUnchangedActiveAdapterWhenSiblingChanges|DecreasesExistingAdapterGateCapacity)$' ./apps/node/internal/node
```

기대 결과: 두 regression test가 반복 실행에서도 timeout 없이 PASS.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/node/internal/node/node_test.go` | REVIEW_REVIEW_REVIEW_REVIEW_NCADAPT-1 |

## 최종 검증

```bash
go test -count=20 -timeout=10s -run 'TestNodeConfigRefresh(DoesNotStopUnchangedActiveAdapterWhenSiblingChanges|DecreasesExistingAdapterGateCapacity)$' ./apps/node/internal/node
go test -count=1 ./apps/node/internal/node ./apps/node/internal/adapters ./apps/node/internal/router
go test -count=1 ./apps/node/...
git diff --check
```

기대 결과: focused 반복 테스트, targeted Node tests, 전체 Node regression PASS, whitespace error 없음.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
