<!-- task=m-runtime-reconnect-config-refresh/06+05_node_drain_runtime_config code_review=G06 tag=REVIEW_NCDRAIN -->

# Code Review - REVIEW_NCDRAIN (G06)

## 구현 에이전트 소유 섹션

### 구현 내용

REVIEW_NCDRAIN-1: `OnConfigRefresh`에서 `runtime.concurrency` mutation을 adapter registry start/swap 성공 이후로 지연시켰다.

**변경된 파일:**
- `apps/node/internal/node/node.go`: runtime concurrency commit ordering 수정
- `apps/node/internal/node/node_test.go`: `TestConfigRefreshFailedAdapterStartDoesNotApplyRuntimeConcurrency` regression test 추가

**상세 변경 (node.go):**
1. **라인 327~348**: runtime config 읽을 때 `nextConcurrency`와 `hasRuntimeConcurrencyChange`만 계산하고 `globalGate.updateCapacity()`를 호출하지 않는다.
2. **라인 353~367**: adapter diff가 없으면 (no-op 경로) `currentConfigSet.Runtime`을 갱신하고 `hasRuntimeConcurrencyChange`가 true면 즉시 concurrency 적용.
3. **라인 376~384**: `nextSet.Registry.Start(ctx)` 실패 시 `FAILED` 반환. 이 시점에 global gate는 mutation되지 않았으므로 이전 상태 유지.
4. **라인 386~411**: adapter registry start 성공 → router swap → adapter gate update → **그 후에** `n.globalGate.updateCapacity(nextConcurrency)` 적용.
5. **라인 438**: `n.currentConfigSet = nextSet` 갱신.

**상세 변경 (node_test.go):**
- `TestConfigRefreshFailedAdapterStartDoesNotApplyRuntimeConcurrency` 추가 (라인 2717~2830)
- `blockingAdapterWithStart` test double 추가 (라인 2832~2865): `MaxConcurrency: 0` (무제한 per-adapter), global gate만으로 결합됨.
- Fixture: concurrency=1, active run 유지 중 `Runtime{Concurrency:2}` + failing CLI persistent profile (`/nonexistent/path/to/binary`) 으로 `Registry.Start` 실패 시나리오 재현.
- Assertion: refresh response `FAILED` + 두 번째 run이 `ErrConcurrencyLimitExceeded`로 reject되어 이전 concurrency=1이 보존됨.

### 검증 stdout/stderr

```
=== RUN   TestConfigRefreshUpdatesGlobalConcurrencyForFutureRuns
--- PASS: TestConfigRefreshUpdatesGlobalConcurrencyForFutureRuns (0.00s)
=== RUN   TestConfigRefreshConcurrencyDecreaseDoesNotCancelInFlight
--- PASS: TestConfigRefreshConcurrencyDecreaseDoesNotCancelInFlight (0.00s)
=== RUN   TestConfigRefreshFailedAdapterStartDoesNotApplyRuntimeConcurrency
--- PASS: TestConfigRefreshFailedAdapterStartDoesNotApplyRuntimeConcurrency (0.00s)
=== RUN   TestConfigRefreshWorkspaceRootReportsRestartRequired
--- PASS: TestConfigRefreshWorkspaceRootReportsRestartRequired (0.00s)
PASS
ok  iop/apps/node/internal/node   0.011s

go test -count=1 ./apps/node/internal/node ./apps/node/internal/adapters ./apps/edge/internal/configrefresh ./apps/edge/internal/bootstrap
ok  iop/apps/node/internal/node   0.299s
ok  iop/apps/node/internal/adapters   0.010s
ok  iop/apps/edge/internal/configrefresh    0.011s
ok  iop/apps/edge/internal/bootstrap    0.264s

git diff --check
(no output - whitespace check passed)
```

### 구현 검증 checklist

- [x] `OnConfigRefresh`에서 `runtime.concurrency`를 adapter registry start/swap 성공 이후에 commit한다. 실패 경로는 mutation 없이 이전 상태 유지.
- [x] `apps/node/internal/node/node_test.go`에 `TestConfigRefreshFailedAdapterStartDoesNotApplyRuntimeConcurrency`를 추가했다.
  - Fixture: concurrency=1, active run 중 `Runtime{Concurrency:2}` + failing CLI persistent profile (`/nonexistent/path/to/binary`) → `FAILED`.
  - Assertion: 두 번째 concurrent run은 계속 `ErrConcurrencyLimitExceeded`로 reject.
- [x] 대상 회귀: `TestConfigRefresh(FailedAdapterStartDoesNotApplyRuntimeConcurrency|UpdatesGlobalConcurrencyForFutureRuns|ConcurrencyDecreaseDoesNotCancelInFlight|WorkspaceRootReportsRestartRequired)` PASS (v4 재검증).
- [x] 최종 회귀: 4패키지 PASS, `git diff --check` PASS (v4 재검증).
- [x] Go test cache output 없음 (`-count=1` 사용).

### 사용자 리뷰 요청

해당 없음. 구현이 막히지 않았고 사용자 리뷰가 필요한 환경/secret/service 차단, 일반 범위 조정, evidence 공백이 없다.

## Reviewer 검증 섹션

- [x] SDD S15 `runtime-config` 실패 원상유지: `runtime.concurrency` live apply가 실패 refresh에서 이전 admission state를 보존함을 regression test로 검증했다.
- [x] SDD S14 `drain` 회귀: 기존 deferred stop, in-flight run completion, follow-up routing tests가 통과했다.
- [x] 빌드/포맷: `git diff --check` PASS.

**Verdict: PASS**

## 코드리뷰 결과

- 종합 판정: PASS
- 차원별 평가:
  - correctness: Pass
  - completeness: Pass
  - test coverage: Pass
  - API contract: Pass
  - code quality: Pass
  - implementation deviation: Pass
  - verification trust: Pass
  - spec conformance: Pass
- 발견된 문제: 없음
- 다음 단계: PASS 완료 처리로 `complete.log`를 작성하고 active task 디렉터리를 archive로 이동한다.

## 리뷰 중 정리

- `gofmt -w apps/node/internal/node/node.go apps/node/internal/node/node_test.go apps/node/internal/node/run_manager.go apps/node/internal/adapters/config_set.go`로 포맷 drift를 정리했다.
- 포맷 정리 후 `gofmt -l` 재확인 결과 출력 없음.
- 포맷 정리 후 대상 회귀 재실행: `go test -count=1 -run 'TestConfigRefresh(FailedAdapterStartDoesNotApplyRuntimeConcurrency|UpdatesGlobalConcurrencyForFutureRuns|ConcurrencyDecreaseDoesNotCancelInFlight|WorkspaceRootReportsRestartRequired)' ./apps/node/internal/node` PASS (`ok iop/apps/node/internal/node 0.008s`).
- 포맷 정리 후 최종 4패키지 재실행: `go test -count=1 ./apps/node/internal/node ./apps/node/internal/adapters ./apps/edge/internal/configrefresh ./apps/edge/internal/bootstrap` PASS (`node 0.292s`, `adapters 0.009s`, `configrefresh 0.013s`, `bootstrap 0.266s`).
- 포맷 정리 후 `git diff --check` PASS.

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

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-local-G06.md`를 `code_review_local_G06_1.log`로 아카이브한다.
- [x] active `PLAN-local-G06.md`를 `plan_local_G06_1.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [x] PASS이므로 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이므로 active task 디렉터리 `agent-task/m-runtime-reconnect-config-refresh/06+05_node_drain_runtime_config/`를 `agent-task/archive/2026/06/m-runtime-reconnect-config-refresh/06+05_node_drain_runtime_config/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-runtime-reconnect-config-refresh`이므로 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이므로 이동 후 빈 active parent `agent-task/m-runtime-reconnect-config-refresh/`를 제거했다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-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로 이동한다.
