<!-- task=m-usage-token-log-ops-mvp/02+01_usage_metrics plan=3 tag=REVIEW_REVIEW_USAGE_METRIC_RETRY -->

# Plan - REVIEW_REVIEW_USAGE_METRIC_RETRY

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

이 plan은 코드리뷰 FAIL의 좁은 후속 수정이다. active `CODE_REVIEW-local-G04.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 마지막 단계다. 구현 후 active 파일을 유지하고 리뷰 준비 상태로 보고한다.

구현 중 직접 사용자에게 질문하지 않는다. 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청` 섹션에 근거를 기록하고 멈춘다. 현재 후속 범위에는 사용자 결정이 필요한 항목이 없다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/usage-token-log-ops-mvp.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/usage-token-log-ops-mvp.md)
- Task ids:
  - `openai-scope`: 1차 metric 대상은 OpenAI-compatible 호출로 제한한다.
  - `passthrough-safe`: pure `passthrough` response body를 바꾸지 않고 Edge 내부 metric만 emit한다.
  - `token-breakdown`: input/output/reasoning/cached_input token type을 구분한다.
  - `reasoning-observed`: provider가 reasoning token을 보고하지 않으면 token 추정 없이 보조 metric만 남긴다.
  - `prometheus-metrics`: low-cardinality Prometheus counter와 label allowlist를 구현한다.
- Completion mode: check-on-pass

## Archive Evidence Snapshot

- Current archived plan: `agent-task/m-usage-token-log-ops-mvp/02+01_usage_metrics/plan_local_G04_2.log`
- Current archived review: `agent-task/m-usage-token-log-ops-mvp/02+01_usage_metrics/code_review_local_G04_2.log`
- Verdict: FAIL
- Required summary:
  - `apps/edge/internal/openai/stream.go:985` and `apps/edge/internal/openai/stream.go:1153`: `passthrough+sideband` metric emit uses only `ProviderTunnelFrameKind_USAGE` values and ignores provider body usage already parsed by `providerChatAssembler`, while production `openai_compat`/`vllm` provider tunnel relay emits response body frames and no separate USAGE frame.
- Affected files:
  - `apps/edge/internal/openai/stream.go`
  - `apps/edge/internal/openai/usage_metrics_test.go`
  - `apps/edge/internal/openai/server_test.go`
  - `apps/node/internal/adapters/openai_compat/openai_compat.go` (evidence only unless implementation proves adapter change is necessary)
  - `apps/node/internal/adapters/vllm/vllm.go` (evidence only unless implementation proves adapter change is necessary)
- Verification evidence from failed loop:
  - `GOCACHE=/config/workspace/iop/.cache/go-build go test -count=1 ./apps/edge/internal/openai -run 'Sideband|UsageMetrics|ToolValidation|Dispatch|Reasoning|Retry'` PASS.
  - `GOCACHE=/config/workspace/iop/.cache/go-build go test -count=1 ./apps/edge/internal/openai` PASS.
  - `git diff --check` PASS.
  - Review-agent applied `gofmt` to `apps/edge/internal/openai/server_test.go` and `apps/edge/internal/openai/usage_metrics_test.go`; `gofmt -l` then returned no files.
- Roadmap carryover: same Roadmap Targets remain claimed only after this follow-up closes real provider-body usage coverage for sideband token breakdown.
- Narrow reread allowed if needed: only the two archived files listed above.

## 범위 결정 근거

후속 범위는 `passthrough+sideband` metric observation source 보정에 한정한다. retry dispatch failure metric과 sideband body write cancel metric은 현재 코드와 재실행한 테스트에서 닫혔다. 이번 plan은 sideband writer가 테스트 전용 USAGE frame에만 의존하지 않고 실제 provider tunnel body의 OpenAI-compatible `usage` object를 metric으로 반영하도록 만든다.

## 구현 체크리스트

- [ ] `writeProviderTunnelSidebandStream` terminal emit이 provider SSE body에서 파싱된 `providerChatAssembler.usageObservation()`을 반영하도록 수정한다. USAGE frame이 별도로 있는 경우에도 input/output/reasoning/cached_input token type을 잃지 않게 병합 또는 우선순위를 명확히 한다.
- [ ] `writeProviderTunnelSidebandResponse` terminal emit이 non-stream provider JSON body의 `usage` object를 반영하도록 수정한다. sideband response schema를 불필요하게 확장하지 않더라도 Prometheus token metric은 input/output/reasoning/cached_input을 구분해야 한다.
- [ ] production relay와 같은 형태로 USAGE frame 없이 provider body만 흐르는 sideband streaming regression test를 추가한다. `tunnelProviderURL` 또는 동등한 실제 body-frame relay fixture를 사용하고, request counter가 `usage_source="provider_reported"`로 1 증가하며 input/output/reasoning/cached_input token counters가 증가하는지 검증한다.
- [ ] USAGE frame 없이 provider body만 흐르는 sideband non-streaming regression test를 추가한다. provider response body는 envelope 안에서 유지되고, Prometheus token counters가 provider body usage detail을 반영하는지 검증한다.
- [ ] 최종 검증 명령을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_REVIEW_USAGE_METRIC_RETRY-1] Sideband Provider Body Usage Metrics

문제: sideband writer들은 body parser가 provider `usage` object를 읽어도 metric emit에서 그 값을 사용하지 않는다. 현재 success tests는 `ProviderTunnelFrameKind_USAGE`를 직접 주입하지만, 실제 `openai_compat`/`vllm` provider tunnel relay는 response-start/body/end frame만 만든다. 따라서 실제 sideband provider 호출은 provider body에 usage가 있어도 token metric이 `provider_reported`로 증가하지 않을 수 있다.

해결 방법:
- streaming sideband defer에서 `pendingMerged`만 emit하지 말고 `assembler.usageObservation()`을 포함한다.
- non-streaming sideband defer에서 `assembler.observation()` 호출 후 저장된 usage observation을 metric에 포함한다.
- `ProviderTunnelFrameKind_USAGE` frame도 계속 지원하되, proto usage의 reasoning/cached_input을 metric에서 버리지 않는다.
- sideband response body/schema는 이번 문제 해결에 꼭 필요한 경우가 아니면 확장하지 않는다. 핵심은 Prometheus metric의 token breakdown이다.

중간 검증:

```bash
$ GOCACHE=/config/workspace/iop/.cache/go-build go test -count=1 ./apps/edge/internal/openai -run 'Sideband|UsageMetrics|Reasoning'
```

## 최종 검증

```bash
$ GOCACHE=/config/workspace/iop/.cache/go-build go test -count=1 ./apps/edge/internal/openai -run 'Sideband|UsageMetrics|ToolValidation|Dispatch|Reasoning|Retry'

$ GOCACHE=/config/workspace/iop/.cache/go-build go test -count=1 ./apps/edge/internal/openai

$ git diff --check
```
