<!-- task=m-edge-model-group-queue-scheduling/03+01_node_queue_simplify plan=0 tag=REFACTOR -->

# Plan - REFACTOR Node Queue Simplify

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 후 검증 명령을 실행하고 실제 출력, 구현 메모, 계획 대비 변경 사항을 채운 뒤 active 파일을 남긴 채 리뷰 준비 상태로 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다. 구현 중 사용자만 결정할 수 있는 범위 변경, 외부 환경/secret, 또는 scope conflict가 생기면 chat에서 묻지 말고 review stub의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 남긴다.

## 배경

현재 Node는 provider-scoped FIFO admission queue와 node-wide global gate를 소유한다. 현재 마일스톤의 목표는 queue owner를 Edge로 옮기는 것이므로, Node는 dispatch된 실행을 수행하고 상태를 보고하는 역할로 단순화되어야 한다. Edge queue core가 먼저 들어간 뒤 Node-local FIFO는 제거하거나 over-dispatch 안전장치 수준으로 축소한다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하지 않는다. code-review가 해당 요청을 검증하고 필요할 때만 `USER_REVIEW.md`를 작성한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/edge-model-group-queue-scheduling.md`
- Task ids:
  - `node-simplify`: Node-local provider FIFO queue가 제거되거나 Edge dispatch 이후의 실행 안전장치로 축소되어, 각 Node가 queue owner가 되지 않는다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-test/local/rules.md`
- `agent-test/local/node-smoke.md`
- `agent-test/local/edge-smoke.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `apps/node/internal/node/node.go`
- `apps/node/internal/node/run_manager.go`
- `apps/node/internal/node/node_test.go`
- `apps/node/internal/node/node_concurrency_integration_test.go`
- `apps/node/internal/runtime/types.go`
- `apps/node/internal/router/router.go`

### 테스트 환경 규칙

- test_env: `local`.
- 적용 profile: `node-smoke`, `edge-smoke`.
- 필수 명령: `go test -count=1 ./apps/node/internal/node`.
- Node 실행 요청/stream/cancel/status 경로에 닿으므로 최종 regression subtask에서 repo 내부 edge-node 진단과 full-cycle 기준을 이어서 수행한다.

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

- 현재 Node tests는 Node-owned FIFO queue를 성공 조건으로 삼는 테스트가 많다.
- Edge-owned queue 전환 뒤에는 Node가 queued status를 만들지 않아야 하므로 기존 `TestAdmissionQueue_*` 계열은 삭제, 이동, 또는 "over-dispatch immediate rejection/safety" 기준으로 전환해야 한다.
- Node capabilities의 queued/in_flight 값은 Edge-owned snapshot과 충돌하지 않아야 한다. queued는 0 또는 safety-only 값으로 정리해야 한다.

### 심볼 참조

- 제거/축소 후보: `fifoGate`, `admissionManager`, `admissionTicket`, `ErrConcurrencyLimitExceeded` 주변 queue_full/queue_timeout helper.
- call site:
  - `apps/node/internal/node/node.go:35` `globalGate`
  - `apps/node/internal/node/node.go:36-37` `adapterGates`
  - `apps/node/internal/node/node.go:98-213` admission enqueue/wait/launch flow
  - `apps/node/internal/node/node.go:216-242` admission helper
  - `apps/node/internal/node/node.go:332-344` capabilities in_flight/queued snapshot
  - `apps/node/internal/node/node_test.go:1438+` admission queue tests
  - `apps/node/internal/node/node_concurrency_integration_test.go:91+` queue overflow integration test

### 분할 판단

- shared task group: `m-edge-model-group-queue-scheduling`.
- 현재 subtask: `03+01_node_queue_simplify`.
- predecessor `01`: active `agent-task/m-edge-model-group-queue-scheduling/01_edge_queue_service/complete.log` 없음. 구현 시작 전 `01_edge_queue_service` PASS complete.log가 필요하다.
- snapshot/surface 조정은 `02+01_surface_snapshot_contract`, final regression은 `04+01,02,03_regression_evidence`로 분리한다.

### 범위 결정 근거

- Node adapter provider probe, runtime adapter contracts, router lookup semantics는 바꾸지 않는다.
- Edge scheduler 구현을 이 subtask에서 재설계하지 않는다.
- 외부 OpenAI/A2A/console surface는 Node simplification 검증 범위가 아니므로 제외한다.

### 빌드 등급

- `cloud-G07`: 기존 Node admission queue는 cancellation, background run, timeout, store status, provider snapshot에 걸쳐 있어 대량 테스트 전환과 concurrency risk가 크다.

## 구현 체크리스트

- [ ] Node `OnRunRequest`에서 FIFO queue ownership을 제거하고 dispatch된 run을 즉시 실행 또는 safety reject로 처리한다.
- [ ] Node capability snapshot의 queued/in-flight 의미를 Edge-owned queue와 충돌하지 않게 정리한다.
- [ ] Node admission queue tests를 삭제/전환하고 over-dispatch safety, cancel, background, store lifecycle 테스트를 남긴다.
- [ ] `go test -count=1 ./apps/node/internal/node`가 통과한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REFACTOR-1] OnRunRequest Queue Ownership 제거

#### 문제

Node가 provider FIFO queue owner다.

```go
// apps/node/internal/node/node.go:98
98  // Admission gate (foreground and background use the same path).
110 admission := n.admissionFor(spec.Adapter, caps)
128 ticket, err := admission.enqueue()
167 if err := ticket.wait(execCtx); err != nil {
177 defer ticket.release()
```

#### 해결 방법

`OnRunRequest`는 router resolve, store insert, runManager register, adapter execute lifecycle만 맡긴다. capacity guard가 필요하면 queue 없는 non-blocking safety gate로 두고, full이면 즉시 error event와 rejected store status를 남긴다. queued store status는 Edge queue가 소유하므로 Node-local 대기 상태로 쓰지 않는다.

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

- [ ] `apps/node/internal/node/node.go`: admission enqueue/wait 흐름 제거 또는 safety guard로 축소.
- [ ] `apps/node/internal/node/run_manager.go`: FIFO queue helper 제거 또는 safety-only helper로 축소.

#### 테스트 작성

- 작성: `TestOnRunRequest_DispatchedRunRunsImmediately`, `TestOnRunRequest_OverDispatchSafetyRejectsWithoutQueue` 또는 기존 테스트 전환.

#### 중간 검증

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

### [REFACTOR-2] Capability Snapshot 정리

#### 문제

Node command capabilities가 Node-local gate의 in-flight/queued를 보고한다.

```go
// apps/node/internal/node/node.go:332
332 inFlight := 0
333 queued := 0
334 if ok {
335     inFlight = gate.activeCount()
336     queued = gate.queuedCount()
337 }
```

Edge-owned snapshot과 섞이면 queued 의미가 충돌한다.

#### 해결 방법

Node는 provider availability/capacity와 safety in-flight만 보고하고 queued는 0으로 고정하거나 safety queue가 없음을 명시한다. Edge-owned queued는 `02`의 service snapshot이 표현한다.

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

- [ ] `apps/node/internal/node/node.go`: capabilities result/snapshot queued semantics 정리.
- [ ] `apps/node/internal/node/node_test.go`: capabilities queued 테스트 전환.

#### 테스트 작성

- 작성/전환: `TestOnCommandRequest_Capabilities`, `TestOnCommandRequest_Capabilities_InFlight`, 기존 `TestOnCommandRequest_Capabilities_Queued` 삭제 또는 safety-only 이름으로 변경.

#### 중간 검증

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

### [REFACTOR-3] Admission Queue Test 전환

#### 문제

`apps/node/internal/node/node_test.go:1438+` 이후는 Node-owned FIFO를 전제로 한다. Edge queue 전환 뒤 이 테스트들이 그대로 남으면 목표와 반대 동작을 고정한다.

#### 해결 방법

FIFO ordering, max_queue, queue_timeout, release promotion 테스트는 제거하거나 Edge service queue tests로 이동된 것을 확인한다. Node에는 cancel/background/store/error lifecycle과 over-dispatch immediate safety만 남긴다.

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

- [ ] `apps/node/internal/node/node_test.go`: `TestAdmissionQueue_*` 계열 정리.
- [ ] `apps/node/internal/node/node_concurrency_integration_test.go`: queue overflow integration test를 safety rejection 또는 제거로 전환.

#### 테스트 작성

- 작성: over-dispatch safety event가 Edge로 도착하는 통합 테스트가 필요하면 기존 `TestQueueOverflow_RejectEventObservedByEdge`를 재명명해 즉시 rejection 기준으로 유지한다.

#### 중간 검증

```bash
go test -count=1 ./apps/node/internal/node -run 'Test(AdmissionQueue|ConcurrencyLimit|QueueOverflow|OnRunRequest|OnCommandRequest_Capabilities)'
```

## 의존 관계 및 구현 순서

- `01_edge_queue_service` complete.log가 먼저 필요하다.
- 구현 전 확인 후보:
  - active: `agent-task/m-edge-model-group-queue-scheduling/01_edge_queue_service/complete.log`
  - archive: `agent-task/archive/*/*/m-edge-model-group-queue-scheduling/01_edge_queue_service/complete.log`

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/node/internal/node/node.go` | REFACTOR-1, REFACTOR-2 |
| `apps/node/internal/node/run_manager.go` | REFACTOR-1 |
| `apps/node/internal/node/node_test.go` | REFACTOR-1, REFACTOR-2, REFACTOR-3 |
| `apps/node/internal/node/node_concurrency_integration_test.go` | REFACTOR-3 |

## 최종 검증

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

기대 결과: 통과. Go test cache는 허용하지 않는다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
