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

# Finalize Non-Streaming Tunnel Body Usage

## For the Implementing Agent

Filling implementation-owned sections in `CODE_REVIEW-cloud-G06.md` is mandatory. Run every verification command, paste actual notes and stdout/stderr, keep the active pair in place, and report ready for review; finalization belongs only to the code-review skill. If blocked, record only the exact blocker, attempted commands/output, and resume condition in implementation-owned evidence fields. Do not ask the user, call user-input tools, create control-plane stop files, classify the next state, archive logs, or write `complete.log`.

## Background

The first implementation separated provider-attempt usage from the request terminal, but runtime-enabled non-streaming tunnels snapshot usage before their buffered JSON body is parsed. Production OpenAI-compatible and vLLM adapters send body-only usage without a separate tunnel `USAGE` frame, so the affected Chat and Responses attempts lose provider attribution. This follow-up fixes that lifecycle ordering and adds a regression over the real frame shape.

## Archive Evidence Snapshot

- Prior task: `agent-task/m-provider-usage-attribution-hot-path/02+01_attempt_usage_emission/`
- Prior plan: `agent-task/m-provider-usage-attribution-hot-path/02+01_attempt_usage_emission/plan_cloud_G10_0.log`
- Prior review: `agent-task/m-provider-usage-attribution-hot-path/02+01_attempt_usage_emission/code_review_cloud_G10_0.log`
- Verdict: FAIL; Required: 1; Suggested: 0; Nit: 0.
- Required finding: `apps/edge/internal/openai/stream_gate_runtime.go:1466` snapshots `providerChatAssembler.usageObservation()` without first invoking the non-streaming JSON parse in `providerChatAssembler.observation()`. Body-only Chat/Responses tunnel attempts therefore emit no provider tokens and leave `usage_source="unavailable"`.
- Affected files: `apps/edge/internal/openai/stream_gate_runtime.go`, `apps/edge/internal/openai/provider_observation.go`, and `apps/edge/internal/openai/usage_attribution_attempt_test.go`.
- Prior focused, package, race, contract, formatting, diff, and provider-capacity smoke commands passed. Fresh reviewer evidence contradicted the claimed production path: a temporary body-only non-streaming tunnel regression failed with `attempt usage = {inputTokens:0 outputTokens:0 reasoningTokens:0 cachedInputTokens:0 providerReported:false reasoningChars:0}, want provider-reported input=9 output=4`; the temporary probe was removed.
- Roadmap carryover: S01/`group-policy`, S02/`dispatch-binding`, and S03/`attempt-usage` remain pending until this follow-up passes. The split predecessor is satisfied by `agent-task/archive/2026/07/m-provider-usage-attribution-hot-path/01_attribution_binding_foundation/complete.log`.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-usage-attribution-hot-path.md`
- Milestone link: [Milestone document](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

## Analysis

### Files Read

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/rules/common/rules-agent-spec.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/edge-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/operational-observability-provider-management/PHASE.md`
- `agent-roadmap/phase/operational-observability-provider-management/milestones/provider-usage-attribution-hot-path.md`
- `agent-roadmap/sdd/operational-observability-provider-management/provider-usage-attribution-hot-path/SDD.md`
- `agent-contract/index.md`
- `agent-contract/outer/openai-compatible-api.md`
- `agent-spec/index.md`
- `agent-spec/input/openai-compatible-surface.md`
- `agent-task/m-provider-usage-attribution-hot-path/02+01_attempt_usage_emission/plan_cloud_G10_0.log`
- `agent-task/m-provider-usage-attribution-hot-path/02+01_attempt_usage_emission/code_review_cloud_G10_0.log`
- `agent-task/archive/2026/07/m-provider-usage-attribution-hot-path/01_attribution_binding_foundation/complete.log`
- `apps/edge/internal/openai/stream_gate_runtime.go`
- `apps/edge/internal/openai/provider_observation.go`
- `apps/edge/internal/openai/stream_gate_dispatcher.go`
- `apps/edge/internal/openai/stream_gate_tunnel_codec.go`
- `apps/edge/internal/openai/usage_metrics.go`
- `apps/edge/internal/openai/usage_attribution_attempt_test.go`
- `apps/edge/internal/openai/stream_gate_dispatcher_test.go`
- `apps/edge/internal/openai/usage_metrics_test.go`
- `apps/node/internal/adapters/openai_compat/provider_tunnel.go`
- `apps/node/internal/adapters/vllm/provider_tunnel.go`

### SDD Criteria

The approved and unlocked SDD is `agent-roadmap/sdd/operational-observability-provider-management/provider-usage-attribution-hot-path/SDD.md`. This follow-up preserves S01/`group-policy` and S02/`dispatch-binding` evidence and repairs S03/`attempt-usage`: the Evidence Map requires a deterministic provider switch, provider-specific token deltas, and exactly one request-terminal increment. The implementation checklist therefore fixes the shared attempt-finalization boundary and changes S03 evidence to use the production body-only non-streaming tunnel shape; final verification reruns the S01-S03 focused tests, full packages, and race suite.

### Verification Context

A code-review handoff was supplied from the prior active plan/review, fresh reviewer commands, the temporary failing probe, the two production adapter sources, and the exact predecessor completion path listed above. On branch `provider-usage-attribution-hot-path` at HEAD `312c8da8950f69ebd31a915d46c2ab1c1ae9670b`, Go is `go1.26.2 linux/arm64` and the module is `/config/workspace/iop-s1/go.mod`; the checkout contains the in-scope implementation plus unrelated user changes that must be preserved. Focused, package, race, formatting, contract, diff, and local smoke checks passed, but the fresh body-only non-streaming probe failed with a zero, non-provider-reported attempt snapshot. Repository-native fallback evidence in both Node tunnel adapters shows only `RESPONSE_START`, `BODY`, and `END` frames, confirming that a `USAGE` frame is not a valid production precondition. Required verification is local and credential-free; the executable workspace `GOTMPDIR` workaround remains necessary because the host `/tmp` mount cannot execute Go test binaries. The remaining gap is deterministic Chat/Responses coverage without an explicit `USAGE` frame. Confidence is high because the failing ordering is localized and directly reproduced.

### Test Coverage Gaps

- The existing S03 provider-switch test supplies an explicit tunnel `USAGE` frame and `stream:true`, so it bypasses the non-streaming body parser used by production body-only adapters.
- No test proves that a successful non-streaming Chat or Responses JSON body is parsed before `CloseAttempt` snapshots provider usage.
- No test proves the same finalization ordering when a body is followed by a provider-error or source-error boundary and the attempt is aborted or replaced.

### Symbol References

No symbol is renamed or removed. `providerChatAssembler.usageObservation()` is called by `openAIStreamGateUsageTrackingTunnelSource.NextEvent`; `providerChatAssembler.observation()` is the existing idempotent non-streaming parser used by response-observation paths. The new finalization helper remains package-private and is called only from the usage-tracking source.

### Split Judgment

The task remains the dependent split subtask `02+01_attempt_usage_emission`. Predecessor index `01` is satisfied by the archived `agent-task/archive/2026/07/m-provider-usage-attribution-hot-path/01_attribution_binding_foundation/complete.log`. The fix is one indivisible ordering invariant: buffered provider JSON must be finalized before the same attempt's usage snapshot is recorded. Splitting production ordering from its regression would permit the defect to recur without independent PASS evidence.

### Scope Rationale

Do not change Node adapter framing, provider routing/admission, retry selection, response wire bytes, metric names or labels, token estimation, Grafana queries, pricing, ledgers, or billing. Do not update the OpenAI contract/spec because their current S01-S03 language is already correct; the implementation must conform to it. Preserve unrelated worktree changes and avoid new files because the existing attribution test file owns S03 evidence.

### Final Routing

- evaluation_mode: `isolated-reassessment`
- finalizer: `finalize-task-policy.sh pair`
- build closures: `scope_closed=true`, `context_closed=true`, `verification_closed=true`, `evidence_trusted=true`, `ownership_closed=true`, `decision_closed=true`
- build scores: `scope_coupling=1`, `state_concurrency=1`, `blast_irreversibility=1`, `evidence_diagnosis=2`, `verification_complexity=1`; grade `G06`; base basis `local-fit`; route basis `recovery-boundary`; route `cloud`; filename `PLAN-cloud-G06.md`
- review closures: `scope_closed=true`, `context_closed=true`, `verification_closed=true`, `evidence_trusted=true`, `ownership_closed=true`, `decision_closed=true`
- review scores: `scope_coupling=1`, `state_concurrency=1`, `blast_irreversibility=1`, `evidence_diagnosis=2`, `verification_complexity=1`; grade `G06`; route basis `official-review`; route `cloud`; adapter `codex`; model `gpt-5.6-sol`; reasoning effort `xhigh`; filename `CODE_REVIEW-cloud-G06.md`
- large_indivisible_context: `false`
- positive loop risks: `temporal_state`, `boundary_contract`, `structured_interpretation`, `variant_product` (count `4`)
- recovery signals: `review_rework_count=1`, `evidence_integrity_failure=true`; recovery boundary matched
- capability-gap evidence: none

## Implementation Checklist

- [ ] Finalize buffered non-streaming tunnel JSON before terminal/error attempt-usage snapshots without changing streaming or response bytes.
- [ ] Replace the explicit-USAGE test gap with deterministic body-only Chat/Responses regression evidence across success and error boundaries.
- [ ] Run fresh focused, package, race, formatting, contract, and diff verification with the executable-workspace Go temporary directory.
- [ ] Fill implementation-owned sections in CODE_REVIEW-*-G??.md with actual implementation notes and verification output.

### [REVIEW_API-1] Finalize Buffered Usage at Attempt Boundaries

#### Problem

`apps/edge/internal/openai/stream_gate_runtime.go:1466` reads `usageObservation()` after every event, but `apps/edge/internal/openai/provider_observation.go:128` buffers all non-streaming bytes and defers JSON parsing until `observation()` at line 196. The attempt controller can therefore close or abort with a zero snapshot even though the complete body contains provider usage.

#### Solution

Add an idempotent assembler finalization method that invokes the existing non-streaming observation parse and then returns the usage snapshot. In `openAIStreamGateUsageTrackingTunnelSource.NextEvent`, use that method before observing the attempt when the source returns a terminal event, provider-error event, or Go error; continue using the non-final snapshot for intermediate events. Preserve the existing response holder update only for a successful terminal and do not mutate provider bytes.

```go
// Before (apps/edge/internal/openai/stream_gate_runtime.go:1466)
func (s *openAIStreamGateUsageTrackingTunnelSource) NextEvent(ctx context.Context) (streamgate.NormalizedEvent, error) {
    ev, err := s.openAITunnelEventSource.NextEvent(ctx)
    if s.assembler != nil {
        obs := s.assembler.usageObservation()
        s.attempt.observe(obs)
```

```go
// After
func (a *providerChatAssembler) finalizeUsageObservation() usageObservation {
    a.observation() // idempotently parses a complete non-streaming body
    return a.usageObservation()
}

func (s *openAIStreamGateUsageTrackingTunnelSource) NextEvent(ctx context.Context) (streamgate.NormalizedEvent, error) {
    ev, err := s.openAITunnelEventSource.NextEvent(ctx)
    final := err != nil || ev.Kind() == streamgate.EventKindTerminal || ev.Kind() == streamgate.EventKindProviderError
    obs := s.assembler.usageObservation()
    if final {
        obs = s.assembler.finalizeUsageObservation()
    }
    s.attempt.observe(obs)
```

#### Modified Files and Checklist

- [ ] `apps/edge/internal/openai/provider_observation.go` — expose an idempotent final usage snapshot that triggers the existing non-streaming parse.
- [ ] `apps/edge/internal/openai/stream_gate_runtime.go` — finalize before terminal/provider-error/source-error observation and keep successful-terminal response-holder semantics.

#### Test Strategy

Regression tests are required and are implemented in REVIEW_API-2. They must fail against the current ordering, cover both endpoint usage envelopes, and prove success plus error finalization without a tunnel `USAGE` frame.

#### Verification

```bash
mkdir -p /tmp/iop-s1-provider-usage-attribution-gocache /config/workspace/iop-s1/.verify-provider-usage-nonstream/gotmp
GOCACHE=/tmp/iop-s1-provider-usage-attribution-gocache GOTMPDIR=/config/workspace/iop-s1/.verify-provider-usage-nonstream/gotmp go test -count=1 ./apps/edge/internal/openai -run 'NonStreamingTunnelAttempt|ProviderSwitchEmitsUsage'
```

Expected: body-only Chat/Responses usage is provider-reported with exact token counts before the attempt is closed or aborted.

### [REVIEW_API-2] Lock Body-Only Chat and Responses Evidence

#### Problem

`apps/edge/internal/openai/usage_attribution_attempt_test.go:24` constructs the replaced tunnel attempt with `stream:true` SSE and a synthetic `PROVIDER_TUNNEL_FRAME_KIND_USAGE` frame at line 26. That frame is absent from the production OpenAI-compatible and vLLM adapters, so the passing test does not execute the defective body parser.

#### Solution

Change `TestProviderSwitchEmitsUsagePerActualAttemptAndOneRequest` so its replaced pool-tunnel attempt is non-streaming and reports Chat usage only in the JSON `BODY`; retain its distinct provider-series deltas and one request-terminal assertion. Add `TestNonStreamingTunnelAttemptFinalizesBodyUsage` as a table over Chat and Responses JSON envelopes, draining the shared usage-tracking event source through physical END and provider-error variants. Assert exact input, output, reasoning, and cached counts in the per-attempt snapshot and successful-terminal holder, with no explicit `USAGE` frame in any fixture.

#### Modified Files and Checklist

- [ ] `apps/edge/internal/openai/usage_attribution_attempt_test.go` — convert S03 to the production body-only non-streaming frame shape and add endpoint/boundary regression cases.

#### Test Strategy

Write and run `TestNonStreamingTunnelAttemptFinalizesBodyUsage` with Chat Completions `prompt_tokens`/`completion_tokens` and Responses `input_tokens`/`output_tokens`, including detail-token fields. Update `TestProviderSwitchEmitsUsagePerActualAttemptAndOneRequest` to prove the body-derived replaced attempt is emitted once through the real pool runtime and that the final request counter still increments once. Re-run both with `-race -count=1`.

#### Verification

```bash
mkdir -p /tmp/iop-s1-provider-usage-attribution-gocache /config/workspace/iop-s1/.verify-provider-usage-nonstream/gotmp
GOCACHE=/tmp/iop-s1-provider-usage-attribution-gocache GOTMPDIR=/config/workspace/iop-s1/.verify-provider-usage-nonstream/gotmp go test -count=1 ./apps/edge/internal/openai -run 'NonStreamingTunnelAttemptFinalizesBodyUsage|ProviderSwitchEmitsUsagePerActualAttemptAndOneRequest'
GOCACHE=/tmp/iop-s1-provider-usage-attribution-gocache GOTMPDIR=/config/workspace/iop-s1/.verify-provider-usage-nonstream/gotmp go test -race -count=1 ./apps/edge/internal/openai -run 'NonStreamingTunnelAttemptFinalizesBodyUsage|ProviderSwitchEmitsUsagePerActualAttemptAndOneRequest'
```

Expected: both endpoint body formats and both lifecycle boundaries retain provider usage, the replaced provider series receives its body-derived counts exactly once, and the race run passes.

## Dependencies and Execution Order

1. Predecessor `01_attribution_binding_foundation` is satisfied by `agent-task/archive/2026/07/m-provider-usage-attribution-hot-path/01_attribution_binding_foundation/complete.log`.
2. Complete REVIEW_API-1 before running the REVIEW_API-2 regression and final verification.

## Modified Files Summary

| File | Item |
|------|------|
| `apps/edge/internal/openai/provider_observation.go` | REVIEW_API-1 |
| `apps/edge/internal/openai/stream_gate_runtime.go` | REVIEW_API-1 |
| `apps/edge/internal/openai/usage_attribution_attempt_test.go` | REVIEW_API-2 |
| `agent-task/m-provider-usage-attribution-hot-path/02+01_attempt_usage_emission/CODE_REVIEW-cloud-G06.md` | REVIEW_API-1, REVIEW_API-2 |

## Final Verification

```bash
mkdir -p /tmp/iop-s1-provider-usage-attribution-gocache /config/workspace/iop-s1/.verify-provider-usage-nonstream/gotmp
test -z "$(gofmt -l apps/edge/internal/openai/provider_observation.go apps/edge/internal/openai/stream_gate_runtime.go apps/edge/internal/openai/usage_attribution_attempt_test.go)"
GOCACHE=/tmp/iop-s1-provider-usage-attribution-gocache GOTMPDIR=/config/workspace/iop-s1/.verify-provider-usage-nonstream/gotmp go test -count=1 ./apps/edge/internal/openai -run 'NonStreamingTunnelAttemptFinalizesBodyUsage|ProviderSwitchEmitsUsagePerActualAttemptAndOneRequest|UsageAttributionPolicy|UsageRecorder|UsageMetricsLabels'
GOCACHE=/tmp/iop-s1-provider-usage-attribution-gocache GOTMPDIR=/config/workspace/iop-s1/.verify-provider-usage-nonstream/gotmp go test -count=1 ./packages/go/config ./apps/edge/internal/service ./apps/edge/internal/openai
GOCACHE=/tmp/iop-s1-provider-usage-attribution-gocache GOTMPDIR=/config/workspace/iop-s1/.verify-provider-usage-nonstream/gotmp go test -race -count=1 ./apps/edge/internal/openai
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
rm -rf /config/workspace/iop-s1/.verify-provider-usage-nonstream
```

Expected: formatting is clean; body-only non-streaming regressions, S01-S03 focused evidence, fresh package tests, and the OpenAI race suite pass; contract/spec pointers still match the implementation; `git diff --check` is clean; the task-specific Go temporary directory is removed. Cached Go test output is not acceptable. After completing all code changes, fill implementation-owned sections in `CODE_REVIEW-*-G??.md`.
