<!-- task=openai_reasoning_estimated_usage/02+01_dev_corp_rollout plan=0 tag=API -->

# Plan - API

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

이 rollout plan은 `openai_reasoning_estimated_usage/01_metric_core`가 PASS되어 `complete.log`를 만든 뒤에만 구현한다. `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션 작성까지가 구현의 마지막 단계다. 검증 출력에는 token/secret 원문을 남기지 않는다. 구현 후 active 파일을 그대로 둔 채 review ready로 보고하고, `USER_REVIEW.md`, `complete.log`, archive log는 만들지 않는다.

## 배경

core plan이 estimated reasoning token metric을 추가해도 dev-corp public Edge에 배포되지 않으면 사용량 측정 상태가 운영에서 확인되지 않는다. 이 plan은 public `iop.ai.kr` Edge와 mac-mini Prometheus를 기준으로 새 metric이 실제 OpenAI-compatible 호출과 동시 호출에서 증가하는지 검증한다.

## 사용자 리뷰 요청 흐름

구현 중 직접 사용자에게 묻지 않는다. 선택된 Milestone lock 결정이 실구현을 차단하는 경우에만 `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 이 plan은 non-roadmap 독립 rollout task이므로 SSH, secret, host readiness 문제는 사용자 리뷰가 아니라 검증 blocker 또는 follow-up으로 기록한다.

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-test/dev-corp/rules.md`
- `agent-test/dev-corp/edge-smoke.md`
- `agent-test/dev-corp/platform-common-smoke.md`
- `agent-contract/outer/openai-compatible-api.md`
- `agent-spec/input/openai-compatible-surface.md`
- `docs/openai-usage-grafana.md`
- `apps/edge/internal/openai/usage_metrics.go`
- `apps/edge/internal/openai/usage_metrics_test.go`

### SDD 기준

not applicable. 이 plan은 현재 Milestone 기능 Task 완료 체크를 하지 않는 non-roadmap rollout task다.

### 테스트 환경 규칙

- test_env: `dev-corp`
- `agent-test/dev-corp/rules.md`: present/read. public Edge runtime은 `iop.ai.kr`, OpenAI-compatible 기본 URL은 `https://digitalplatform.iop.ai.kr/v1`, Edge metrics는 `19105`, Prometheus는 mac-mini runner `19112`다.
- matched profiles:
  - `agent-test/dev-corp/edge-smoke.md`: present/read. OpenAI-compatible 경계 변경은 `/healthz`, `/v1/models`, `/v1/chat/completions` 동등 smoke와 Edge service/node adapter 수렴 확인이 필요하다.
  - `agent-test/dev-corp/platform-common-smoke.md`: present/read. 이 rollout은 proto/config 계약을 바꾸지 않으므로 platform-common command는 적용하지 않는다.

#### 테스트 환경 프리플라이트

- runner: `ssh fe@172.24.63.178`
- runner repo root: `/Users/fe/agent-work/iop-dev-corp`
- public Edge host: `iop.ai.kr`
- public Edge runtime root: `/Users/toki/agent-work/iop-dev-corp`
- Edge config path: `/Users/toki/agent-work/iop-dev-corp/build/dev-corp-runtime/edge.yaml`
- Edge id: `dev-corp-edge`
- expected ports: OpenAI-compatible `18086`, Edge-Node `18087`, admin `19094`, metrics `19105`, Prometheus API `19112` on mac-mini
- source sync rule: if runner checkout is dirty/divergent/stale, sync the exact implemented source state before building.
- setup blocker: if `01_metric_core/complete.log` is missing, do not deploy.

Preflight commands:

```bash
test -f agent-task/openai_reasoning_estimated_usage/01_metric_core/complete.log
ssh fe@172.24.63.178 'cd /Users/fe/agent-work/iop-dev-corp && git status --short --branch && git rev-parse HEAD && (/opt/homebrew/bin/go version || go version)'
ssh fe@172.24.63.178 'curl -fsS http://127.0.0.1:19112/-/ready'
ssh toki@iop.ai.kr 'cd /Users/toki/agent-work/iop-dev-corp && test -f build/dev-corp-runtime/edge.yaml && pgrep -af "iop-edge.*serve" && lsof -nP -iTCP:18086 -sTCP:LISTEN && lsof -nP -iTCP:19105 -sTCP:LISTEN'
```

### 테스트 커버리지 공백

- Local unit tests prove metric emission in-process, but not that the public Edge binary was rebuilt and Prometheus scraped the new metric.
- Single-call smoke proves metric availability but not concurrent request accounting.
- Prometheus query can lag scrape interval; rollout verification must either wait for scrape or inspect direct Edge metrics plus Prometheus.

### 심볼 참조

renamed/removed symbol: none.

### 분할 판단

split decision policy를 평가했다. 이 plan은 `openai_reasoning_estimated_usage/02+01_dev_corp_rollout`이며 predecessor index `01`은 `openai_reasoning_estimated_usage/01_metric_core`다. 현재 plan 작성 시점에는 predecessor `complete.log`가 없으므로 구현 시작 전 `agent-task/openai_reasoning_estimated_usage/01_metric_core/complete.log` 존재를 확인해야 한다.

### 범위 결정 근거

- source code 변경은 이 plan에서 하지 않는다. source 변경이 필요하면 `01_metric_core`로 되돌려 follow-up을 만든다.
- provider runtime tuning, capacity policy, Control Plane dashboard, Grafana dashboard JSON import는 제외한다.
- raw smoke token은 remote secret path에서 읽되 stdout/stderr, docs, task artifact에 기록하지 않는다.
- 기존 dev-corp metrics port 관련 dirty docs/test 변경은 보존한다.

### 빌드 등급

lane=`cloud`, grade=`G07`. public Edge 배포, remote runner build, Prometheus scrape, 동시 호출 검증처럼 외부 runtime 상태가 핵심이며 local-only 검증으로 완료를 판단할 수 없다.

## 의존 관계 및 구현 순서

1. `openai_reasoning_estimated_usage/01_metric_core/complete.log` 확인
2. runner/public Edge preflight
3. source sync와 Edge binary rebuild
4. public Edge binary 교체 및 restart
5. single-call metric smoke
6. 6개 이상 concurrent smoke
7. Prometheus query evidence 기록

## 구현 체크리스트

- [ ] `openai_reasoning_estimated_usage/01_metric_core/complete.log` 존재를 확인하고 없으면 배포하지 않는다.
- [ ] dev-corp runner, public Edge, Prometheus, ports, binary/config path preflight를 실행하고 blocker를 기록한다.
- [ ] 구현 완료 source state를 mac-mini runner에 sync하고 `iop-edge` Linux ARM64 binary를 rebuild한다.
- [ ] public Edge host에 새 binary를 배포하고 Edge process를 restart한 뒤 `/healthz`, `/v1/models`, `/metrics`를 확인한다.
- [ ] smoke token으로 `ornith:35b` single-call을 실행하고 Prometheus에서 estimated reasoning metric 증가를 확인한다.
- [ ] 같은 token으로 6개 이상 concurrent `/v1/chat/completions`를 실행하고 request/estimated metric 증가를 확인한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] dev-corp Edge binary rebuild and rollout

#### 문제

core plan의 local tests가 PASS해도 public `iop.ai.kr` Edge binary가 갱신되지 않으면 dev-corp에서는 새 metric이 보이지 않는다. `agent-test/dev-corp/edge-smoke.md:36-44`와 `agent-test/dev-corp/edge-smoke.md:130-135`는 OpenAI-compatible 경계 변경 시 public Edge 기준 검증을 요구한다.

#### 해결 방법

preflight로 runner/public Edge 상태를 확인하고, 구현 완료 source state를 runner에 sync한 뒤 Linux ARM64 Edge binary를 rebuild해 public Edge runtime에 배포한다. restart 후 direct health와 metrics endpoint에서 새 metric name이 노출되는지 확인한다.

Source sync/build command 예시:

```bash
rsync -a --delete --exclude .git --exclude build/dev-corp-runtime --exclude agent-task/archive ./ fe@172.24.63.178:/Users/fe/agent-work/iop-dev-corp/
ssh fe@172.24.63.178 'cd /Users/fe/agent-work/iop-dev-corp && GOCACHE=/tmp/iop-go-cache /opt/homebrew/bin/go test -count=1 ./apps/edge/internal/openai && GOOS=linux GOARCH=arm64 CGO_ENABLED=0 GOCACHE=/tmp/iop-go-cache /opt/homebrew/bin/go build -o build/dev-corp-runtime/bin/iop-edge ./apps/edge/cmd/edge'
scp fe@172.24.63.178:/Users/fe/agent-work/iop-dev-corp/build/dev-corp-runtime/bin/iop-edge /tmp/iop-edge.dev-corp
scp /tmp/iop-edge.dev-corp toki@iop.ai.kr:/Users/toki/agent-work/iop-dev-corp/build/dev-corp-runtime/bin/iop-edge.new
ssh toki@iop.ai.kr 'cd /Users/toki/agent-work/iop-dev-corp && mv build/dev-corp-runtime/bin/iop-edge.new build/dev-corp-runtime/bin/iop-edge && pkill -f "iop-edge.*serve" && nohup build/dev-corp-runtime/bin/iop-edge serve --config build/dev-corp-runtime/edge.yaml > build/dev-corp-runtime/logs/iop-edge-rollout-$(date +%Y%m%d%H%M%S).log 2>&1 &'
```

If the existing process manager differs, record the replacement restart command in `계획 대비 변경 사항` with `pgrep -af` evidence.

#### 수정 파일 및 체크리스트

- [ ] no source file edits in this plan
- [ ] runner source sync performed from completed core source state
- [ ] runner `iop-edge` rebuilt for `GOOS=linux GOARCH=arm64`
- [ ] public Edge binary replaced atomically with `.new` then `mv`
- [ ] public Edge restarted and listening ports verified

#### 테스트 작성

skip new source tests. This is a rollout-only plan; local source tests are inherited from `01_metric_core` and rerun before build.

#### 중간 검증

```bash
ssh toki@iop.ai.kr 'curl -fsS http://127.0.0.1:18086/healthz && curl -fsS http://127.0.0.1:19105/metrics | grep -F "iop_openai_reasoning_estimated_tokens_total"'
```

Expected: healthz succeeds and metrics output contains the new metric name.

### [API-2] single-call reasoning estimated metric smoke

#### 문제

Metric name exposure alone does not prove runtime request accounting. A real OpenAI-compatible request must produce reasoning text without provider-reported reasoning token count and increase the estimated metric for the smoke principal/token.

#### 해결 방법

Read the smoke token from the existing remote secret path without echoing it. Call `ornith:35b` through public OpenAI-compatible URL and query Prometheus on the mac-mini runner for the smoke `token_ref`.

Commands:

```bash
SMOKE_TOKEN="$(ssh toki@iop.ai.kr 'cat /Users/toki/agent-work/iop-dev-corp/build/dev-corp-runtime/.secrets/openai_usage_metering_smoke_token')"
curl -fsS https://digitalplatform.iop.ai.kr/v1/models -H "Authorization: Bearer ${SMOKE_TOKEN}" >/tmp/devcorp-models.json
curl -fsS https://digitalplatform.iop.ai.kr/v1/chat/completions \
  -H "Authorization: Bearer ${SMOKE_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"model":"ornith:35b","messages":[{"role":"user","content":"짧게 생각 과정을 내부적으로 사용해서 2+2 답만 말해."}],"max_tokens":64,"include_reasoning":true}' \
  >/tmp/devcorp-reasoning-single.json
ssh fe@172.24.63.178 'curl -fsG http://127.0.0.1:19112/api/v1/query --data-urlencode "query=sum by (token_ref,estimation_method) (iop_openai_reasoning_estimated_tokens_total{token_ref=\"devcorp-smoke-usage\"})"'
```

Do not paste `${SMOKE_TOKEN}` value into review output.

#### 수정 파일 및 체크리스트

- [ ] `/v1/models` with smoke token returns 200
- [ ] single `/v1/chat/completions` returns 200
- [ ] Prometheus query shows `token_ref="devcorp-smoke-usage"` and positive estimated token value
- [ ] `iop_openai_usage_tokens_total{token_type="reasoning"}` remains absent or unchanged when provider did not report reasoning tokens

#### 테스트 작성

skip source tests. This item is live smoke verification.

#### 중간 검증

```bash
ssh fe@172.24.63.178 'curl -fsG http://127.0.0.1:19112/api/v1/query --data-urlencode "query=sum by (token_ref,estimation_method) (iop_openai_reasoning_estimated_tokens_total{token_ref=\"devcorp-smoke-usage\"})"'
```

Expected: query succeeds and returns a positive value after scrape.

### [API-3] concurrent smoke and Prometheus accounting

#### 문제

Single-call smoke does not prove concurrent accounting. The original user flow requires usage measurement under real runtime calls, and earlier dev-corp smokes used concurrent requests to catch routing/accounting misses.

#### 해결 방법

Run at least 6 concurrent requests with the same smoke token. Then query requests and estimated reasoning metric deltas by `token_ref`. If Prometheus scrape lags, wait one scrape interval and rerun only the query.

Commands:

```bash
SMOKE_TOKEN="$(ssh toki@iop.ai.kr 'cat /Users/toki/agent-work/iop-dev-corp/build/dev-corp-runtime/.secrets/openai_usage_metering_smoke_token')"
rm -f /tmp/devcorp-reasoning-concurrent-*.json
seq 1 6 | xargs -P6 -I{} sh -c 'curl -fsS https://digitalplatform.iop.ai.kr/v1/chat/completions -H "Authorization: Bearer '"${SMOKE_TOKEN}"'" -H "Content-Type: application/json" -d "{\"model\":\"ornith:35b\",\"messages\":[{\"role\":\"user\",\"content\":\"요청 {}: 한 문장으로 답해.\"}],\"max_tokens\":64,\"include_reasoning\":true}" > /tmp/devcorp-reasoning-concurrent-{}.json'
ssh fe@172.24.63.178 'curl -fsG http://127.0.0.1:19112/api/v1/query --data-urlencode "query=sum by (token_ref,status) (iop_openai_requests_total{token_ref=\"devcorp-smoke-usage\"})"'
ssh fe@172.24.63.178 'curl -fsG http://127.0.0.1:19112/api/v1/query --data-urlencode "query=sum by (token_ref,estimation_method) (iop_openai_reasoning_estimated_tokens_total{token_ref=\"devcorp-smoke-usage\"})"'
```

#### 수정 파일 및 체크리스트

- [ ] 6 concurrent requests all return HTTP 200
- [ ] `iop_openai_requests_total{token_ref="devcorp-smoke-usage",status="success"}` increases by at least 6 from the pre-concurrency snapshot
- [ ] `iop_openai_reasoning_estimated_tokens_total{token_ref="devcorp-smoke-usage"}` increases from the pre-concurrency snapshot
- [ ] no raw token appears in terminal output copied into review stub

#### 테스트 작성

skip source tests. This item is live concurrency verification.

#### 중간 검증

```bash
test "$(ls /tmp/devcorp-reasoning-concurrent-*.json | wc -l | tr -d ' ')" -ge 6
```

Expected: at least 6 response files exist and each curl command succeeded.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| none | API-1, API-2, API-3 |

## 최종 검증

```bash
test -f agent-task/openai_reasoning_estimated_usage/01_metric_core/complete.log
ssh fe@172.24.63.178 'cd /Users/fe/agent-work/iop-dev-corp && git status --short --branch && git rev-parse HEAD && (/opt/homebrew/bin/go version || go version)'
ssh fe@172.24.63.178 'curl -fsS http://127.0.0.1:19112/-/ready'
ssh toki@iop.ai.kr 'cd /Users/toki/agent-work/iop-dev-corp && test -f build/dev-corp-runtime/edge.yaml && pgrep -af "iop-edge.*serve" && lsof -nP -iTCP:18086 -sTCP:LISTEN && lsof -nP -iTCP:19105 -sTCP:LISTEN'
ssh toki@iop.ai.kr 'curl -fsS http://127.0.0.1:18086/healthz && curl -fsS http://127.0.0.1:19105/metrics | grep -F "iop_openai_reasoning_estimated_tokens_total"'
ssh fe@172.24.63.178 'curl -fsG http://127.0.0.1:19112/api/v1/query --data-urlencode "query=sum by (token_ref,estimation_method) (iop_openai_reasoning_estimated_tokens_total{token_ref=\"devcorp-smoke-usage\"})"'
ssh fe@172.24.63.178 'curl -fsG http://127.0.0.1:19112/api/v1/query --data-urlencode "query=sum by (token_ref,status) (iop_openai_requests_total{token_ref=\"devcorp-smoke-usage\"})"'
git diff --check -- agent-task/openai_reasoning_estimated_usage
```

Expected: predecessor complete.log exists, preflight succeeds, new metric is exposed, Prometheus returns positive estimated reasoning token values for smoke token, request success count includes concurrent calls, and task artifact diff has no whitespace errors.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
