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

# Plan - 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 리뷰에서 production 쪽 보수화 구현과 검증 명령은 통과했지만, 두 회귀 테스트가 이전 결함을 실제로 잡지 못하는 형태로 작성되어 있었다. 이번 follow-up은 `adapter-diff` 완료 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_2.log`
- Archived review: `agent-task/m-runtime-reconnect-config-refresh/05+04_node_adapter_diff/code_review_local_G07_2.log`
- Verdict: FAIL
- Required summary:
  - `apps/node/internal/node/node_test.go:2220` does not actually create the unchanged active adapter + changed/removed sibling case; active `adapter-A` is updated to `mock`.
  - `apps/node/internal/node/node_test.go:2364` rejects a third run while two runs are already active, so stale capacity 2 and refreshed capacity 1 both pass.
- 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=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

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-roadmap/current.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/node-smoke.md`
- `agent-roadmap/phase/update-plane-self-update-foundation/milestones/runtime-reconnect-config-refresh.md`
- `agent-roadmap/sdd/update-plane-self-update-foundation/runtime-reconnect-config-refresh/SDD.md`
- `agent-task/m-runtime-reconnect-config-refresh/05+04_node_adapter_diff/plan_local_G07_2.log`
- `agent-task/m-runtime-reconnect-config-refresh/05+04_node_adapter_diff/code_review_local_G07_2.log`
- `agent-task/m-runtime-reconnect-config-refresh/05+04_node_adapter_diff/plan_cloud_G07_1.log`
- `agent-task/m-runtime-reconnect-config-refresh/05+04_node_adapter_diff/code_review_cloud_G07_1.log`
- `apps/node/internal/node/node.go`
- `apps/node/internal/node/run_manager.go`
- `apps/node/internal/node/node_test.go`
- `apps/node/internal/router/router.go`
- `apps/node/internal/adapters/config_set.go`
- `apps/node/internal/adapters/registry.go`
- `apps/node/internal/adapters/factory.go`
- `apps/node/internal/adapters/cli/cli.go`
- `apps/node/internal/adapters/mock/mock.go`
- `apps/node/internal/bootstrap/module.go`

### 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 completion evidence 중 capacity decrease와 old-registry stop-scope regression이 이전 결함을 실제로 검출하도록 테스트를 보강한다. S14 full drain은 범위가 아니다.

### 테스트 환경 규칙

- test_env: `local`.
- `agent-test/local/rules.md` present/read.
- Matched profile: `agent-test/local/node-smoke.md`.
- 적용 명령: `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`.
- Go test cache output은 허용하지 않는다.

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

- Old registry stop scope: 현재 sibling-change 테스트는 active adapter를 unchanged로 유지하지 않아 이전 `updated/removed` 한정 active-run 검사 결함을 잡지 못한다.
- Adapter gate capacity decrease: 현재 decrease 테스트는 두 run active 상태에서 세 번째 run만 거부해 stale capacity 2와 refreshed capacity 1을 구분하지 못한다.

### 심볼 참조

- rename/remove 없음.

### 분할 판단

- split decision policy를 평가했다.
- 이 follow-up은 기존 split subtask `05+04_node_adapter_diff`의 plan=3이며, 두 Required 모두 같은 파일의 test evidence correction이다.
- production API, cross-domain 계약, runtime behavior를 새로 바꾸지 않으므로 별도 subtask로 분리하지 않는다.

### 범위 결정 근거

- 포함: `apps/node/internal/node/node_test.go`의 두 regression test 수정과 필요 시 테스트 helper 보강.
- 제외: `apps/node/internal/node/node.go`, `run_manager.go`, router, adapter factory production behavior 변경. 테스트를 통과시키기 위한 production 완화는 이 follow-up 범위가 아니다.
- 제외: full deferred stop scheduling, runtime concurrency/workspace root live apply, Edge/proto/field smoke. 해당 항목은 sibling task 범위다.

### 빌드 등급

- lane/GNN: `local-G07`.
- 이유: 변경은 Node test evidence에 한정되어 deterministic Go tests로 검증 가능하지만, active-run ordering과 capacity gate regression의 증거력을 판단해야 한다.

## 구현 체크리스트

- [ ] `TestNodeConfigRefreshDoesNotStopUnchangedActiveAdapterWhenSiblingChanges`가 active adapter를 refresh 후에도 unchanged로 유지하고, changed/removed sibling 때문에 old registry `Stop`이 호출되지 않는지 검증하도록 수정한다.
- [ ] `TestNodeConfigRefreshDecreasesExistingAdapterGateCapacity`가 capacity 2에서 active run 하나만 유지한 뒤 capacity 1 refresh 후 두 번째 run이 `ErrConcurrencyLimitExceeded`로 거부되는지 검증하도록 수정한다.
- [ ] `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`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- 이 follow-up은 같은 subtask `05+04_node_adapter_diff` 안의 plan=3이다.
- 선행 구현은 현재 working tree와 archived review `code_review_local_G07_2.log`로 확인한다.
- 구현 순서: unchanged sibling regression 수정 -> capacity decrease evidence 수정 -> targeted tests -> full Node regression.

### [REVIEW_REVIEW_REVIEW_NCADAPT-1] Unchanged Active Sibling Stop Regression

#### 문제

`apps/node/internal/node/node_test.go:2220`의 refresh payload는 active `adapter-A`를 `Type: "mock"`으로 바꾼다. 따라서 `adapter-A`는 unchanged active adapter가 아니라 updated adapter이고, 이전 실패 구현처럼 updated/removed adapter만 active-run 검사해도 테스트가 통과할 수 있다.

#### 해결 방법

active run은 refresh 후에도 same key/type/fingerprint로 남는 adapter에 걸고, diff는 sibling adapter에서만 발생시킨다. Supported payload 경로만 써야 하면 active adapter는 blocked `openai_compat`로 구성하고, old registry에 test lifecycle sibling을 수동 등록한 뒤 next payload에서 active `openai_compat`만 동일하게 유지해 sibling removal을 만든다. Assertion은 sibling lifecycle adapter `Stop`이 active run 중 호출되지 않는지 확인해야 한다.

Before `apps/node/internal/node/node_test.go:2220`:

```go
refreshPayload := &iop.NodeConfigPayload{
	Adapters: []*iop.AdapterConfig{
		{
			Name:    "adapter-A",
			Type:    "mock",
			Enabled: true,
		},
	},
}
```

After:

```go
// Keep the active adapter unchanged and change/remove only its sibling.
refreshPayload := unchangedActiveAdapterPayload(server.URL)
```

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

- [ ] `apps/node/internal/node/node_test.go`: test fixture가 active adapter unchanged와 sibling removed/changed를 분명히 만들도록 수정한다.
- [ ] `apps/node/internal/node/node_test.go`: `time.Sleep`만으로 active 상태를 추정하지 말고 request-start channel 등 deterministic signal을 사용한다.

#### 테스트 작성

- 수정 대상: `TestNodeConfigRefreshDoesNotStopUnchangedActiveAdapterWhenSiblingChanges`.
- Assertion: active adapter가 diff `Unchanged`에 해당하고 sibling만 `Removed` 또는 `Updated`인 refresh에서 old registry `Stop`이 호출되지 않는다. 기존 active run은 정상 완료된다.

#### 중간 검증

```bash
go test -count=1 ./apps/node/internal/node
```

기대 결과: unchanged active sibling regression이 이전 결함을 잡는 형태로 PASS.

### [REVIEW_REVIEW_REVIEW_NCADAPT-2] Capacity Decrease Evidence

#### 문제

`apps/node/internal/node/node_test.go:2364`는 capacity 2에서 두 run을 이미 active로 만든 뒤 capacity 1 refresh를 적용하고 세 번째 run 거부를 확인한다. stale gate가 capacity 2로 남아 있어도 in-flight가 2라 세 번째 run은 거부되므로 `updateCapacity(1)` 적용 여부를 검증하지 못한다.

#### 해결 방법

capacity 2로 기존 gate를 만든 뒤 active run은 하나만 유지한다. refresh로 capacity 1을 적용한 후 두 번째 run이 `ErrConcurrencyLimitExceeded`로 거부되어야 stale capacity 2와 refreshed capacity 1을 구분할 수 있다. 기존 active run은 refresh 후에도 정상 완료되어야 한다.

Before `apps/node/internal/node/node_test.go:2424`:

```go
errChan1 := make(chan error, 1)
go func() { ... }()

errChan2 := make(chan error, 1)
go func() { ... }()
```

After:

```go
errChan1 := make(chan error, 1)
go func() { ... }()
waitForBlockedCompletion(t, requestStarted)
// refresh capacity 2 -> 1, then run-2 must be rejected.
```

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

- [ ] `apps/node/internal/node/node_test.go`: capacity decrease test를 one-active-run scenario로 바꾼다.
- [ ] `apps/node/internal/node/node_test.go`: httptest handler가 completion request 진입을 channel로 알려 테스트가 sleep에 의존하지 않게 한다.

#### 테스트 작성

- 수정 대상: `TestNodeConfigRefreshDecreasesExistingAdapterGateCapacity`.
- Assertion: capacity 2에서 one active run, refresh capacity 1, second run rejects with `ErrConcurrencyLimitExceeded`, first run completes successfully.

#### 중간 검증

```bash
go test -count=1 ./apps/node/internal/node
```

기대 결과: stale gate capacity 2이면 실패하고 refreshed capacity 1이면 PASS.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/node/internal/node/node_test.go` | REVIEW_REVIEW_REVIEW_NCADAPT-1, REVIEW_REVIEW_REVIEW_NCADAPT-2 |

## 최종 검증

```bash
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
```

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