<!-- task=m-provider-usage-attribution-hot-path/02+01_attempt_usage_emission plan=0 tag=API -->

# Code Review Reference - API

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `Implementation Checklist`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked, record the exact blocker, attempted commands/output, and resume condition only in implementation-owned evidence fields.
> Do not ask the user directly, present choices, call user-input tools, create control-plane stop files, or classify the next state.
> Finalization (`Code Review Result`, log rename, `complete.log`, archive moves, `Review-Only Checklist`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.

## Overview

date=2026-07-31
task=m-provider-usage-attribution-hot-path/02+01_attempt_usage_emission, plan=0, tag=API

## Roadmap Targets

- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-usage-attribution-hot-path.md`
- Milestone link: [Milestone 문서](agent-roadmap/phase/operational-observability-provider-management/milestones/provider-usage-attribution-hot-path.md)
- Task ids:
  - `group-policy`: approved model-group rollup policy over one canonical provider series
  - `dispatch-binding`: actual provider, served model, and node binding reaches the emitter
  - `attempt-usage`: retry/fallback usage is emitted once per actual provider attempt
- Completion mode: check-on-pass

## For the Review Agent

> **[REVIEW AGENT ONLY]** The finalization steps below are review-agent only. Implementing agents must not execute this section.

Compare implementation of each item against source files and verify that output in `Verification Results` matches code.
Review completion means the following steps are finished:

1. Append verdict and `review_rework_count` / `evidence_integrity_failure` routing signals.
2. Archive `CODE_REVIEW-cloud-G10.md` → `code_review_cloud_G10_0.log` and `PLAN-cloud-G10.md` → `plan_cloud_G10_0.log`.
3. If PASS, write `complete.log` and move active task directory to `agent-task/archive/YYYY/MM/m-provider-usage-attribution-hot-path/02+01_attempt_usage_emission/`. If WARN/FAIL, fully write the next filesystem state required by the code-review skill.
4. If PASS, report completion event metadata for `group-policy`, `dispatch-binding`, and `attempt-usage` without modifying the roadmap.
5. Check applicable `Review-Only Checklist` items at the final `.log` location before reporting.

---

## Implementation Item Completion

| Item | Status |
|------|---------|
| API-1 Separate request and attempt metric contracts | [x] |
| API-2 Finalize usage at every attempt lifecycle boundary | [x] |
| API-3 Lock S01-S03 evidence and current contract | [x] |

## Implementation Checklist

- [x] Split request-terminal accounting from provider-attributed attempt usage and enforce the approved group policy without duplicate counters.
- [x] Bind and finalize usage for every direct, tunnel, normalized, retry, fallback, success, error, and cancel attempt exactly once.
- [x] Add deterministic S01-S03 tests and synchronize the minimum current OpenAI contract/spec.
- [x] Run fresh focused, full-package, race, local provider-capacity smoke, formatting, and diff verification.
- [x] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

## Review-Only Checklist

> **[REVIEW AGENT ONLY]** This checklist is used only by the review agent.
> Implementing agents must not modify or check this section.

- [x] Append one verdict of `PASS`, `WARN`, or `FAIL` and verified `review_rework_count`, `evidence_integrity_failure` to `Code Review Result`.
- [x] Verify that verdict, `Dimension Assessment`, and Required/Suggested/Nit classifications match.
- [x] Archive active `CODE_REVIEW-cloud-G10.md` to `code_review_cloud_G10_0.log`.
- [x] Archive active `PLAN-cloud-G10.md` to `plan_cloud_G10_0.log`.
- [x] Verify that the Agent-Ops managed block in `.gitignore` unignores `agent-task/**/*.md` and `agent-task/**/*.log` and ignores `agent-roadmap/current.md`.
- [ ] If PASS, write `complete.log` based on `agent-ops/skills/common/code-review/templates/complete-log-template.md` and leave no active `.md` files.
- [ ] If PASS, move active task directory `agent-task/m-provider-usage-attribution-hot-path/02+01_attempt_usage_emission/` to `agent-task/archive/YYYY/MM/m-provider-usage-attribution-hot-path/02+01_attempt_usage_emission/` and update this checklist at the final archive path.
- [ ] If PASS, report milestone completion event metadata for the listed Task ids without editing roadmap or directly calling `update-roadmap`.
- [ ] If PASS for split work, remove the active parent if no sibling remains, or verify why it stays.
- [x] If WARN/FAIL, write the next filesystem state matching code-review verdict and do not write `complete.log`.

## Deviations from Plan

- `normalized_sse.go` now finishes the request terminal when the response writer cannot flush. This call site was identified by the plan's complete terminal-path requirement but was omitted from its modified-file table.
- `provider_test_support_test.go` now preserves the selected provider id and served target on provider-pool tunnel handles. The strict binding intentionally rejects the old incomplete test double, so the fixture had to mirror the production dispatch contract.
- `scripts/e2e-provider-capacity-smoke.sh` now supplies the required `openai.provider_id` in its generated Edge config. The predecessor validation contract made the former fixture invalid before the smoke could start.
- The host's inherited Go cache was unreadable and its default Go build temporary directory could not execute binaries. Verification therefore used the planned fresh `GOCACHE` plus a request-local `GOTMPDIR` under the workspace. The smoke script hard-codes its runtime directory under `/tmp`, so its `mktemp` call was overridden only in the verification shell to create that temporary directory under the executable workspace; the script behavior and assertions were unchanged.
- The first smoke attempt failed on the inherited cache (`permission denied`). A fresh-cache attempt then exposed `/tmp` as non-executable, and the first executable-workspace attempt correctly exposed the now-invalid missing `openai.provider_id` fixture. After the fixture update, the same smoke passed.

## Key Design Decisions

- One `openAIUsageRecorder` is created after ingress identity/route resolution. It owns immutable caller/route labels, per-attempt deduplication, provider-usage presence, and an exactly-once request terminal.
- `RecordAttempt` always uses strict actual `provider_id` and `served_model`; adapter and node identifiers are never fallback identities. `node_id` remains in the internal binding and is asserted in tests, but is absent from every public label vector.
- `usage_attribution="model_group"` remains a policy label on the single actual-provider series. Model-group reporting is a query-time aggregation by `route_model`, not a duplicate counter.
- Provider-report presence is tracked separately from numeric counts, so an observed zero-valued provider usage payload is still provider-reported while reasoning characters alone are not.
- Runtime event sources update a per-attempt observation. The idempotent attempt controller flushes it before either graceful close or abort, preserving rejected/replaced attempts. The existing final-attempt holder remains only for response rendering.
- Legacy tool-validation paths record the completed rejected attempt before dispatching a replacement. Direct normalized, live SSE, buffered SSE, Responses, and tunnel paths all separate attempt recording from request finishing.
- The request terminal is finished only after runtime resources have finalized their attempts and uses the final committed response mode. Duplicate terminal calls are ignored.

## Reviewer Checkpoints

- Predecessor `01_attribution_binding_foundation` is satisfied by the active `complete.log` or an exact archived completion path supplied by the task-loop dependency resolver, without broad archive search.
- Canonical token/reasoning usage begins at actual provider id and served model; approved groups use only query-time rollup.
- Provider-reported usage from every observed retry/fallback attempt is finalized once, including replaced or aborted attempts; unobserved attempts emit no usage.
- Each attempt uses its own actual execution/response mode, while the HTTP terminal counter increments once at the final committed mode across success, error, cancel, dispatch failure, and provider switch.
- Node identity reaches the immutable recorder binding but is not a public metric label; attempt/run/session identity remains internal deduplication state.
- No adapter fallback, duplicate group counter, secret/high-cardinality label, routing policy, Grafana query, ledger, or billing change is present, and reasoning characters alone do not alter existing `usage_source` semantics.
- SDD Evidence Map S01-S03 has named deterministic tests and actual output.

## Verification Results

### Request and attempt metric contract

```bash
mkdir -p /tmp/iop-s1-provider-usage-attribution-gocache
GOCACHE=/tmp/iop-s1-provider-usage-attribution-gocache go test -count=1 ./apps/edge/internal/openai -run 'UsageAttributionPolicy|UsageRecorder|UsageMetricsLabels'
```

Output (exit 0; `GOTMPDIR` added as documented above):

```text
ok  	iop/apps/edge/internal/openai	0.059s
```

### Attempt lifecycle

```bash
GOCACHE=/tmp/iop-s1-provider-usage-attribution-gocache go test -count=1 ./apps/edge/internal/openai -run 'ProviderSwitchEmitsUsage|Attempt.*Usage|ActualOpenAIProvider'
GOCACHE=/tmp/iop-s1-provider-usage-attribution-gocache go test -race -count=1 ./apps/edge/internal/openai -run 'ProviderSwitchEmitsUsage|Attempt.*Usage'
```

Output (both exit 0; the second line is the race run):

```text
ok  	iop/apps/edge/internal/openai	0.041s
ok  	iop/apps/edge/internal/openai	1.067s
```

### S01-S03 contract and evidence

```bash
rg --sort path -n 'route_model|usage_attribution|provider_id|served_model|terminal counter' agent-contract/outer/openai-compatible-api.md agent-spec/input/openai-compatible-surface.md
GOCACHE=/tmp/iop-s1-provider-usage-attribution-gocache go test -count=1 ./apps/edge/internal/openai -run 'UsageAttribution|ProviderSwitch|UsageMetrics'
```

Output (exit 0):

```text
agent-contract/outer/openai-compatible-api.md:104:- `iop_openai_requests_total` is emitted exactly once for each OpenAI-compatible request terminal. Its route dimension is `route_model`; `response_mode`, `status`, and `usage_source` describe the final committed HTTP result.
agent-contract/outer/openai-compatible-api.md:106:- Canonical provider-attempt dimensions are `usage_attribution`, strict actual `provider_id`, actual `served_model`, `route_model`, `endpoint`, and the attempt response mode. A missing strict provider/model binding does not fall back to adapter or node identity and does not create a provider usage series.
agent-contract/outer/openai-compatible-api.md:107:- `usage_attribution="model_group"` is an explicit query-time rollup policy. It does not duplicate token counters or replace the canonical actual-provider series; operators roll up those series by `route_model` when the policy requests model-group attribution.
agent-contract/outer/openai-compatible-api.md:186:- Responses provider passthrough usage uses `endpoint="responses"`, the caller route alias in `route_model`, and the selected actual provider/served model on each attempt. Observation data is never inserted into the provider body.
agent-spec/input/openai-compatible-surface.md:91:| attribution route binding | provider-pool model은 `models[].usage_attribution`의 effective policy와 선택된 actual provider를 보존한다. direct route는 route-level `provider_id`를 top-level fallback보다 우선하며 adapter/node text를 provider identity로 대체하지 않는다. |
agent-spec/input/openai-compatible-surface.md:109:| Responses provider passthrough | provider-pool model group route와 direct OpenAI-compatible provider route의 `/v1/responses`는 provider raw tunnel을 사용한다. Edge는 `model`만 served target으로 rewrite하고 unknown/Codex field와 `stream:true` raw SSE를 provider로 relay한다. Usage is recorded with endpoint=`responses`, response_mode=`passthrough`, route_model=request alias, and the selected actual provider/served model. |
agent-spec/input/openai-compatible-surface.md:158:- provider-pool model의 `usage_attribution`은 생략 시 `provider`이고 `model_group`은 명시적 opt-in이다. direct dispatch는 `openai.model_routes[].provider_id`를 우선하고 없으면 `openai.provider_id`를 사용한다.
agent-spec/input/openai-compatible-surface.md:159:- normalized run과 provider tunnel의 성공 dispatch는 actual `provider_id`, served target, resolved node id, effective attribution policy를 Edge-local result에 보존한다. strict attempt binding은 `provider_id`만 actual provider로 인정하고 adapter 또는 node id로 대체하지 않는다.
agent-spec/input/openai-compatible-surface.md:161:- provider capacity와 long-context slot은 model alias별이 아니라 `node_id + provider_id`별로 공유한다. queue pending 상한과 timeout은 Edge root `provider_pool` policy이며, lease 반환·refresh·disconnect/reconnect가 모든 model group waiter를 global enqueue 순서로 재평가한다.
agent-spec/input/openai-compatible-surface.md:170:- The request terminal uses `route_model`, `endpoint`, final `response_mode`, `status`, and `usage_source` with the stable caller labels. Provider token/reasoning series additionally use `usage_attribution`, strict actual `provider_id`, and actual `served_model` for each attempt.
agent-spec/input/openai-compatible-surface.md:172:- `usage_attribution="model_group"` is a query-time rollup instruction over canonical provider series grouped by `route_model`; it does not emit a duplicate model-group token counter.
ok  	iop/apps/edge/internal/openai	0.037s
```

### Final verification

```bash
mkdir -p /tmp/iop-s1-provider-usage-attribution-gocache
test -z "$(gofmt -l apps/edge/internal/openai/usage_metrics.go apps/edge/internal/openai/dispatch_context.go apps/edge/internal/openai/stream_gate_dispatcher.go apps/edge/internal/openai/stream_gate_runtime.go apps/edge/internal/openai/responses_stream_gate.go apps/edge/internal/openai/chat_handler.go apps/edge/internal/openai/chat_completion.go apps/edge/internal/openai/buffered_sse.go apps/edge/internal/openai/chat_stream_session.go apps/edge/internal/openai/provider_tunnel.go apps/edge/internal/openai/responses_handler.go apps/edge/internal/openai/responses_completion.go apps/edge/internal/openai/provider_observation.go apps/edge/internal/openai/usage_metrics_test.go apps/edge/internal/openai/usage_attribution_attempt_test.go apps/edge/internal/openai/stream_gate_dispatcher_test.go)"
GOCACHE=/tmp/iop-s1-provider-usage-attribution-gocache go test -count=1 ./packages/go/config ./apps/edge/internal/service ./apps/edge/internal/openai
GOCACHE=/tmp/iop-s1-provider-usage-attribution-gocache go test -race -count=1 ./apps/edge/internal/openai
./scripts/e2e-provider-capacity-smoke.sh
rg --sort path -n 'route_model|usage_attribution|provider_id|served_model|terminal counter' agent-contract/outer/openai-compatible-api.md agent-spec/input/openai-compatible-surface.md
git diff --check
```

Output (all final commands exit 0; formatting and `git diff --check` produced no stdout):

```text
ok  	iop/packages/go/config	0.522s
ok  	iop/apps/edge/internal/service	5.899s
ok  	iop/apps/edge/internal/openai	7.422s
ok  	iop/apps/edge/internal/openai	8.840s
[provider-capacity-smoke] building loopback binaries
[provider-capacity-smoke] metrics=disabled bounded_probe_timeout_sec=2
[provider-capacity-smoke] offline_snapshot_rejected=true
[provider-capacity-smoke] aliases=ornith:35b,ornith-fast queue_observed=true
[provider-capacity-smoke] backend={"calls":2,"active":0,"peak":1}
[provider-capacity-smoke] final_provider=[{"node_id":"ornith-node","connected":true,"providers":[{"id":"ornith-provider","status":"available","health":"available","capacity":1,"in_flight":0,"queued":0,"long_context_capacity":1,"long_in_flight":0,"long_queued":0}]}]
[provider-capacity-smoke] PASS evidence=/config/workspace/iop-s1/.provider-capacity-smoke-BKibPu
```

The final contract grep output is recorded in the preceding section and was repeated successfully. The smoke evidence directory was request-local and removed by the script's normal cleanup.

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

## Section Ownership

| Section | Owner | Note |
|---------|-------|------|
| Header comment, Overview, Review Agent Instructions | Fixed at stub creation | Implementing agent must not modify or execute these |
| Roadmap Targets | Fixed at stub creation from plan | Implementing agent must not modify; code-review copies it into `complete.log` only on PASS |
| Implementation Item Completion (item names) | Fixed at stub creation | Implementing agent checks `[ ]` → `[x]` only |
| Implementation Checklist (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` → `[x]` only |
| Review-Only Checklist | Review agent only | Implementing agent must not modify or check |
| Deviations from Plan, Key Design Decisions | Implementing agent | Replace placeholders with actual content |
| Reviewer Checkpoints | Fixed at stub creation | Reviewer verifies them |
| Verification Results | Implementing agent | Paste actual output; command changes require a deviation |
| Code Review Result | Review agent appends | Not included in the stub |

## Code Review Result

- Overall Verdict: FAIL
- Dimension Assessment:
  - Correctness: Fail
  - Completeness: Fail
  - Test coverage: Fail
  - API contract: Fail
  - Code quality: Pass
  - Implementation deviation: Pass
  - Verification trust: Fail
  - Spec conformance: Fail
- Findings:
  - Required — `apps/edge/internal/openai/stream_gate_runtime.go:1466`: A runtime-enabled non-streaming provider tunnel never parses its buffered response body before the attempt controller snapshots usage. `openAIStreamGateUsageTrackingTunnelSource.NextEvent` calls only `assembler.usageObservation()`, while `providerChatAssembler.Write` buffers non-streaming bytes and defers JSON usage parsing to `providerChatAssembler.observation()`. The production OpenAI-compatible and vLLM tunnel adapters send `RESPONSE_START`, `BODY`, and `END` without a separate `USAGE` frame, so direct and pool Chat/Responses attempts on this path emit no provider token series and finish the request with `usage_source="unavailable"`. This contradicts API-2, SDD scenario S03, and the current outer contract. Finalize/parse the non-streaming assembler before observing the attempt at terminal and error boundaries, then add deterministic body-only non-streaming runtime tests for the shared Chat/Responses tunnel behavior without relying on an explicit `USAGE` frame.
- Routing Signals:
  - `review_rework_count=1`
  - `evidence_integrity_failure=true`
- Next Step: Prepare and route a follow-up plan for the Required finding.
