# OpenAI-compatible Usage – Grafana Query Guide

> **Purpose**: 이 문서는 운영자가 Prometheus metric을 Grafana 패널로 조회해 사용자별 OpenAI-compatible token 사용량, 사용 출처, cloud-equivalent cost, avoided-cost ROI 판단 기준을 볼 수 있도록 한다. Control Plane/Client dashboard, request-level ledger, live cloud pricing sync, billing/chargeback, 사용자별 제한 enforcement는 만들지 않는다.

---

## Metric 목록

### 1. `iop_openai_usage_tokens_total` (Counter)

Provider-reported OpenAI-compatible token usage by token type.

| Label | Value 예시 | 설명 |
|-------|-----------|------|
| `edge_id` | `edge-1` | Edge instance identity |
| `principal_ref` | `usr-abc123` | Principal foreign-key-like reference |
| `principal_alias` | `john@acme` | Human-readable alias |
| `token_ref` | `tok-xyz789` | Token identity (not a raw secret) |
| `credential_slot_ref` | `slot-openai-primary` | Stable managed provider-credential slot reference from the verified lease scope; not a secret or slot alias |
| `credential_revision` | `42` | Immutable credential revision used by this provider attempt |
| `route_model` | `gpt-4o` | Caller가 요청한 route alias; trace 및 승인된 model-group rollup key |
| `usage_attribution` | `provider`, `model_group` | 기본 provider 집계 또는 명시 승인된 model-group query-time rollup 정책 |
| `provider_id` | `provider-a` | 실제 호출된 provider resource identity |
| `served_model` | `gpt-4o-2024-08-06` | 해당 provider가 실제 호출한 model target |
| `endpoint` | `chat.completions`, `responses` | OpenAI-compatible route |
| `response_mode` | `passthrough`, `normalized` | Internal execution label; callers cannot set it via OpenAI metadata |
| `token_type` | `input`, `output`, `reasoning`, `cached_input` | Token type |

### 2. `iop_openai_requests_total` (Counter)

OpenAI-compatible requests processed by terminal status and usage source.

| Label | Value 예시 | 설명 |
|-------|-----------|------|
| `edge_id` | `edge-1` | Edge instance identity |
| `principal_ref` | `usr-abc123` | Principal reference |
| `principal_alias` | `john@acme` | Human-readable alias |
| `token_ref` | `tok-xyz789` | Token identity |
| `route_model` | `gpt-4o` | Caller가 요청한 route alias; request terminal의 route dimension |
| `endpoint` | `chat.completions`, `responses` | OpenAI-compatible route |
| `response_mode` | `passthrough`, `normalized` | Internal execution label; callers cannot set it via OpenAI metadata |
| `status` | `success`, `error`, `cancel` | Terminal request status |
| `usage_source` | `provider_reported`, `unavailable` | Whether at least one token type was reported |

### 3. `iop_openai_reasoning_observed_total` (Counter)

Requests where reasoning text was observed but the provider did not report reasoning token usage.
Token metric과 같은 실제 provider-attempt labels를 사용하되 `token_type`은 없다: `edge_id`, `principal_ref`, `principal_alias`, `token_ref`, `credential_slot_ref`, `credential_revision`, `route_model`, `usage_attribution`, `provider_id`, `served_model`, `endpoint`, `response_mode`.

### 4. `iop_openai_reasoning_chars_total` (Counter)

Observed reasoning text characters for requests without provider-reported reasoning tokens. The token estimate is emitted separately via `iop_openai_reasoning_estimated_tokens_total`; provider-reported `token_type="reasoning"` remains authoritative.

### 5. `iop_openai_reasoning_estimated_tokens_total` (Counter)

Estimated reasoning tokens for requests with observed reasoning text but without provider-reported reasoning token usage. Calculated as `ceil(chars / 4)` with `estimation_method="chars_div_4"`. This is an operator-grade observation estimate only, never billing-grade or chargeback-confirmed.

---

## Label Allowlist & Forbidden Labels

### 허용 label

```
edge_id, principal_ref, principal_alias, token_ref,
credential_slot_ref, credential_revision,
route_model, usage_attribution, provider_id, served_model,
endpoint, response_mode, status, token_type,
usage_source, estimation_method
```

`credential_slot_ref` and `credential_revision` are provider-attempt dimensions on token and reasoning metrics only. They are intentionally absent from `iop_openai_requests_total`, which remains a single request-terminal counter. `estimation_method`는 `iop_openai_reasoning_estimated_tokens_total` 전용 보조 라벨이며, 그 외 counter에는 사용되지 않습니다.

### 금지 label

- `request_id`, `session_id` — request-level 상세는 후속 ledger/Loki 축에서 다룬다.
- `run_id`, `attempt_id`, `node_id` — 실행 시도 evidence는 request-local로 유지하고 public metric label로 노출하지 않는다.
- Bearer token, provider API key, raw payload, raw prompt/response text — secret 누출 방지 (SDD S08).
- `credential_slot_alias`, raw credential material, lease ID, recipient key, and target URL — managed credential internals and secrets must never become public metric labels.
- 그 외 임의 label — cardinality 폭발을 막는다.

---

## Principal / Token / Provider Attribution 전제

- `principal_ref`는 사용자/테넌트/외부 운영 시스템의 안정 참조값이다.
- `principal_alias`는 Grafana legend와 table에 보여줄 낮은 cardinality 별칭이다.
- `token_ref`는 raw bearer token이 아니라 앱/통합/용도별 token 참조값이다.
- `token_ref` identifies the inbound IOP authentication token. It is distinct from `credential_slot_ref`, which identifies the managed provider credential selected for an outbound attempt; neither field may contain raw credential material.
- `credential_slot_ref` and `credential_revision` are copied from the verified lease scope into the immutable dispatch snapshot. Retries for the same bound credential retain the same pair, while attempts using different slots or revisions remain separate series.
- 같은 `principal_ref` 아래 여러 `token_ref`를 둘 수 있다. 이 경우 `principal_ref` 기준 query는 사용자 합산, `token_ref` 기준 query는 앱/통합별 breakdown으로 본다.
- 운영 token 발급은 raw token을 tracked 파일에 남기지 않고 hash/reference만 기록하는 절차를 따른다.
- canonical token/reasoning series는 실제 호출 시도의 `provider_id`와 `served_model`에 귀속한다. `route_model`은 요청 alias이며 기본 provider 집계 key가 아니다.
- `usage_attribution="model_group"`은 운영자가 동일 논리 모델임을 명시 승인한 route에서만 사용한다. 이 series만 `route_model`로 query-time rollup하며 별도 model-group counter는 없다.
- retry/fallback으로 provider가 바뀌면 각 실제 호출 시도가 별도 provider series로 남는다. `iop_openai_requests_total`은 최종 request terminal을 한 번만 기록하므로 provider 시도 횟수로 사용하지 않는다.

관련 기준:

- `agent-contract/outer/openai-compatible-api.md`: OpenAI-compatible principal token auth 계약
- `agent-contract/inner/edge-config-runtime-refresh.md`: `openai.principal_tokens[]` config 계약
- `agent-ops/skills/project/openai-usage-token-issue/SKILL.md`: 운영 token 발급 절차
- `apps/edge/internal/openai/identity_metering_test.go`: 같은 principal의 여러 token resolution 테스트
- `packages/go/config/config_test.go`: 같은 principal의 여러 `principal_tokens[]` config load 테스트

### Managed slot lifecycle interpretation

- A managed attempt may be counted only after an exact projected route binding produced a verified lease scope. Non-empty `credential_slot_ref` with revision `N` means that attempt used revision `N`; it does not prove the slot remains active now.
- After rotation, new successful attempts must move to the new revision. Historical samples for the old revision remain valid history and must not be deleted or relabeled.
- After disable or revoke, the bound route must fail closed. The expected confirmation is no new token/reasoning sample for that slot revision and no Node/upstream attempt, not removal of existing Prometheus samples.
- Lease expiry is enforced at consumption time. Waiting past TTL must make an old envelope unusable; a later successful request acquires a fresh lease but may retain the same slot revision when the credential itself was not rotated.
- Request-terminal counters intentionally omit slot/revision because one request can own multiple provider attempts. Correlate terminal health with canonical provider-attempt series; never infer a credential from `token_ref`, `route_model`, provider id, or Node identity.

Managed API-key interpretation: managed API-key profiles qualify because the Control Plane canonicalizes the resolved auth header (lowercase `x-api-key` to `X-Api-Key`) before signing the lease scope. Deterministic qualification proves that both managed routes — the OpenAI-compatible Chat profile and the Anthropic Messages profile — complete end to end with exact auth and safe credential-slot attribution recorded in the qualification record. That record is a route-completion proof, not a Prometheus expectation: OpenAI canonical Prometheus slot/revision series are emitted only for OpenAI attempts, because Anthropic handlers do not record the OpenAI canonical usage series and ignore native `USAGE` tunnel frames. An Anthropic Messages slot series is therefore not expected until separate Anthropic metering is implemented, and its absence is not an anomaly. The deterministic OpenAI packet is one initial Chat call plus one rotated Chat call on the same managed slot, producing successive `credential_slot_ref`/`credential_revision` samples; it is not evidence of an Anthropic Messages series. A lease failure must fail closed with no Node/upstream attempt rather than fall back to a bearer slot or caller auth.

---

## PromQL Examples

### 기본: 사용자별 사용량 (principal_alias 기준)

```promql
# 입력 토큰 합계
sum by (principal_alias) (iop_openai_usage_tokens_total{token_type="input"})

# 출력 토큰 합계
sum by (principal_alias) (iop_openai_usage_tokens_total{token_type="output"})
```

### principal_ref별 usage breakdown

```promql
# principal_ref, token_type별 aggregation
sum by (principal_ref, token_type) (iop_openai_usage_tokens_total)
```

### token_ref별 사용량

```promql
# token_ref별 input/output 합계
sum by (token_ref, token_type) (iop_openai_usage_tokens_total)
```

### 사용자 합산 + 앱/통합별 breakdown

```promql
# 같은 principal_ref의 전체 사용량
sum by (principal_ref, principal_alias, token_type) (
  iop_openai_usage_tokens_total
)

# 같은 principal_ref 안에서 token_ref별 사용량
sum by (principal_ref, principal_alias, token_ref, token_type) (
  iop_openai_usage_tokens_total
)
```

### 실제 provider별 usage

```promql
# 실제 provider와 served model, token_type별 aggregation
sum by (provider_id, served_model, token_type) (
  iop_openai_usage_tokens_total
)
```

### Managed credential-slot usage

Use the safe slot reference and immutable revision to compare outbound provider-credential usage. Do not join or replace this dimension with inbound `token_ref`.

```promql
# Actual token usage by managed provider credential slot and revision
sum by (credential_slot_ref, credential_revision, provider_id, served_model, token_type) (
  iop_openai_usage_tokens_total{credential_slot_ref!=""}
)
```

### 승인된 model-group rollup

`route_model` 집계는 `usage_attribution="model_group"`으로 명시 승인된 series에만 적용한다. 기본값인 `provider` series를 route alias로 합치지 않는다.

```promql
# 동일 논리 모델로 승인된 route만 query-time rollup
sum by (route_model, token_type) (
  iop_openai_usage_tokens_total{usage_attribution="model_group"}
)
```

### endpoint별 요청 수

```promql
# endpoint, status별 요청 수
sum by (endpoint, status) (iop_openai_requests_total)
```

### response_mode별 분석

`response_mode`는 handler 실행 경로에서 파생되는 내부 라벨이다. provider 라우트의 raw tunnel 패스스루는 `passthrough`, 정규화된 RunEvent 경로는 `normalized`로 기록된다. caller가 OpenAI metadata로 설정할 수 있는 값이 아니며 API selector가 아니다.

```promql
# response_mode별 성공 요청 수
sum by (response_mode, status) (iop_openai_requests_total{status="success"})
```

### usage origin breakdown

```promql
# "어디서 얼만큼 사용했는지"를 보는 table용 breakdown
sum by (
  principal_alias, token_ref, provider_id, served_model,
  route_model, usage_attribution, endpoint, response_mode, token_type
) (
  iop_openai_usage_tokens_total
)
```

### token_type별 사용량 전체

```promql
# 모든 token_type별 합계 (time range 그래프용)
sum by (token_type) (irate(iop_openai_usage_tokens_total[5m]))
```

### usage_source별 요청 coverage

```promql
# provider_reported vs unavailable 비율
sum by (usage_source) (iop_openai_requests_total)
```

### reasoning 보조 metric

provider가 reasoning token을 보고하지 않는 경우:

```promql
# reasoning text가 관찰된 요청 수 (provider token 미보고)
sum by (principal_alias, provider_id, served_model) (
  iop_openai_reasoning_observed_total
)

# reasoning text character 합계
sum by (principal_alias, provider_id, served_model) (
  iop_openai_reasoning_chars_total
)

# estimated reasoning tokens (provider가 token 수를 보고하지 않는 경우)
sum by (principal_alias, provider_id, served_model, estimation_method) (
  iop_openai_reasoning_estimated_tokens_total
)
```

> **주의**: `iop_openai_reasoning_observed_total` / `iop_openai_reasoning_chars_total` / `iop_openai_reasoning_estimated_tokens_total`는 provider-reported reasoning token이 있을 경우 emit되지 않는다. 즉, 이 metric들은 "reasoning text는 있으나 token count 보고를 안 하는 provider"만 커버한다. `iop_openai_reasoning_estimated_tokens_total`은 `estimation_method="chars_div_4"`로 ceil(chars/4) 추정을 제공하지만 billing-grade 정산값이 아니다.

---

## Dashboard Panel 후보

| 패널 | query | visualization |
|------|-------|---------------|
| 사용자별 token 사용량 (line) | `sum by (principal_alias, token_type) (irate(iop_openai_usage_tokens_total[5m]))` | Time series, legend=`{{principal_alias}} {{token_type}}` |
| 일별 usage origin (table) | `sum by (principal_alias, token_ref, provider_id, served_model, route_model, usage_attribution, endpoint, response_mode, token_type) (increase(iop_openai_usage_tokens_total[1d]))` | Table |
| 실제 provider별 token 비율 (pie) | `sum by (provider_id, served_model, token_type) (iop_openai_usage_tokens_total)` | Stat or bar chart |
| 승인된 model-group token 비율 (pie) | `sum by (route_model, token_type) (iop_openai_usage_tokens_total{usage_attribution="model_group"})` | Stat or bar chart |
| 요청 상태 분포 (table) | `sum by (endpoint, status, usage_source) (iop_openai_requests_total)` | Table |
| cloud-equivalent cost (stat/table) | token rollup query에 price scalar 또는 Grafana calculated field 적용 | Stat or Table |
| avoided-cost ROI summary (stat) | cloud-equivalent cost 합계, 필요 시 별도 infra cost field 차감 | Stat |
| reasoning 보조 지표 (singlestat) | `sum(iop_openai_reasoning_observed_total)` | Singlestat |
| estimated reasoning tokens (bar) | `sum by (estimation_method) (iop_openai_reasoning_estimated_tokens_total)` | Bar chart |
| estimated reasoning vs provider-reported (line) | `sum by (provider_id, served_model, estimation_method) (iop_openai_reasoning_estimated_tokens_total)` vs `sum by (provider_id, served_model) (iop_openai_usage_tokens_total{token_type="reasoning"})` | Time series |

> Grafana JSON dashboard import file은 본 scope에 포함되지 않는다. 위 PromQL을 바탕으로 패널을 수동 구성한다.

---

## daily / monthly Rollup

### 일일 토큰 합계

```promql
# 일일 canonical token rollup (principal/token/provider/served-model/route/endpoint/response-mode/token-type별)
sum by (
  principal_ref, principal_alias, token_ref, provider_id, served_model,
  route_model, usage_attribution, endpoint, response_mode, token_type
) (
  increase(iop_openai_usage_tokens_total[1d])
)
```

> Grafana dashboard에서 `by time` aggregation으로 대체 가능:
> ```promql
> # Grafana 범용 aggregation (PromQL 함수 아님)
> # time range를 1일로 설정 후 sum by (principal_alias, token_type)
> iop_openai_usage_tokens_total
> ```

### 월간 토큰 합계

```promql
# 최근 30일 token rollup. 달력 월은 Grafana time range를 월 단위로 잡고 $__range를 사용한다.
sum by (
  principal_ref, principal_alias, token_ref, provider_id, served_model,
  route_model, usage_attribution, endpoint, response_mode, token_type
) (
  increase(iop_openai_usage_tokens_total[30d])
)
```

> Grafana dashboard에서 범용 aggregation으로 대체 가능:
> ```promql
> # Grafana 범용 aggregation (PromQL 함수 아님)
> # time range를 30일로 설정 후 sum by (principal_alias, token_type)
> iop_openai_usage_tokens_total
> ```

### 일일/월간 rollup 활용 시나리오

- `principal_alias` 또는 `principal_ref`를 `by` clause에 추가해 사용자별 일일/월간 토큰 사용량을 산출한다.
- `token_ref`를 추가하면 같은 사용자 아래 앱/통합/용도별 사용량을 분해한다.
- `token_type`을 함께 group by하면 input/output/reasoning/cached_input을 구분한다.
- `provider_id`, `served_model`, `endpoint`, `response_mode`를 추가하면 실제 사용 위치와 응답 경로별 origin breakdown을 만든다.
- 동일 논리 모델로 승인된 합계가 필요할 때만 `usage_attribution="model_group"`을 선택하고 `route_model`로 group by한다.

---

## Cloud Price Baseline

가격은 런타임이 자동으로 가져오지 않는다. 운영자가 기준일과 기준 모델을 명시한 정적 baseline을 Grafana dashboard 변수, annotation, table panel, 또는 운영 문서에 둔다.

최소 필드:

| Field | 예시 | 설명 |
|-------|------|------|
| `cloud_provider` | `example-cloud` | 가격 기준 provider |
| `baseline_model` | `example-model` | 비교 기준 cloud model |
| `provider_id` | `example-provider` | IOP metric의 실제 provider와 매핑할 값 |
| `served_model` | `example-model` | IOP metric의 실제 served model과 매핑할 값 |
| `route_model` | `example-route` | `usage_attribution=model_group` 승인 시에만 사용하는 논리 model rollup key |
| `token_type` | `input`, `output`, `cached_input`, `reasoning` | 가격을 적용할 token type |
| `price_per_1m_tokens` | `1.00` | 1M tokens당 단가 |
| `currency` | `USD` | 통화 |
| `effective_date` | `2026-07-10` | 가격 기준일 |
| `source_note` | `operator baseline` | 운영자가 남기는 기준 설명 |

baseline 예시. 아래 숫자는 문서용 예시이며 최신 cloud 가격이 아니다.

| cloud_provider | baseline_model | provider_id | served_model | route_model | token_type | price_per_1m_tokens | currency | effective_date |
|----------------|----------------|-------------|--------------|-------------|------------|----------------------|----------|----------------|
| example-cloud | example-model | example-provider | example-model | example-route | input | 1.00 | USD | 2026-07-10 |
| example-cloud | example-model | example-provider | example-model | example-route | output | 3.00 | USD | 2026-07-10 |
| example-cloud | example-model | example-provider | example-model | example-route | cached_input | 0.50 | USD | 2026-07-10 |

`reasoning` token은 provider가 별도 단가를 제공하면 별도 baseline을 둔다. 별도 단가가 없으면 운영자가 `output`과 같은 단가로 볼지, report에서 `reasoning`을 제외할지 baseline에 명시한다.

> `iop_openai_reasoning_estimated_tokens_total`은 operator observation용 추정치이며, cloud-equivalent cost에 합산하거나 chargeback에 사용하지 않는다. billing-grade reasoning cost는 provider-reported `iop_openai_usage_tokens_total{token_type="reasoning"}`에 한한다.

---

## Cost Formula

기본 산식:

```text
cloud_equivalent_cost = (tokens / 1_000_000) * price_per_1m_tokens
daily_cloud_equivalent_cost = sum(cloud_equivalent_cost by day, principal/token/model/endpoint/token_type)
monthly_cloud_equivalent_cost = sum(cloud_equivalent_cost over selected month)
```

Prometheus metric 자체에는 price table이 없으므로 1차 표면에서는 다음 중 하나를 사용한다.

- Grafana table transformation으로 token rollup 결과에 static price column을 붙이고 calculated field를 만든다.
- model/token_type별 panel query에 운영자가 관리하는 scalar price를 곱한다.
- Prometheus recording rule을 운영자가 별도 관리한다. 이 repo는 recording rule 파일을 만들지 않는다.

PromQL scalar 예시:

```promql
# example-provider / example-model input daily cloud-equivalent cost, USD
sum by (principal_alias, token_ref, provider_id, served_model, endpoint) (
  increase(iop_openai_usage_tokens_total{provider_id="example-provider", served_model="example-model", token_type="input"}[1d])
) / 1000000 * 1.00

# example-provider / example-model output daily cloud-equivalent cost, USD
sum by (principal_alias, token_ref, provider_id, served_model, endpoint) (
  increase(iop_openai_usage_tokens_total{provider_id="example-provider", served_model="example-model", token_type="output"}[1d])
) / 1000000 * 3.00

# example-provider / example-model cached input daily cloud-equivalent cost, USD
sum by (principal_alias, token_ref, provider_id, served_model, endpoint) (
  increase(iop_openai_usage_tokens_total{provider_id="example-provider", served_model="example-model", token_type="cached_input"}[1d])
) / 1000000 * 0.50
```

동일 논리 모델로 승인된 route에 같은 baseline을 적용할 때만 `usage_attribution="model_group", route_model="example-route"` selector와 `route_model` group by를 사용할 수 있다. 승인되지 않은 provider series를 route alias 기준으로 비용 합산하지 않는다.

여러 token type을 합산할 때는 token type별 cost query를 Grafana transformation으로 더한다. 단일 query에서 서로 다른 가격을 자동 join하려면 별도 recording rule 또는 external price table이 필요하다.

---

## ROI / Avoided-Cost Summary

이 MVP의 ROI는 billing-grade 정산이 아니라 cloud-equivalent avoided cost다.

```text
cloud_equivalent_cost = cloud baseline으로 환산한 사용량 비용
avoided_cost = IOP/local/provider에서 처리한 사용량의 cloud_equivalent_cost
net_avoided_cost = cloud_equivalent_cost - separately_known_local_infra_cost
```

- local infra cost가 별도로 계상되지 않으면 `avoided_cost`만 표시한다.
- local infra cost를 운영자가 별도 산정해 Grafana에 넣을 수 있으면 `net_avoided_cost`를 보조 field로 둔다.
- 이 값은 실제 결제, chargeback, 조직별 비용 배부, full infra cost accounting 근거가 아니다.
- 최소 summary table column은 `date`, `principal_alias`, `token_ref`, `provider_id`, `served_model`, `route_model`, `usage_attribution`, `endpoint`, `tokens`, `currency`, `cloud_equivalent_cost`, `avoided_cost`다.

Grafana table 구성 예:

1. Query A: daily token rollup by `principal_alias`, `token_ref`, `provider_id`, `served_model`, `route_model`, `usage_attribution`, `endpoint`, `token_type`.
2. Transformation: baseline price table 또는 panel-level scalar를 token type별로 적용한다.
3. Calculated field: `tokens / 1000000 * price_per_1m_tokens`.
4. Group by: `date`, `principal_alias`, `token_ref`, `provider_id`, `served_model`, `endpoint`. 승인된 model-group view만 `route_model`을 대신 사용한다.
5. Reduce: `cloud_equivalent_cost` 합계와 token 합계를 보여준다.

---

## Grafana Report 절차

1. Grafana time range를 `Today`, `Yesterday`, 최근 7일, 또는 이번 달로 설정한다.
2. `daily / monthly Rollup` query로 token usage table을 만든다.
3. `principal_ref`/`principal_alias` 기준 panel과 `token_ref` 기준 panel을 나란히 둔다.
4. `provider_id`, `served_model`, `route_model`, `usage_attribution`, `endpoint`, `response_mode`, `token_type` column을 유지해 실제 usage origin과 rollup 정책을 확인한다.
5. 운영 price baseline의 `effective_date`, `baseline_model`, `currency`를 dashboard text 또는 table에 함께 표시한다.
6. token type별 cost formula를 적용해 cloud-equivalent cost를 계산한다.
7. daily/monthly `avoided_cost` summary stat을 추가한다.
8. `usage_source="unavailable"` 요청 비율과 reasoning 보조 metric을 함께 확인해 cost report coverage 위험을 판단한다.
9. model-group view는 `usage_attribution="model_group"` selector가 있는 query만 사용하고, provider-default series가 섞이지 않았는지 확인한다.

### Rotation, revocation, and incident checks

1. For a planned rotation, record only the safe slot reference and expected old/new revision. Confirm the new revision begins receiving attempts and the old revision stops receiving new attempts after projection convergence.
2. For disable/revoke, issue one authorized full request through the bound public route and confirm it fails without a new provider-attempt sample. Do not test fallback with a different credential.
3. After a suspected leak or stale lease, inspect retained config/log/evidence with fixed-string matches for the known secret values in a restricted environment. Do not paste those values into PromQL, Grafana annotations, tickets, task artifacts, or shell history.
4. Before sharing evidence, reject matches for private-key blocks, provider secret/token formats, lease ids, slot aliases, target URLs with user-info, raw prompts/responses, and recipient/issuer private material. Keep only sanitized counters, safe slot reference/revision, provider/model identity, UTC time, and result.

---

## Limit Follow-up: 경계 명시

### 후속 Milestone으로 남기는 항목

- 사용자별 daily/monthly token **warn** 또는 **reject** enforcement는 이 Milestone의 범위가 아니며 후속 Milestone으로 Planned된다.
- 실제 결제, chargeback, 조직별 비용 배부, full infra cost accounting은 후속 범위다.
- live cloud pricing API 연동 또는 최신 가격 자동 동기화는 후속 범위다.
- request-level ledger / audit log는 Loki/queriable log축 후속 작업의 대상이다.

### 현재 문서가 제공하는 것

- `principal_ref`, `principal_alias`, `token_ref`, `provider_id`, `served_model`, `route_model`, `usage_attribution`, `endpoint`, `response_mode`, `token_type` 라벨 기반 daily/monthly rollup PromQL
- 실제 provider 기준 집계와 `usage_attribution="model_group"`으로 승인된 route만의 model-group rollup 분리 기준
- 같은 principal의 여러 token/app usage를 합산과 breakdown으로 함께 보는 기준
- 운영자가 관리하는 cloud price baseline 필드
- token type별 cloud-equivalent cost 산식과 query/report 예시
- avoided-cost 수준의 ROI summary 기준
- provider reasoning report 누락 시 `reasoning_observed_total` / `reasoning_chars_total`로 보조 확인, `reasoning_estimated_tokens_total`로 chars/4 token 추정치 제공 (billing-grade 아님)

이 rollup 기준은 후속 enforce Milestone에서 Prometheus query를 그대로 재사용할 수 있도록 설계되었다.

---

## Quick Reference: Label Values

| Label | 허용 값 |
|-------|---------|
| `endpoint` | `chat.completions`, `responses` |
| `status` | `success`, `error`, `cancel` |
| `usage_source` | `provider_reported`, `unavailable` |
| `token_type` | `input`, `output`, `reasoning`, `cached_input` |
| `response_mode` | `passthrough`, `normalized` |
| `usage_attribution` | `provider`, `model_group` |
| `estimation_method` | `chars_div_4` (특히 `iop_openai_reasoning_estimated_tokens_total` 전용) |

---

## Appendix: Grafana Scrape Target

Compose 관측 스택에서는 Prometheus가 내부 Docker DNS로 `control-plane:9093`을 scrape하고, Grafana datasource는 `http://prometheus:9090`을 사용한다. dev profile은 기존 native Edge endpoint를 바꾸지 않기 위해 `host.docker.internal:19101`도 scrape한다.

Host publish 기본 포트:

- local/test: Prometheus `19110`, Grafana `19120`, Control Plane metrics `19100`, Edge metrics `19092`
- dev: Prometheus `19111`, Grafana `19121`, Control Plane metrics `19103`, Edge metrics `19101`
- dev-corp compose: Prometheus `19112`, Grafana `19122`, Control Plane metrics `19104`, Edge metrics `19105`

이 관측 스택은 기존 테스트 사용자-facing endpoint를 변경하지 않는다. dev 기준 기존 UI/Control Plane/Edge OpenAI-compatible 접속점은 유지하며, Grafana/Prometheus/metrics 포트만 운영자용으로 추가한다. dev Grafana/Prometheus는 기본적으로 원격 runner 내부 또는 SSH 터널 접근 기준이다. dev-corp는 현재 접근 가능한 runner가 없을 때 문서 정의만 갱신하고 실제 compose 세팅은 별도 세션에서 수행한다.
