<!-- task=m-node-multi-target-serving-foundation/07+02_runtime_concurrency plan=0 tag=REFACTOR -->

# Code Review Reference - REFACTOR

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> Fill implementation-owned sections and actual verification output, then stop with active files in place.
> Do not ask the user directly; record user-only blockers in `사용자 리뷰 요청`.

## 개요

date=2026-06-10
task=m-node-multi-target-serving-foundation/07+02_runtime_concurrency, plan=0, tag=REFACTOR

## Roadmap Targets

- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md`
- Task ids:
  - `runtime-concurrency`: `runtime.concurrency`와 adapter capability가 Node 멀티 호출에서 실제 제한 또는 스케줄링 정책으로 적용된다.
- Completion mode: check-on-pass

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

> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.

concurrency policy가 deterministic하고 foreground/background 양쪽에서 같은지 검토한다.

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REFACTOR-1] Runtime Concurrency Gate | [x] |

## 구현 체크리스트

- [x] Node runtime global concurrency와 adapter/profile concurrency 중 적용 우선순위를 정의한다.
- [x] Foreground/background run 모두 동일한 scheduling/limit 정책을 통과한다.
- [x] 제한 초과 요청의 queue/reject/timeout 중 하나를 선택하고 error/event/store 상태를 테스트로 고정한다.
- [x] race/order test를 추가하고 최종 검증을 실행한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

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

- [x] `코드리뷰 결과`에 판정을 append한다.
- [x] active `CODE_REVIEW-cloud-G08.md`를 `code_review_cloud_G08_N.log`로 아카이브한다.
- [x] active `PLAN-cloud-G07.md`를 `plan_cloud_G07_M.log`로 아카이브한다.
- [ ] PASS이면 `complete.log` 작성 후 archive 이동한다.
- [x] WARN/FAIL이면 후속 상태를 작성한다.

## 계획 대비 변경 사항

- `proto/iop/runtime.proto`: 변경 없음. `NodeRuntimeConfig.concurrency` 필드가 이미 존재하며 충분하다.
- `apps/node/internal/runtime/types.go`: 변경 없음. `Capabilities.MaxConcurrency`는 advisory 값으로 유지한다. runtime gate에는 사용하지 않는다.
- `run_manager.go` 책임 분리 방식: 별도 파일 신설 대신 `run_manager.go`에 `permitManager`를 추가했다. scheduling/limit 책임을 같은 파일에 두는 것이 더 응집도가 높다.

## 주요 설계 결정

- **정책: reject-on-exceed** — queue/timeout 대신 즉시 `ErrConcurrencyLimitExceeded` 반환. 가장 단순하고 deterministic하며 테스트 고정이 가능하다.
- **우선순위: global > adapter.MaxConcurrency** — `NodeRuntimeConfig.Concurrency`(global)가 > 0이면 그것을 사용. 0이면 unlimited. adapter `MaxConcurrency`는 `CAPABILITIES` command 응답에만 사용되며 runtime gate에서는 advisory로만 취급한다.
- **foreground/background 동일 경로** — `OnRunRequest` 내에서 `spec.Background` 분기 이전에 `n.permits.tryAcquire()`를 호출하여 양쪽 모두 동일한 gate를 통과한다.
- **permit 누수 방지** — `run` 클로저의 첫 번째 `defer`를 `n.permits.release()`로 두어 adapter error, timeout, cancellation, 정상 완료 모든 경로에서 반드시 release된다.
- **`New()` 시그니처 추가** — `globalConcurrency int` 파라미터 추가. bootstrap에서 `result.Config.GetRuntime().GetConcurrency()`를 넘기도록 수정.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._

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

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

- permit acquire/release가 adapter error, timeout, cancellation, background run에서 누수되지 않는다.
- 선택한 제한 초과 정책이 테스트와 문서에 일치한다.
- proto-socket transport 변경으로 문제를 우회하지 않았다.

## 검증 결과

### REFACTOR-1 중간 검증

```bash
$ go test -race -count=1 ./apps/node/internal/node/...
ok  	iop/apps/node/internal/node	1.138s
```

### 최종 검증

```bash
$ go test -race -count=1 ./apps/node/internal/node/...
ok  	iop/apps/node/internal/node	1.138s

$ go test -count=1 ./apps/node/...
ok  	iop/apps/node/cmd/node	0.012s
ok  	iop/apps/node/internal/adapters	0.009s
ok  	iop/apps/node/internal/adapters/cli	42.042s
?   	iop/apps/node/internal/adapters/cli/internal/testutil	[no test files]
ok  	iop/apps/node/internal/adapters/cli/status	39.795s
?   	iop/apps/node/internal/adapters/mock	[no test files]
ok  	iop/apps/node/internal/adapters/ollama	0.009s
ok  	iop/apps/node/internal/adapters/vllm	0.011s
ok  	iop/apps/node/internal/bootstrap	0.270s
ok  	iop/apps/node/internal/node	0.020s
ok  	iop/apps/node/internal/router	0.006s
?   	iop/apps/node/internal/runtime	[no test files]
ok  	iop/apps/node/internal/store	0.064s
ok  	iop/apps/node/internal/terminal	0.448s
ok  	iop/apps/node/internal/transport	5.134s
```

## 코드리뷰 결과

- 종합 판정: FAIL
- 차원별 평가:
  - correctness: Fail
  - completeness: Fail
  - test coverage: Fail
  - API contract: Fail
  - code quality: Pass
  - plan deviation: Fail
  - verification trust: Fail
- 발견된 문제:
  - Required: `apps/node/internal/node/node.go:96`에서 concurrency 초과를 `ErrConcurrencyLimitExceeded` 반환으로만 처리하지만, 실제 proto-socket listener는 `OnRunRequest` 에러를 `apps/node/internal/transport/session.go:45`에서 로그로만 남기고 Edge로 응답/이벤트를 보내지 않습니다. 따라서 reject 정책이 실제 Edge-Node 경계에서는 호출자에게 관측되지 않고, store에도 terminal 상태가 남지 않습니다. 제한 초과 시 `RunEvent` error/cancelled 또는 명확한 command/transport 응답 계약을 보내고, store 상태를 failed/rejected 등으로 고정한 뒤 transport 관측 테스트와 store 테스트를 추가해야 합니다.
  - Required: Roadmap Target과 계획은 `runtime.concurrency`와 adapter capability가 Node 멀티 호출의 실제 제한 또는 scheduling 정책으로 적용되어야 한다고 요구하지만, 구현은 `apps/node/internal/node/node.go:92`에서 adapter `Capabilities().MaxConcurrency`를 advisory로만 선언하고 `apps/node/internal/node/run_manager.go:62`의 permitManager에는 global limit만 주입합니다. `apps/node/internal/node/node_test.go:1061`의 unlimited 테스트도 adapter `MaxConcurrency=1`을 무시하는 동작을 고정합니다. global runtime limit과 adapter/profile limit의 effective limit 계산 규칙을 구현하고, global unset/adapter cap present 및 global+adapter cap 조합 테스트를 추가해야 합니다.
  - Required: `apps/node/internal/node/node.go:64`의 run request path와 `apps/node/internal/bootstrap/module.go:72` bootstrap wiring을 바꿨지만, node/testing domain이 요구하는 repo edge-node 진단 또는 full-cycle 실제 구동 증거가 없습니다. 코드 수정 후 `go test`뿐 아니라 변경 범위에 맞는 edge-node diagnostic/full-cycle 검증 또는 실행 불가 사유와 잔여 위험을 review stub에 기록해야 합니다.
- 다음 단계: FAIL이므로 active plan/review를 로그로 아카이브하고, 같은 task 경로에 후속 `PLAN-cloud-G08.md`와 `CODE_REVIEW-cloud-G08.md`를 작성한다.
