<!-- task=m-provider-availability-capacity-queue-foundation/01_probe_contract plan=0 tag=API -->

# Probe Contract Plan - API

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

`CODE_REVIEW-local-G05.md`의 구현 에이전트 소유 섹션을 반드시 채운다. 구현 후 검증을 실행하고 실제 출력, 설계 결정, 계획 대비 변경 사항을 기록한 뒤 active 파일을 남긴 채 리뷰 준비를 보고한다. 사용자 결정, 사용자 소유 외부 환경, scope 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 적고 멈춘다. 직접 사용자에게 질문하거나 `USER_REVIEW.md`, `complete.log`, archive 로그를 만들지 않는다. 명령 재실행이나 산출물 수집으로 해소 가능한 증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

현재 Node adapter는 `Capabilities`에서 targets와 concurrency만 반환하며 provider endpoint 생존과 target/model 가용성 확인 계약이 없다. `status-model` 작은 작업으로 `unknown|available|unavailable` 모델은 생겼지만, Ollama/vLLM/SGLang 계열 provider가 같은 방식으로 상태를 생산하는 인터페이스는 아직 없다. 이 작업은 provider별 probe 차이를 adapter 내부에 숨기는 공통 probe 경계를 만든다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 `CODE_REVIEW-local-G05.md`의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 구현 에이전트는 채팅으로 직접 선택지를 내거나 사용자 입력 도구를 호출하지 않는다. code-review가 요청 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/provider-availability-capacity-queue-foundation.md`
- Task ids:
  - `probe-contract`: Ollama, Lemonade, vLLM, SGLang 등 provider adapter가 endpoint 생존 여부와 target/model 사용 가능 여부를 확인하는 공통 probe 인터페이스를 구현한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/inference-provider-extension/PHASE.md`
- `agent-roadmap/phase/inference-provider-extension/milestones/provider-availability-capacity-queue-foundation.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/project/domain/node/rules.md`
- `agent-ops/rules/project/domain/edge/rules.md`
- `agent-ops/rules/project/domain/platform-common/rules.md`
- `agent-ops/rules/project/domain/testing/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/node-smoke.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `apps/node/internal/runtime/types.go`
- `apps/node/internal/node/node.go`
- `apps/node/internal/node/node_test.go`
- `apps/node/internal/adapters/ollama/ollama.go`
- `apps/node/internal/adapters/ollama/ollama_test.go`
- `apps/node/internal/adapters/vllm/vllm.go`
- `apps/node/internal/adapters/vllm/vllm_test.go`
- `apps/node/internal/adapters/mock/mock.go`

### 테스트 환경 규칙

test_env는 `local`이다. `agent-test/local/rules.md`를 읽었고, 이 subtask의 실제 변경 범위는 `apps/node/**`라 `agent-test/local/node-smoke.md`를 적용한다. 기본 검증은 `go test ./apps/node/...`이며, adapter 실행/stream/cancel 경로를 바꾸면 repo 내부 edge-node 진단 또는 full-cycle 검증을 추가해야 한다. `edge-smoke`와 `platform-common-smoke`는 provider-state 전체 분류 때문에 읽었지만, 이 subtask가 proto/config/edge 파일을 수정하지 않으면 적용하지 않는다.

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

- `Capabilities` command는 `provider_status` 제한만 검증되어 있고 provider endpoint 생존 또는 target/model 가용성 probe는 아직 검증하지 않는다.
- Ollama `/api/tags` 실패와 vLLM `/v1/models` 실패가 `unavailable`로 접히는 회귀 테스트가 없다.
- target이 명시됐지만 provider model 목록에 없을 때 `unavailable`로 나오는 테스트가 없다.

### 심볼 참조

none. 새 interface/type 추가가 중심이며 기존 symbol rename/remove는 없다.

### 분할 판단

split decision policy를 평가했다. shared task group은 `m-provider-availability-capacity-queue-foundation`이다. `01_probe_contract`는 독립 subtask이며 predecessor가 없다. `02+01_edge_snapshot`은 이 probe 결과를 Edge-visible snapshot으로 운반하므로 `01_probe_contract`의 `complete.log`가 필요하다. API foundation과 Edge/proto rollout 경계가 분리되어 multi-plan이 맞다.

### 범위 결정 근거

이 subtask는 Node runtime interface와 provider adapter probe까지만 다룬다. proto schema, Edge service/Control Plane status, capacity queue admission은 `02+01_edge_snapshot` 또는 capacity-queue Epic에서 다룬다. Lemonade adapter 신규 구현도 후속 Milestone 범위라 제외한다.

### 빌드 등급

`local-G05`. 변경 범위가 Node 내부 interface와 adapter tests로 제한되고, httptest 기반 검증으로 reviewer가 재현 가능하다.

## 구현 체크리스트

- [ ] runtime에 provider probe 결과 타입과 interface를 추가하고 `ProviderStatus` 제한 모델을 재사용한다.
- [ ] Ollama adapter가 `/api/tags` endpoint 생존과 target/model 존재 여부를 `available|unavailable`로 반환하도록 구현하고 테스트한다.
- [ ] vLLM adapter가 `/v1/models` endpoint 생존과 target/model 존재 여부를 `available|unavailable`로 반환하도록 구현하고 SGLang 호환 경로를 문서화한다.
- [ ] Node `CAPABILITIES` command가 provider prober를 발견하면 target-aware probe 결과로 `provider_status`와 targets를 채우도록 구현한다.
- [ ] `go test ./apps/node/...`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] Runtime Provider Probe Contract

문제: [apps/node/internal/runtime/types.go](/config/workspace/iop/apps/node/internal/runtime/types.go:77)는 제한된 status 모델만 있고, [apps/node/internal/runtime/types.go](/config/workspace/iop/apps/node/internal/runtime/types.go:174)의 adapter 계약은 execution/capabilities만 가진다.

해결 방법:

```go
// before apps/node/internal/runtime/types.go:174
type Adapter interface {
	Name() string
	Capabilities(ctx context.Context) (Capabilities, error)
	Execute(ctx context.Context, spec ExecutionSpec, sink EventSink) error
}
```

`ProviderProbeResult`와 optional `ProviderProber` interface를 추가한다. Result는 `AdapterName`, `InstanceKey`, `Target`, `Targets`, `Status`, `Detail`, `Metadata` 정도로 제한하고 `NormalizeProviderStatus`를 통과한 status만 외부로 내보낸다.

수정 파일 및 체크리스트:

- [ ] `apps/node/internal/runtime/types.go`에 `ProviderProbeResult`와 `ProviderProber` 추가
- [ ] status normalization helper 재사용

테스트 작성: 직접 test file은 만들지 않는다. Node command와 adapter tests에서 interface 동작을 검증한다.

중간 검증:

```bash
go test ./apps/node/internal/runtime ./apps/node/internal/node
```

### [API-2] Provider Adapter Probe Implementations

문제: [apps/node/internal/adapters/ollama/ollama.go](/config/workspace/iop/apps/node/internal/adapters/ollama/ollama.go:195)와 [apps/node/internal/adapters/vllm/vllm.go](/config/workspace/iop/apps/node/internal/adapters/vllm/vllm.go:165)는 model list fetch 실패를 `nil` targets로 숨긴다. 이 때문에 endpoint down과 empty model list를 Edge-visible 상태로 구분할 수 없다.

해결 방법: `fetchTargets`를 context-aware helper로 바꾸고 error를 반환한다. `ProbeProvider(ctx,target)`는 endpoint error면 `unavailable`, target이 비어 있고 endpoint/list decode가 성공하면 `available`, target이 목록에 있으면 `available`, 없으면 `unavailable`을 반환한다. vLLM adapter는 OpenAI-compatible `/v1/models` 기준이므로 SGLang도 같은 구현 경로를 사용한다.

수정 파일 및 체크리스트:

- [ ] `apps/node/internal/adapters/ollama/ollama.go`
- [ ] `apps/node/internal/adapters/ollama/ollama_test.go`
- [ ] `apps/node/internal/adapters/vllm/vllm.go`
- [ ] `apps/node/internal/adapters/vllm/vllm_test.go`
- [ ] 필요 시 `apps/node/internal/adapters/mock/mock.go`는 항상 `available`인 test-friendly probe로 둔다

테스트 작성: httptest server로 `200 + model list`, `500`, target miss를 각각 검증한다. Test names는 `TestOllamaProbeProviderAvailability`, `TestVllmProbeProviderAvailability`.

중간 검증:

```bash
go test ./apps/node/internal/adapters/ollama ./apps/node/internal/adapters/vllm
```

### [API-3] Node Capabilities Uses Probe

문제: [apps/node/internal/node/node.go](/config/workspace/iop/apps/node/internal/node/node.go:306)는 `Capabilities`만 호출한다. request target이 있어도 target/model 사용 가능 여부를 반영하지 못한다.

해결 방법:

```go
// before apps/node/internal/node/node.go:311
caps, err := adapter.Capabilities(ctx)
if err != nil {
	return n.commandErrorResponse(req, err.Error())
}
```

`adapter.(runtime.ProviderProber)`가 가능하면 `ProbeProvider(ctx, req.GetTarget())`를 호출하고 result map의 `provider_status`, `targets`, optional `provider_detail`을 probe 결과로 채운다. Probe error는 command error로 만들지 말고 `provider_status=unavailable` 결과로 반환한다.

수정 파일 및 체크리스트:

- [ ] `apps/node/internal/node/node.go`
- [ ] `apps/node/internal/node/node_test.go`

테스트 작성: 기존 `TestOnCommandRequest_CapabilitiesProviderStatusModel` 옆에 prober test double을 추가해 target hit/miss와 probe error 결과를 검증한다.

중간 검증:

```bash
go test ./apps/node/internal/node
```

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `apps/node/internal/runtime/types.go` | API-1 |
| `apps/node/internal/adapters/ollama/ollama.go` | API-2 |
| `apps/node/internal/adapters/ollama/ollama_test.go` | API-2 |
| `apps/node/internal/adapters/vllm/vllm.go` | API-2 |
| `apps/node/internal/adapters/vllm/vllm_test.go` | API-2 |
| `apps/node/internal/adapters/mock/mock.go` | API-2 |
| `apps/node/internal/node/node.go` | API-3 |
| `apps/node/internal/node/node_test.go` | API-3 |

## 최종 검증

```bash
go test ./apps/node/...
```

기대 결과: 모든 node package test가 통과한다. Go test cache 출력은 허용하지 않는다. fresh 실행이 필요하면 구현 에이전트가 `go test -count=1 ./apps/node/...`로 대체하고 사유와 출력을 review stub에 기록한다.

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