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

# Plan - REVIEW_USAGE_METRIC

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

이 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_cloud_G07_0.log`
- Current archived review: `agent-task/m-usage-token-log-ops-mvp/02+01_usage_metrics/code_review_cloud_G07_0.log`
- Verdict: FAIL
- Required summary:
  - `apps/edge/internal/openai/stream.go:963`, `apps/edge/internal/openai/stream.go:1099`: `passthrough+sideband` stream/non-stream writers terminate without `emitUsageMetrics`, so sideband OpenAI-compatible requests are omitted from request/usage counters.
  - `apps/edge/internal/openai/chat_handler.go:488`, `apps/edge/internal/openai/stream.go:235`, `apps/edge/internal/openai/chat_handler.go:144`, `apps/edge/internal/openai/responses_handler.go:129`, `apps/edge/internal/openai/stream.go:392`: tool-validation and dispatch terminal error paths write OpenAI-compatible errors without request metric emission.
- Affected files:
  - `apps/edge/internal/openai/chat_handler.go`
  - `apps/edge/internal/openai/responses_handler.go`
  - `apps/edge/internal/openai/stream.go`
  - `apps/edge/internal/openai/usage_metrics.go`
  - `apps/edge/internal/openai/usage_metrics_test.go`
  - `apps/edge/internal/openai/server_test.go`
- Verification evidence from failed loop:
  - `make proto` PASS.
  - `GOCACHE=/config/workspace/iop/.cache/go-build go test -count=1 ./apps/edge/internal/openai ./apps/node/internal/node ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm` PASS.
  - `GOCACHE=/config/workspace/iop/.cache/go-build go test -count=1 ./apps/edge/... ./apps/node/...` PASS.
  - `git diff --check` PASS.
- Roadmap carryover: same Roadmap Targets remain claimed only after this follow-up closes the missing S04/S06/S08 metric coverage.
- Narrow reread allowed if needed: only the two archived files listed above.

## 범위 결정 근거

후속 범위는 metric terminal coverage 누락 수리에 한정한다. Proto schema, Node adapter usage parsing, principal token auth/config, pure passthrough body invariance는 이전 루프에서 구현되어 대상 패키지와 넓은 Go 테스트가 통과했다. 이번 plan은 OpenAI-compatible handler 내부의 request/usage counter emission과 regression tests만 다룬다.

## 구현 체크리스트

- [x] `passthrough+sideband` streaming/non-streaming provider tunnel writers가 success/error/cancel/timeout terminal status마다 정확히 한 번 `emitUsageMetrics`를 호출하도록 수정한다.
- [x] sideband body usage와 `PROVIDER_TUNNEL_FRAME_KIND_USAGE` usage가 `usageObservation`에 반영되고, reasoning chars는 token estimate 없이 보조 metric에만 반영되도록 보존한다.
- [x] normalized Chat/Responses/provider-tunnel dispatch failure와 tool-validation failure 경로가 `iop_openai_requests_total` error/cancel status를 정확히 한 번 emit하도록 수정한다.
- [x] sideband success/error와 validation/dispatch failure metric regression tests를 추가하거나 기존 tests를 확장한다.
- [x] 최종 검증 명령을 실행한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_USAGE_METRIC-1] Sideband Metric Terminal Coverage

문제: `writeProviderTunnelSidebandStream`와 `writeProviderTunnelSidebandResponse`가 sideband response를 생성하지만 `emitUsageMetrics`를 호출하지 않는다. `passthrough+sideband`는 OpenAI-compatible Chat Completions 경로이므로 MVP metric scope에서 빠지면 안 된다.

해결 방법:
- `responseModePassthroughSideband` label을 사용하는 metric labels를 sideband writer에 만든다.
- pure passthrough writer처럼 `metricStatus`를 추적하고 defer 또는 단일 terminal helper로 request counter를 한 번만 emit한다.
- sideband stream/response에서 provider body usage와 `PROVIDER_TUNNEL_FRAME_KIND_USAGE` frame usage를 `usageObservation`에 기록한다.
- provider body 자체와 sideband response schema를 불필요하게 바꾸지 않는다.

테스트 작성:
- sideband streaming success가 `iop_openai_requests_total{response_mode="passthrough+sideband",status="success",usage_source="provider_reported"}`와 token type counters를 증가시키는지 검증한다.
- sideband non-streaming success도 같은 metric delta를 검증한다.
- sideband timeout/error/caller-cancel 중 최소 하나가 error/cancel request counter를 증가시키는지 검증한다.

중간 검증:

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

### [REVIEW_USAGE_METRIC-2] Error Terminal Metric Coverage

문제: tool validation failure와 dispatch failure가 OpenAI-compatible error response로 끝나지만 request counter를 증가시키지 않는다. MVP request counter는 success뿐 아니라 terminal status별 count를 제공해야 한다.

해결 방법:
- normalized Chat non-stream, Chat stream, Responses, provider tunnel submit 단계에서 post-auth terminal error가 발생하면 `usageStatusError` 또는 `usageStatusForError(err)`로 request metric을 emit한다.
- retry 가능한 tool-validation attempt는 최종 terminal result만 request metric을 emit하도록 유지한다.
- 이미 metrics를 emit하는 success/error paths와 중복 증가하지 않게 helper 또는 명확한 ownership을 둔다.

테스트 작성:
- non-stream tool validation retry limit failure가 `status="error",usage_source="unavailable"` request counter를 1 증가시키는지 검증한다.
- streaming text-tool validation failure가 request counter를 1 증가시키는지 검증한다.
- `SubmitRun` 또는 `SubmitProviderTunnel` dispatch failure가 request counter를 1 증가시키는지 검증한다.

중간 검증:

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

## 최종 검증

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

$ GOCACHE=/config/workspace/iop/.cache/go-build go test -count=1 ./apps/edge/internal/openai ./apps/node/internal/node ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm
ok  	iop/apps/edge/internal/openai	1.805s
ok  	iop/apps/node/internal/node	0.807s
ok  	iop/apps/node/internal/adapters/openai_compat	0.124s
ok  	iop/apps/node/internal/adapters/vllm	0.124s

$ GOCACHE=/config/workspace/iop/.cache/go-build go test -count=1 ./apps/edge/... ./apps/node/...
ok  	iop/apps/edge/cmd/edge	0.072s
ok  	iop/apps/edge/internal/bootstrap	0.277s
ok  	iop/apps/edge/internal/configrefresh	0.019s
ok  	iop/apps/edge/internal/controlplane	4.452s
ok  	iop/apps/edge/internal/edgecmd	0.013s
ok  	iop/apps/edge/internal/edgevalidate	0.004s
ok  	iop/apps/edge/internal/events	0.003s
ok  	iop/apps/edge/internal/input	0.006s
ok  	iop/apps/edge/internal/input/a2a	0.005s
ok  	iop/apps/edge/internal/node	0.007s
ok  	iop/apps/edge/internal/openai	1.817s
ok  	iop/apps/edge/internal/opsconsole	0.007s
ok  	iop/apps/edge/internal/service	0.973s
ok  	iop/apps/edge/internal/transport	2.039s
ok  	iop/apps/node/cmd/node	0.010s
ok  	iop/apps/node/internal/adapters	0.010s
ok  	iop/apps/node/internal/adapters/cli	46.978s
?   	iop/apps/node/internal/adapters/cli/internal/testutil	[no test files]
ok  	iop/apps/node/internal/adapters/cli/status	39.701s
?   	iop/apps/node/internal/adapters/mock	[no test files]
ok  	iop/apps/node/internal/adapters/ollama	0.008s
ok  	iop/apps/node/internal/adapters/openai_compat	0.123s
ok  	iop/apps/node/internal/adapters/vllm	0.123s
ok  	iop/apps/node/internal/bootstrap	0.433s
ok  	iop/apps/node/internal/node	0.810s
ok  	iop/apps/node/internal/router	0.504s
?   	iop/apps/node/internal/runtime	[no test files]
ok  	iop/apps/node/internal/store	0.033s
ok  	iop/apps/node/internal/terminal	0.543s
ok  	iop/apps/node/internal/transport	5.435s

$ git diff --check
(no output - no whitespace errors)
```

`proto/iop/runtime.proto`를 다시 수정한 경우에만 `make proto`도 실행하고 결과를 review stub에 남긴다.