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

# Plan - REVIEW_REVIEW_REFACTOR

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

이 계획은 `code_review_cloud_G08_1.log`의 FAIL Required 이슈만 해결하는 후속 작업이다. 사용자에게 직접 질문하지 말고, 사용자 전용 blocker는 `CODE_REVIEW-cloud-G08.md`의 `사용자 리뷰 요청`에 근거와 재개 조건을 기록한 뒤 중단한다.

## 배경

이전 follow-up은 reject store 상태와 adapter cap fallback을 추가했지만, `globalConcurrency`가 node-wide limit이 아니라 adapter key별 limit으로만 작동한다. 또한 reject event 전송 경로가 실제 session/Edge 관측 테스트로 고정되지 않았고, `scripts/e2e-smoke.sh`를 직접 `scripts/dev/edge.sh` + `scripts/dev/node.sh` 진단의 대체로 기록해 testing rule을 충족하지 못했다.

## 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

## 구현 체크리스트

- [ ] `runtime.concurrency`가 서로 다른 adapter key를 포함한 Node 전체 동시 실행 상한으로 작동하고, adapter `MaxConcurrency`는 adapter key별 상한으로 함께 적용된다.
- [ ] concurrency reject가 실제 Edge-facing `RunEvent{type:error}`로 관측되는 integration test가 추가된다.
- [ ] `scripts/dev/edge.sh` + `scripts/dev/node.sh` 직접 진단 증거 또는 정확한 실행 불가 명령/출력/남은 위험이 review stub에 기록된다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_REFACTOR-1] Node-Wide Global Permit

- 문제: `apps/node/internal/node/node.go:101`의 effective limit은 global 값을 반영하지만, `apps/node/internal/node/node.go:102`에서 adapter key별 `permitManager` 하나에만 적용된다. 따라서 global=1이어도 adapter A와 adapter B가 각각 1개씩 동시에 실행될 수 있다.
- 해결 방법: node-wide global permit과 adapter key별 permit을 분리한다. 예: globalConcurrency > 0이면 모든 run이 공유하는 global permit을 먼저 획득하고, adapter `MaxConcurrency` > 0이면 해당 adapter key의 permit도 획득한다. adapter permit 획득 실패 시 이미 획득한 global permit을 즉시 release한다. 성공한 foreground/background run 종료 시 획득한 모든 permit을 release한다.
- 수정 파일 후보:
  - `apps/node/internal/node/node.go`
  - `apps/node/internal/node/run_manager.go`
  - `apps/node/internal/node/node_test.go`
- 테스트 결정: 추가한다. global=1, adapter A cap=4, adapter B cap=4에서 A run이 실행 중이면 B run도 reject되는 테스트가 필요하다. 기존 same-adapter global/adapter cap fallback 테스트는 유지한다.
- 중간 검증:
  - `go test -race -count=1 ./apps/node/internal/node/...`

### [REVIEW_REVIEW_REFACTOR-2] Reject Event Integration

- 문제: `TestConcurrencyLimit_RejectStoreAndEvent`는 zero-value `transport.Session{}`을 사용해 `sess.IsAlive()`가 false가 되므로 `RunEvent` send 경로를 검증하지 않는다.
- 해결 방법: 실제 proto-socket session으로 reject event를 관측하는 integration test를 추가한다. `transport.DialEdge`와 `toki.NewTcpServer`를 사용해 Node session을 만들고, `node.New(...).SetHandler` 경로로 first run을 hold한 뒤 second run을 edge client에서 보내 `RunEvent{type:"error", run_id:<second>}`가 도착하는지 확인한다. store rejected 상태도 같은 테스트 또는 기존 unit test에서 유지한다.
- 수정 파일 후보:
  - `apps/node/internal/node/node_test.go`
  - 필요 시 `apps/node/internal/transport/integration_test.go`
- 테스트 결정: 추가한다. event payload의 `run_id`, `type`, `error`, `session_id/background` 중 계약상 필요한 값을 assertion한다.
- 중간 검증:
  - `go test -race -count=1 ./apps/node/internal/node/...`
  - integration test를 transport package에 둘 경우 `go test -race -count=1 ./apps/node/internal/transport/...`

### [REVIEW_REVIEW_REFACTOR-3] Direct Dev Diagnostic Evidence

- 문제: `./scripts/e2e-smoke.sh`는 보조 smoke이고 스크립트 출력도 직접 `scripts/dev/edge.sh` + `scripts/dev/node.sh` user-flow verification이 여전히 필요하다고 명시한다.
- 해결 방법: 임시 config/포트를 사용해 `scripts/dev/edge.sh`와 `scripts/dev/node.sh`를 별도 프로세스로 직접 실행하고, edge console에 `/nodes`, 메시지 2회, `/capabilities`, `/transport`, `/sessions`, 필요한 경우 `/terminate-session` 입력을 보내 결과를 기록한다. `exec_command`의 PTY 또는 FIFO를 사용할 수 있다. 정말 불가능하면 시도한 정확한 명령, stdout/stderr, 불가능한 이유, 남은 위험을 기록한다.
- 테스트 결정: 검증 증거를 수집한다. `./scripts/e2e-smoke.sh`는 보조 증거로만 별도 기록한다.
- 최종 검증:
  - `go test -race -count=1 ./apps/node/internal/node/...`
  - `go test -count=1 ./apps/node/...`
  - 직접 dev diagnostic: `scripts/dev/edge.sh` + `scripts/dev/node.sh`
  - 보조 smoke: `./scripts/e2e-smoke.sh`

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

- global runtime concurrency는 adapter별이 아니라 Node 전체 상한이다.
- adapter `MaxConcurrency`는 global limit과 별개의 adapter key별 상한으로 적용된다.
- reject event 테스트가 zero-value session을 우회하지 않고 실제 Edge 관측 경로를 탄다.
- 보조 smoke와 직접 dev diagnostic이 서로 대체되지 않고 분리 기록되어 있다.
