diff --git a/agent-ops/rules/project/rules.md b/agent-ops/rules/project/rules.md index 02d06cb..cb1917b 100644 --- a/agent-ops/rules/project/rules.md +++ b/agent-ops/rules/project/rules.md @@ -94,6 +94,7 @@ ## 스킬 라우팅 +- OpenAI-compatible usage token 발급, principal_ref token 등록, principal alias 매핑, raw IOP token 1회 전달: `agent-ops/skills/project/openai-usage-token-issue/SKILL.md` - dev-corp 배포, dev-corp runtime 배포, 회사망 mac-mini Edge/Node dev-corp 환경 배포, dev-corp provider pool 배포, dev-corp OpenAI-compatible capacity smoke 검증: `agent-ops/skills/project/dev-corp-runtime-deploy/SKILL.md` - dev 배포, dev-runtime 배포, Edge/Node dev 환경 배포, provider pool 배포, OpenAI-compatible capacity smoke 검증: `agent-ops/skills/project/dev-runtime-deploy/SKILL.md` - 사용자 실행 파이프라인 검증, repo 내부 edge-node 진단, 메시지 2회 왕복, edge command 응답, 보조 E2E smoke, full-cycle 실제 구동, `scripts/dev/edge.sh`/`scripts/dev/node.sh` 진단 테스트: `agent-ops/skills/project/e2e-smoke/SKILL.md` diff --git a/agent-ops/skills/project/openai-usage-token-issue/SKILL.md b/agent-ops/skills/project/openai-usage-token-issue/SKILL.md new file mode 100644 index 0000000..71a7d53 --- /dev/null +++ b/agent-ops/skills/project/openai-usage-token-issue/SKILL.md @@ -0,0 +1,110 @@ +--- +name: openai-usage-token-issue +version: 1.0.0 +description: OpenAI-compatible usage metering용 IOP token을 principal_ref/internal alias에 연결해 발급하는 운영 절차 +--- + +# openai-usage-token-issue + +## 목적 + +OpenAI-compatible 사용량 metering에 쓸 IOP bearer token을 발급하고, raw token 없이 `token_ref`, token hash, `principal_ref`, 내부 alias 매핑만 운영 기록에 남긴다. +IOP는 사용자/테넌트 source of truth를 소유하지 않고, 외부 principal id 또는 내부 운영 id를 참조값으로만 다룬다. + +## 언제 호출할지 + +- OpenAI-compatible 호출 사용량을 특정 `principal_ref` 또는 내부 alias로 귀속할 IOP token을 새로 발급할 때 +- dev/dev-corp 운영자가 Grafana 사용량 label에 노출될 `token_ref`, `principal_alias`를 준비할 때 +- raw bearer token을 tracked 파일이나 최종 보고에 남기지 않고 1회 전달해야 할 때 + +## 입력 + +- `principal_ref`: 외부 사용자/테넌트 프로젝트 또는 운영 시스템의 principal 참조값 (필수) +- `principal_alias`: Grafana에 노출할 내부 alias. 없으면 `principal_ref`에서 secret이 아닌 짧은 별칭을 정한다. (선택) +- `token_ref`: metric label과 설정에 쓸 안정 token 참조값. 없으면 token hash prefix로 만든다. (선택) +- `output_path`: raw token 없이 매핑 기록을 저장할 비공개/운영 전용 파일 경로. tracked docs/config에 쓰지 않는다. (선택) + +## 먼저 확인할 것 + +- [ ] `principal_ref`가 secret, raw email, provider token, provider identity가 아니라 외부 시스템 참조값인지 확인한다. +- [ ] raw token을 tracked `docs/`, `agent-roadmap/`, `agent-spec/`, `configs/`, git diff, shell history, 최종 보고에 남기지 않을 전달 경로를 정한다. +- [ ] `token_ref`와 `principal_alias`가 낮은 cardinality label로 안전한 값인지 확인한다. +- [ ] 기존 token을 회전하는 경우 기존 `token_ref`를 재사용할지 새 `token_ref`를 만들지 운영 정책을 확인한다. + +## 실행 절차 + +1. **입력 정규화** + - `principal_ref` 앞뒤 공백을 제거한다. + - `principal_alias`는 공백을 `-`로 바꾸고, 운영자가 식별할 수 있는 짧은 ASCII alias로 둔다. + - `token_ref`를 직접 받지 않았으면 생성할 token hash의 앞 16자를 사용해 `ioptok_` 형식으로 만든다. + +2. **raw token 생성** + - 현재 shell에서 `set +x`를 확인한다. + - 아래 형태의 고엔트로피 token을 생성한다. 실제 출력은 operator에게 1회만 전달한다. + +```bash +set +x +umask 077 +raw_token="iop_$(openssl rand -base64 36 | tr '+/' '-_' | tr -d '=')" +token_hash="$(printf '%s' "$raw_token" | sha256sum | awk '{print $1}')" +token_ref="${token_ref:-ioptok_${token_hash:0:16}}" +``` + +3. **매핑 기록 작성** + - raw token은 파일에 쓰지 않는다. + - 운영 기록에는 아래 필드만 남긴다. + +```yaml +token_ref: "" +principal_ref: "" +principal_alias: "" +token_hash_sha256: "" +status: active +``` + +4. **raw token 1회 전달** + - raw token은 operator-only 채널로 한 번만 전달한다. + - 채팅 최종 보고, git diff, tracked 문서, 검증 출력에는 raw token을 쓰지 않는다. + +5. **누출 확인** + - 저장소 안에 raw token이 남지 않았는지 조용한 검색으로 확인한다. 실패 시 출력에 raw token이 찍히지 않게 한다. + +```bash +if rg -q -F "$raw_token" agent-ops agent-roadmap agent-spec agent-contract docs configs apps packages proto; then + echo "raw token leak detected in tracked workspace paths" + exit 1 +fi +echo "raw token not found in tracked workspace paths" +``` + +6. **결과 보고** + - `token_ref`, `principal_ref`, `principal_alias`, 매핑 기록 위치, raw token 전달 여부만 보고한다. + - raw token과 전체 token hash는 보고하지 않는다. + +## 실행 결과 검증 + +- [ ] raw token이 operator에게 1회만 전달되었는가 +- [ ] tracked 파일에는 raw token이 없고, `token_ref`, `principal_ref`, `principal_alias`, hash만 남았는가 +- [ ] `rg -q -F "$raw_token" ...` 누출 확인이 실패하지 않았는가 +- [ ] 최종 보고에 raw token, provider token, provider identity, raw prompt/response가 포함되지 않았는가 +- 검증 실패 시: raw token을 폐기하고 새 token을 발급한다. 누출된 tracked 파일은 수정한 뒤 다시 누출 확인을 실행한다. + +## 출력 형식 + +```text +OpenAI usage token issue +- token_ref: +- principal_ref: +- principal_alias: +- mapping_record: +- raw_token_delivered_once: +- leak_check: +- notes: raw token omitted from report +``` + +## 금지 사항 + +- raw token을 tracked 파일, 최종 보고, 로그, metric label, Grafana dashboard, shell trace에 남기지 않는다. +- `metadata.user`, provider token, provider identity를 사용자 식별 source로 쓰지 않는다. +- `request_id`, `session_id`, raw token, raw prompt, raw response 같은 high-cardinality 또는 secret 값을 metric label 후보로 만들지 않는다. +- 사용자 CRUD, tenant/org source of truth, token 제한 enforcement를 이 스킬 책임으로 확장하지 않는다. diff --git a/agent-roadmap/phase/operational-observability-provider-management/PHASE.md b/agent-roadmap/phase/operational-observability-provider-management/PHASE.md index e283046..20a2e34 100644 --- a/agent-roadmap/phase/operational-observability-provider-management/PHASE.md +++ b/agent-roadmap/phase/operational-observability-provider-management/PHASE.md @@ -32,9 +32,9 @@ provider 확장 Phase에서 검증한 Ollama, vLLM, SGLang, Lemonade 같은 추 - 경로: [model-group-long-context-admission](../../archive/phase/operational-observability-provider-management/milestones/model-group-long-context-admission.md) - 요약: model group의 단일 context window 계약을 유지하면서 입력 기준 long-context 요청을 별도 slot으로 admission하고, long 요청이 찬 provider와 queue 앞 long 요청이 normal 요청을 불필요하게 막지 않도록 라우팅 정책을 완료했다. -- [스케치] 사용량, 토큰, 로그 운영 추적 MVP +- [계획] 사용자별 OpenAI-compatible 토큰 측정 MVP - 경로: [usage-token-log-ops-mvp](milestones/usage-token-log-ops-mvp.md) - - 요약: 사용자 관리, token 관리, API/CLI/local inference 사용량 집계, 요청별 device/provider/time/token ledger, 로그 관리의 1차 운영 경계를 스케치한다. + - 요약: OpenAI-compatible 호출을 IOP bearer token 기반 `principal_ref`/alias로 귀속하고, provider/user-facing 표면을 넓히지 않은 채 input/output/reasoning token 사용량을 Prometheus metric으로 측정해 Grafana에서 ROI 판단의 1차 근거로 본다. - [스케치] 요청 실행 로그와 Usage Ledger 기반 - 경로: [request-execution-log-usage-ledger-foundation](milestones/request-execution-log-usage-ledger-foundation.md) diff --git a/agent-roadmap/phase/operational-observability-provider-management/milestones/usage-token-log-ops-mvp.md b/agent-roadmap/phase/operational-observability-provider-management/milestones/usage-token-log-ops-mvp.md index 4fc519f..7ab3764 100644 --- a/agent-roadmap/phase/operational-observability-provider-management/milestones/usage-token-log-ops-mvp.md +++ b/agent-roadmap/phase/operational-observability-provider-management/milestones/usage-token-log-ops-mvp.md @@ -1,4 +1,4 @@ -# Milestone: 사용량, 토큰, 로그 운영 추적 MVP +# Milestone: 사용자별 OpenAI-compatible 토큰 측정 MVP ## 위치 @@ -7,63 +7,66 @@ ## 목표 -사용자 관리, token 관리, API/CLI/local inference 사용량 집계, 로그 관리를 1차 MVP 운영 축으로 스케치한다. -이미 존재하는 runtime usage, CLI usage status, audit/observability 패키지와 Control Plane/Client 운영 표면을 기준으로 삼되, 구체 schema와 저장소 구현은 후속 구체화 전까지 확정하지 않는다. -사용자 요청 하나가 어떤 device/provider/node/model로 처리되었는지, 언제 시작/종료되었는지, input/think/output token을 얼마나 사용했는지 추적 가능한 운영 기록을 MVP 핵심 후보로 둔다. +OpenAI-compatible 호출의 토큰 사용량을 IOP bearer token 기준으로 외부 principal id 또는 내부 alias에 귀속하고, Prometheus metric으로 노출해 사용자별 사용량과 ROI 판단의 1차 근거를 볼 수 있게 한다. +외부 호출 표면은 OpenAI-compatible 표준 field와 `Authorization: Bearer `만 사용하며, `metadata.user`나 provider token/provider identity를 사용자 입력 또는 사용자-facing 사용량 표면에 추가하지 않는다. +IOP는 완성된 사용자/테넌트 관리 시스템을 소유하지 않고, 별도 사용자/테넌트 프로젝트와 연결될 `principal_ref`를 token mapping으로 보관하는 얇은 인증/귀속 레이어만 가진다. +1차 MVP는 측정에 집중하고, 사용자별 token 제한과 비용/모델 가격 비교는 측정 데이터가 안정화된 뒤 후속 축으로 진행한다. ## 상태 -[스케치] +[계획] ## 승격 조건 -- [ ] 1차 MVP에서 관리할 사용자/token 범위와 운영 주체를 결정한다. -- [ ] API, CLI, local inference 사용량을 요청, run, session, user, provider/device/model 중 어떤 단위로 집계할지 결정한다. -- [ ] 요청별 input token, cached input token, think/reasoning token, output token, total token을 어떤 provider-reported/estimated 기준으로 기록할지 결정한다. -- [ ] 요청별 사용 device/provider/node, model alias/served model, queue/dispatch/start/first-token/end timestamp, duration, status/error를 어느 이벤트와 저장소에 남길지 결정한다. -- [ ] 로그 관리의 최소 범위, 보관/조회/export 책임 경계, prompt/response preview redaction 정책을 결정한다. -- [ ] Control Plane, Edge-local CLI, Client 중 어떤 표면을 MVP에 포함할지 결정한다. +- 없음: 사용자 검토에서 1차 범위가 OpenAI-compatible bearer-token 기반 Prometheus metering과 얇은 principal-token 매핑으로 확정되었고, 완성된 사용자/테넌트 관리, Control Plane 대시보드, request-level ledger, Loki 로그, 사용자별 제한, 비용/모델 가격 비교는 후속으로 분리되었다. ## 구현 잠금 -- 상태: 잠금 -- SDD: 불필요 -- SDD 사유: 현재 Milestone은 운영 범위와 산출물 스케치이며, proto/schema/storage/API 구현 전 별도 구체화에서 SDD 필요 여부를 재판정한다. -- 결정 필요: 아래 체크리스트 - - [ ] 사용자와 token을 개인/Edge/조직 중 어떤 단위로 시작할지 결정한다. - - [ ] 사용량 집계의 MVP 단위를 요청, token, runtime duration, CLI limit status, provider/device throughput 중 어디까지로 둘지 결정한다. - - [ ] think/reasoning token을 provider-reported 값만 신뢰할지, 표시된 reasoning text 기반 추정치를 함께 둘지 결정한다. - - [ ] 요청별 device/provider 사용 기록을 사용자 로그, audit log, 성능 ledger 중 어디에 canonical하게 둘지 결정한다. - - [ ] 로그 조회와 export의 MVP 표면을 Control Plane/Client/CLI 중 어디에 둘지 결정한다. +- 상태: 해제 +- SDD: 필요 +- SDD 문서: [SDD.md](../../../sdd/operational-observability-provider-management/usage-token-log-ops-mvp/SDD.md) +- SDD 사유: 1차는 OpenAI-compatible request/response body, Edge-Node proto, Control Plane API를 바꾸지 않지만, IOP token alias 매핑과 Prometheus metric schema라는 운영 계약을 고정해야 한다. +- 잠금 해제 조건: + - [x] SDD 잠금이 해제되어 있다. + - [x] SDD 사용자 리뷰가 없거나 승인/해결되었다. + - [x] Acceptance Scenario가 Milestone 기능 Task와 연결되어 있다. + - [x] Evidence Map이 완료 시 `Roadmap Completion`과 최종 검증 evidence로 검증 가능하게 연결되어 있다. +- 결정 필요: 없음 ## 범위 -- 사용자 관리와 token 관리의 1차 운영 경계 -- API, CLI, local inference 사용량 집계의 MVP 산출물 정의 -- 요청별 run ledger: user/session/workspace/source/request_id/run_id, 사용 device/provider/node/model, queue/dispatch/start/first-token/end timestamp, status/error -- token ledger: input, cached input, think/reasoning, output, total token과 provider-reported/estimated/unavailable source 표시 -- Edge/Node/Control Plane 로그 관리의 최소 조회 경계 -- 기존 `Usage`, CLI usage status, audit/observability 패키지와 연결 가능한 표준선 정리 +- OpenAI-compatible HTTP 호출 중 현재 provider/runtime usage를 관측할 수 있는 경로의 토큰 사용량 측정 +- IOP bearer token 또는 그 secret reference를 `principal_ref`, 내부 alias, token reference로 매핑하는 운영 경계 +- 프로젝트용 운영 skill이 `principal_ref` 또는 내부 id를 입력받아 raw token을 생성하고, tracked 문서/config에는 raw token 없이 token reference 또는 hash와 alias만 남기는 경계 +- pure `passthrough` 응답 body를 바꾸지 않고 Edge 내부에서 usage frame, provider-reported usage, 완료 이벤트를 관측해 metric으로 집계하는 경로 +- Prometheus counter 기반 사용자별 token usage metric과 request count/status metric +- token type: `input`, `output`, provider가 별도 보고한 `reasoning`, provider가 별도 보고한 `cached_input` +- reasoning token을 provider가 별도 보고하지 않는 경우 token 추정 없이 `unavailable`로 취급하고, 필요 시 reasoning 관측 여부와 reasoning character count만 보조 metric으로 남기는 기준 +- Grafana dashboard/query를 1차 조회 표면으로 두고 Control Plane/Client 대시보드는 만들지 않는 운영 경계 +- 사용자별 제한은 2차 후속 축으로 분리하되, 1차 metric label과 rollup이 제한 정책에 재사용될 수 있게 하는 기준 ## 기능 ### Epic: [ops-usage] Usage and Log Operations -운영자가 사용량과 로그를 한 곳에서 이해하기 위한 최소 capability를 묶는다. +운영자가 OpenAI-compatible token 사용량을 외부 principal id 또는 내부 alias 단위로 빠르게 확인하기 위한 Prometheus/Grafana 중심 capability를 묶는다. -- [ ] [identity-scope] 사용자와 token 관리의 MVP 책임 경계가 정리되어 있다. -- [ ] [usage-rollup] API, CLI, local inference 사용량 집계 후보와 제외 범위가 요청/run/session/user/provider/device/model 단위로 정리되어 있다. -- [ ] [request-ledger] 사용자 요청별 사용 device/provider/node/model, run_id/request_id, queue/dispatch/start/first-token/end timestamp, duration, status/error를 남기는 MVP ledger 후보가 정리되어 있다. -- [ ] [token-breakdown] input, cached input, think/reasoning, output, total token과 usage source(provider-reported/estimated/mixed/unavailable) 기준이 정리되어 있다. -- [ ] [usage-collection] OpenAI-compatible streaming usage, provider별 native usage, CLI usage status, 추정 token count를 어떤 우선순위로 수집할지 정리되어 있다. -- [ ] [log-surface] Edge/Node/Control Plane 로그 조회와 export의 최소 표면 후보가 redaction/preview 정책과 함께 정리되어 있다. -- [ ] [ops-review] 사용자가 MVP 운영 범위와 후속 구체화 우선순위를 검토했다. +- [ ] [identity-scope] OpenAI-compatible 호출 주체는 `metadata.user`가 아니라 IOP bearer token의 운영 매핑으로 판별하고, IOP는 사용자/테넌트 모델이 아닌 `principal_ref` 참조값만 소유하는 기준이 정리되어 있다. +- [ ] [token-alias] Edge-owned 운영 설정 또는 secret reference에서 `token_ref -> principal_ref/internal_alias` 매핑을 관리하고, Grafana에는 내부 alias와 token reference만 노출하는 기준이 정리되어 있다. +- [x] [token-issue-skill] 프로젝트용 운영 skill이 `principal_ref` 또는 내부 id를 등록하면 raw IOP token을 생성하고, raw token은 한 번만 운영자에게 전달하며, tracked 파일에는 token reference 또는 hash만 남기는 기준이 정리되어 있다. +- [ ] [openai-scope] 1차 측정 범위가 OpenAI-compatible 호출로 제한되고, CLI usage status, A2A, local-only adapter usage, request-level ledger는 후속 범위로 분리되어 있다. +- [ ] [passthrough-safe] pure `passthrough` 응답 body에는 IOP sideband field/event를 추가하지 않고, Edge 내부 관측만으로 usage metric을 emit하는 기준이 정리되어 있다. +- [ ] [token-breakdown] `input`, `output`, provider-reported `reasoning`, provider-reported `cached_input` token type과 provider-reported/unavailable source 표시 기준이 정리되어 있다. +- [ ] [reasoning-observed] provider가 reasoning token을 별도 보고하지 않으면 token 추정치를 만들지 않고, reasoning 관측 여부와 character count만 보조 metric으로 남기는 기준이 정리되어 있다. +- [ ] [prometheus-metrics] Prometheus metric 이름과 label cardinality 기준이 정리되어 있다. request_id, session_id, raw token, raw prompt/response, provider token은 label에 넣지 않는다. +- [ ] [grafana-surface] Control Plane/Client dashboard를 만들지 않고 Grafana dashboard/query를 1차 조회 표면으로 쓰는 기준이 정리되어 있다. +- [ ] [limit-followup] 사용자별 token 제한은 2차 후속 축으로 분리하고, 1차 metric이 daily/monthly warn/reject 정책에 재사용될 수 있는 기준이 정리되어 있다. ## 완료 리뷰 - 상태: 없음 - 요청일: 없음 -- 완료 근거: 스케치 Milestone이며 기능 Task가 아직 충족되지 않았다. +- 완료 근거: 기능 Task가 아직 충족되지 않았다. - 리뷰 필요: - [ ] 사용자가 완료 결과를 확인했다 - [ ] archive 이동을 승인했다 @@ -72,15 +75,29 @@ ## 범위 제외 - 결제, chargeback, 조직 IAM, 장기 retention 정책의 상세 구현 +- 사용자 CRUD, tenant/org 모델, RBAC, SSO/OIDC, 초대/탈퇴 같은 완성된 사용자/테넌트 관리 +- Claude Opus, Gemma4 공식/내부 가격 비교와 ROI 금액 산출 +- 사용자별 token 제한의 warn/reject enforcement 구현 +- Control Plane 또는 Client 기반 usage dashboard 구현 +- request-level ledger 저장소, Loki 연동, 요청별 상세 감사/디버깅 조회 - 모든 로그 schema와 audit schema 확정 - provider routing 또는 inference adapter 구현 -- hidden reasoning token을 provider가 보고하지 않는 경우의 완전 정확한 복원 +- `metadata.user` 또는 OpenAI-compatible request body 확장으로 사용자를 식별하는 방식 +- 사용자-facing 운영 표면에 provider token 또는 provider identity를 노출하는 방식 +- hidden reasoning token을 provider가 보고하지 않는 경우의 추정 token 산출 또는 완전 정확한 복원 ## 작업 컨텍스트 -- 관련 경로: `apps/control-plane`, `apps/client`, `apps/edge`, `apps/node`, `packages/go/audit`, `packages/go/observability`, `proto/iop/runtime.proto`, `apps/edge/internal/openai`, `apps/node/internal/adapters/openai_compat` -- 표준선(선택): Edge는 로컬 runtime 상태의 원본을 유지하고, Control Plane은 연결 view와 운영 기록을 보기 쉽게 제공한다. -- 표준선(선택): provider가 usage를 보고하면 provider-reported 값을 우선하고, 보고하지 않는 필드는 estimated/unavailable source를 명시한다. +- 관련 경로: `apps/edge/internal/openai`, `apps/edge/internal/service`, `apps/node/internal/adapters/openai_compat`, `apps/node/internal/adapters/vllm`, `packages/go/observability`, `packages/go/config`, `configs/edge.yaml`, `proto/iop/runtime.proto`, [openai-compatible-api.md](../../../../agent-contract/outer/openai-compatible-api.md) +- 표준선(선택): OpenAI-compatible 호출 주체 식별은 `Authorization: Bearer `에서 온 IOP token을 Edge-owned 운영 매핑으로 `principal_ref`와 내부 alias에 연결해 처리한다. `metadata.user`는 사용하지 않는다. +- 표준선(선택): IOP는 사용자/테넌트 source of truth가 아니며, `principal_ref`는 별도 사용자/테넌트 프로젝트 또는 dev-corp 운영 id에 대한 foreign-key-like 참조값이다. +- 표준선(선택): token 발급은 프로젝트용 운영 skill로 보조할 수 있지만 raw token은 tracked roadmap/docs/config/log/metric에 남기지 않고 operator에게 1회 전달한다. +- 완료 근거(`token-issue-skill`): [openai-usage-token-issue/SKILL.md](../../../../agent-ops/skills/project/openai-usage-token-issue/SKILL.md)와 [project rules.md](../../../../agent-ops/rules/project/rules.md)에 raw token 1회 전달, tracked 파일 raw token 미저장, `token_ref`/`principal_ref`/alias 매핑 절차가 등록되어 있다. +- 표준선(선택): provider token과 provider identity는 사용자 입력 표면이나 사용자-facing usage dashboard에 노출하지 않는다. 내부 진단이 필요하면 user usage metric과 분리한다. +- 표준선(선택): pure `passthrough`는 provider-original response body를 유지하고, IOP usage metric은 Edge 내부 관측으로만 emit한다. +- 표준선(선택): token count는 provider-reported 값을 우선하며, missing reasoning token은 추정하지 않고 unavailable로 둔다. 단, reasoning stream/content를 관측한 사실과 character count는 token이 아닌 보조 metric으로 남길 수 있다. +- 표준선(선택): Prometheus label은 low-cardinality 값만 사용한다. 후보는 `edge_id`, `principal_ref`, `principal_alias`, `token_ref`, `model_group`, `endpoint`, `response_mode`, `token_type`, `usage_source`, `status`이며, request/session/raw token/raw payload는 label로 쓰지 않는다. +- 표준선(선택): Grafana가 1차 대시보드이며 Control Plane/Client usage dashboard는 후속 필요가 생길 때만 검토한다. - 선행 작업: Control Plane과 Client 운영, OpenAI-compatible usage 응답, CLI usage checker -- 후속 작업: 요청 실행 로그와 Usage Ledger 기반, 운영 리포트, audit retention, 품질 기반 routing/fallback 고도화 -- 확인 필요: 사용자/token 단위, 사용량 집계 단위, think token 신뢰 기준, 로그 관리 표면 +- 후속 작업: 사용자별 token 제한 enforcement, 요청 실행 로그와 Usage Ledger 기반, 운영 리포트, audit retention, 가격 baseline/ROI 비교, 품질 기반 routing/fallback 고도화 +- 확인 필요: 없음 diff --git a/agent-roadmap/sdd/operational-observability-provider-management/usage-token-log-ops-mvp/SDD.md b/agent-roadmap/sdd/operational-observability-provider-management/usage-token-log-ops-mvp/SDD.md new file mode 100644 index 0000000..7c7b777 --- /dev/null +++ b/agent-roadmap/sdd/operational-observability-provider-management/usage-token-log-ops-mvp/SDD.md @@ -0,0 +1,132 @@ +# SDD: 사용자별 OpenAI-compatible 토큰 측정 MVP + +## 위치 + +- Milestone: [usage-token-log-ops-mvp](../../../phase/operational-observability-provider-management/milestones/usage-token-log-ops-mvp.md) +- Phase: [PHASE.md](../../../phase/operational-observability-provider-management/PHASE.md) + +## 상태 + +[승인됨] + +## SDD 잠금 + +- 상태: 해제 +- 사용자 리뷰: 없음 +- 잠금 항목: + - 없음 + +## 문제 / 비목표 + +- 문제: IOP dev-corp 환경에서 OpenAI-compatible 호출이 어떤 외부 principal id 또는 내부 alias의 토큰 사용량으로 귀속되는지 빠르게 측정할 기준이 필요하다. Control Plane 대시보드나 request-level ledger를 먼저 만들면 ROI 검증보다 제품 표면 구현이 커지므로, 1차는 Edge가 IOP bearer token 기반 principal mapping을 판별하고 Prometheus metric을 emit해 Grafana에서 집계하는 방향으로 고정한다. +- 비목표: + - OpenAI-compatible request/response body 확장 + - `metadata.user` 기반 사용자 식별 + - 사용자 CRUD, tenant/org 모델, RBAC, SSO/OIDC 같은 완성된 사용자/테넌트 관리 + - provider token 또는 provider identity의 사용자-facing 노출 + - Control Plane/Client dashboard 구현 + - request-level ledger 저장소, Loki 연동, 요청별 상세 감사 조회 + - 사용자별 token 제한 warn/reject enforcement + - Claude Opus, Gemma4 가격 baseline과 ROI 금액 산출 + - provider가 보고하지 않는 reasoning token 추정 + +## Source of Truth + +| 영역 | 기준 | 메모 | +|------|------|------| +| Roadmap | [usage-token-log-ops-mvp](../../../phase/operational-observability-provider-management/milestones/usage-token-log-ops-mvp.md) | 1차 측정 범위, 제외 범위, 기능 Task 기준 | +| Contract | [openai-compatible-api.md](../../../../agent-contract/outer/openai-compatible-api.md) | 외부 호출 표면은 `Authorization: Bearer `과 OpenAI-compatible 표준 field를 따른다 | +| Code | `apps/edge/internal/openai`, `apps/edge/internal/service`, `packages/go/observability`, `packages/go/config`, `configs/edge.yaml`, `agent-ops/skills/project/**` | principal-token alias 판별, usage 관측, metric emit, 운영 설정, project skill 기준 | +| External Provider | OpenAI-compatible provider usage payload | provider-reported usage가 있으면 token metric 원본으로 사용한다 | +| User Decision | 없음 | 사용자 결정은 채팅에서 해소되었고 추가 USER_REVIEW는 없다 | + +## State Machine + +| 상태 | 진입 조건 | 다음 상태 | 근거 | +|------|-----------|-----------|------| +| request_authenticated | OpenAI-compatible 요청이 `Authorization: Bearer `으로 인증된다 | user_resolved 또는 metering_unattributed | Edge OpenAI auth 경계 | +| user_resolved | IOP token이 운영 매핑에서 `token_ref`, `principal_ref`, 내부 alias로 해석된다 | request_observed | Edge-owned token alias mapping | +| metering_unattributed | token 매핑이 없거나 auth가 비활성인 dev 경로다 | request_observed | raw token 없이 `unknown` 또는 configured fallback alias | +| request_observed | 요청이 provider tunnel 또는 normalized runtime path로 dispatch된다 | usage_observed 또는 request_terminal | Edge dispatch/run handle | +| usage_observed | provider/runtime usage frame 또는 complete usage가 관측된다 | metric_emitted | `Usage`/provider usage payload | +| request_terminal | usage 없이 complete/error/cancelled가 관측된다 | metric_emitted | status-only request count, unavailable usage source | +| metric_emitted | 낮은 cardinality label로 Prometheus counter가 증가한다 | 없음 | Prometheus scrape와 Grafana query | + +## Interface Contract + +- 계약 원문: [openai-compatible-api.md](../../../../agent-contract/outer/openai-compatible-api.md) +- 입력: + - `Authorization: Bearer `: IOP 사용자 식별의 유일한 OpenAI-compatible 호출 입력이다. + - `principal_ref`: 별도 사용자/테넌트 프로젝트 또는 dev-corp 운영 id와 연결될 참조값이다. + - `principal_alias`: 운영자가 Grafana에서 볼 내부 alias다. + - `token_ref`: raw token을 저장하지 않고 metric label에 쓸 안정 식별자다. + - `usage.input_tokens`: provider-reported input token count다. + - `usage.output_tokens`: provider-reported output token count다. + - `usage.reasoning_tokens`: provider가 별도 보고할 때만 사용하는 reasoning token count다. + - `usage.cached_input_tokens`: provider가 별도 보고할 때만 사용하는 cached input token count다. +- 출력: + - `iop_openai_requests_total`: OpenAI-compatible request count counter 후보. + - `iop_openai_usage_tokens_total`: token usage counter 후보. + - `iop_openai_reasoning_observed_total`: reasoning stream/content 관측 counter 후보. + - `iop_openai_reasoning_chars_total`: reasoning text character count counter 후보. + - project skill output: raw IOP token은 operator에게 1회 보여주고, tracked 파일에는 token reference 또는 hash와 `principal_ref`/alias만 남긴다. +- 금지: + - pure `passthrough` response body에 IOP usage field 또는 sideband event를 섞지 않는다. + - `metadata.user`를 사용자 식별 source로 쓰지 않는다. + - raw token, provider token, raw prompt, raw response, request_id, session_id를 Prometheus label로 넣지 않는다. + - provider가 별도 보고하지 않은 reasoning token을 token metric으로 추정하지 않는다. + +## Acceptance Scenarios + +| ID | Milestone Task | Given | When | Then | +|----|----------------|-------|------|------| +| S01 | `identity-scope` | OpenAI-compatible 요청이 bearer token으로 들어온다 | Edge가 metric context를 만든다 | 사용자 식별은 IOP token 운영 매핑에서만 파생되고 `metadata.user`는 사용되지 않는다 | +| S02 | `token-alias` | token alias 설정 또는 secret reference가 있다 | Edge가 요청을 인증/관측한다 | raw token 없이 `principal_ref`, 내부 alias, `token_ref`가 metric label 후보로 계산된다 | +| S03 | `token-issue-skill` | 운영자가 `principal_ref` 또는 내부 id를 등록한다 | 프로젝트용 skill이 IOP token을 발급한다 | raw token은 1회 전달되고 tracked 파일에는 token reference 또는 hash와 principal 매핑만 남는다 | +| S04 | `openai-scope` | CLI, A2A, local-only adapter 요청이 있다 | 1차 metering 범위를 적용한다 | OpenAI-compatible 호출만 MVP metric 대상이고 다른 표면은 후속으로 남는다 | +| S05 | `passthrough-safe` | provider route가 pure `passthrough`로 응답한다 | usage를 관측한다 | provider response body는 유지되고 metric만 Edge 내부에서 emit된다 | +| S06 | `token-breakdown` | provider가 input/output/reasoning/cached usage를 일부 또는 전부 보고한다 | usage metric을 emit한다 | 보고된 token type만 counter에 반영되고 source는 provider-reported 또는 unavailable로 구분된다 | +| S07 | `reasoning-observed` | reasoning stream/content는 보이지만 reasoning token usage가 없다 | usage metric을 emit한다 | reasoning token은 추정하지 않고 관측 여부와 character count만 보조 metric으로 남긴다 | +| S08 | `prometheus-metrics` | metric label을 구성한다 | Prometheus counter를 증가시킨다 | request_id, session_id, raw token, raw payload 같은 high-cardinality/secret 값이 label에 없다 | +| S09 | `grafana-surface` | 운영자가 사용량을 조회한다 | Grafana query/dashboard를 사용한다 | Control Plane/Client dashboard 없이 Prometheus metric으로 사용자별 사용량을 볼 수 있다 | +| S10 | `limit-followup` | 사용자별 제한 구현을 검토한다 | 후속 Milestone을 계획한다 | 1차 metric label과 rollup은 daily/monthly 제한 정책에 재사용 가능한 형태로 남는다 | + +## Evidence Map + +| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 | +|----------|-------------------|------------------|---------------------------| +| S01 | bearer token 기반 alias 판별 테스트 또는 code evidence | `agent-task/m-usage-token-log-ops-mvp/...` | `Roadmap Completion`에 `identity-scope`와 S01 충족 근거 | +| S02 | raw token 미저장, principal_ref/internal alias/token_ref 산출 테스트 또는 설정 예시 | `agent-task/m-usage-token-log-ops-mvp/...` | `Roadmap Completion`에 `token-alias`와 S02 충족 근거 | +| S03 | project skill의 token 생성, raw token 1회 출력, tracked 파일 raw secret 미저장 검증 | `agent-task/m-usage-token-log-ops-mvp/...` | `Roadmap Completion`에 `token-issue-skill`와 S03 충족 근거 | +| S04 | OpenAI-compatible route만 metric 대상인 테스트 또는 문서 근거 | `agent-task/m-usage-token-log-ops-mvp/...` | `Roadmap Completion`에 `openai-scope`와 S04 충족 근거 | +| S05 | passthrough body 불변성과 metric emit 검증 | `agent-task/m-usage-token-log-ops-mvp/...` | `Roadmap Completion`에 `passthrough-safe`와 S05 충족 근거 | +| S06 | input/output/reasoning/cached token type별 metric 테스트 | `agent-task/m-usage-token-log-ops-mvp/...` | `Roadmap Completion`에 `token-breakdown`와 S06 충족 근거 | +| S07 | reasoning token 미보고 시 token 추정 없음과 보조 metric 검증 | `agent-task/m-usage-token-log-ops-mvp/...` | `Roadmap Completion`에 `reasoning-observed`와 S07 충족 근거 | +| S08 | Prometheus label allowlist 또는 cardinality/secret 제외 테스트 | `agent-task/m-usage-token-log-ops-mvp/...` | `Roadmap Completion`에 `prometheus-metrics`와 S08 충족 근거 | +| S09 | Grafana query/dashboard 예시 또는 Prometheus scrape evidence | `agent-task/m-usage-token-log-ops-mvp/...` | `Roadmap Completion`에 `grafana-surface`와 S09 충족 근거 | +| S10 | 제한 enforcement가 후속 범위로 남고 metric 재사용 기준이 문서화됨 | `agent-task/m-usage-token-log-ops-mvp/...` | `Roadmap Completion`에 `limit-followup`와 S10 충족 근거 | + +## Cross-repo Dependencies + +- 없음 + +## Drift Check + +- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다. +- [x] Evidence Map이 code-review/complete.log에서 검증 가능하다. +- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다. +- [x] 사용자 리뷰가 필요한 항목은 `USER_REVIEW.md`에만 남겼다. + +## 사용자 리뷰 이력 + +- 없음 + +## 작업 컨텍스트 + +- 표준선: OpenAI-compatible 호출 주체 식별은 `Authorization: Bearer `의 IOP token을 Edge-owned 운영 매핑으로 `principal_ref`와 내부 alias에 연결한다. +- 표준선: IOP는 사용자/테넌트 source of truth가 아니며, `principal_ref`는 별도 사용자/테넌트 프로젝트 또는 dev-corp 운영 id에 대한 foreign-key-like 참조값이다. +- 표준선: project skill은 `principal_ref` 또는 내부 id를 받아 raw IOP token을 생성하되 raw token은 tracked 파일이나 metric/log에 남기지 않는다. +- 표준선: Prometheus는 숫자 집계 표면이고 Grafana가 1차 조회 표면이다. request-level 상세 감사는 후속 ledger/Loki 축에서 다룬다. +- 표준선: pure `passthrough` response body는 provider-original 경계를 유지하고, usage metric은 Edge 내부 관측으로만 emit한다. +- 표준선: reasoning token은 provider-reported 값만 token으로 인정한다. reasoning text 기반 tokenizer 추정은 하지 않는다. +- 후속 SDD: [request-execution-log-usage-ledger-foundation SDD](../request-execution-log-usage-ledger-foundation/SDD.md) diff --git a/agent-task/m-usage-token-log-ops-mvp/01_identity_alias_auth/CODE_REVIEW-cloud-G05.md b/agent-task/m-usage-token-log-ops-mvp/01_identity_alias_auth/CODE_REVIEW-cloud-G05.md new file mode 100644 index 0000000..64edd2b --- /dev/null +++ b/agent-task/m-usage-token-log-ops-mvp/01_identity_alias_auth/CODE_REVIEW-cloud-G05.md @@ -0,0 +1,127 @@ + + +# Code Review Reference - USAGE_ID + +> **[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 `구현 체크리스트`; 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 by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-07-09 +task=m-usage-token-log-ops-mvp/01_identity_alias_auth, plan=0, tag=USAGE_ID + +## 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: + - `identity-scope`: OpenAI-compatible 호출 주체는 IOP bearer token 운영 매핑으로 판별한다. + - `token-alias`: `token_ref -> principal_ref/internal_alias` 매핑을 raw token 없이 관리한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G05.md` -> `code_review_cloud_G05_N.log`, `PLAN-cloud-G05.md` -> `plan_cloud_G05_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-usage-token-log-ops-mvp/01_identity_alias_auth/`로 이동한다. +4. PASS complete.log에는 `Roadmap Completion` 섹션을 포함하고 completed task ids로 `identity-scope`, `token-alias`를 적는다. +5. PASS이고 task group이 `m-usage-token-log-ops-mvp`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정이나 `update-roadmap` 직접 호출은 런타임 책임이다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [USAGE_ID-1] Principal Token Config | [ ] | +| [USAGE_ID-2] Auth Principal Context | [ ] | +| [USAGE_ID-3] Dispatch Metadata Propagation | [ ] | + +## 구현 체크리스트 + +- [ ] `packages/go/config`에 raw token 없는 OpenAI principal token mapping schema와 validation을 추가한다. +- [ ] `configs/edge.yaml`에 secret 없는 예시와 운영 주석을 추가한다. +- [ ] `apps/edge/internal/openai` auth를 hash 기반 principal mapping 우선, legacy bearer fallback으로 재구성하고 request context metadata를 만든다. +- [ ] Chat Completions, Responses, provider tunnel dispatch metadata에 `principal_ref`, `principal_alias`, `token_ref`, source를 추가하고 `metadata.user`를 사용하지 않는 test를 추가한다. +- [ ] `go test -count=1 ./packages/go/config ./apps/edge/internal/openai`와 `go test -count=1 ./apps/edge/...`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] active `CODE_REVIEW-*-G??.md`와 `PLAN-*-G??.md`를 `.log`로 아카이브한다. +- [ ] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리를 archive로 이동한다. +- [ ] PASS이고 task group이 `m-usage-token-log-ops-mvp`이면 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- raw bearer token이 config/docs/log/metric label에 남지 않는지 확인한다. +- `metadata.user`가 사용자 식별 source로 쓰이지 않는지 확인한다. +- legacy `openai.bearer_token` auth가 깨지지 않았는지 확인한다. +- principal metadata가 normalized run과 provider tunnel 양쪽에 들어가는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +### USAGE_ID-1 중간 검증 +```bash +$ go test -count=1 ./packages/go/config +(output) +``` + +### USAGE_ID-2/3 중간 검증 +```bash +$ go test -count=1 ./apps/edge/internal/openai -run 'Principal|Metadata|TestRoutes' +(output) +``` + +### 최종 검증 +```bash +$ go test -count=1 ./packages/go/config ./apps/edge/internal/openai +(output) +$ go test -count=1 ./apps/edge/... +(output) +$ git diff --check +(output) +``` + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** diff --git a/agent-task/m-usage-token-log-ops-mvp/01_identity_alias_auth/PLAN-cloud-G05.md b/agent-task/m-usage-token-log-ops-mvp/01_identity_alias_auth/PLAN-cloud-G05.md new file mode 100644 index 0000000..fb92afc --- /dev/null +++ b/agent-task/m-usage-token-log-ops-mvp/01_identity_alias_auth/PLAN-cloud-G05.md @@ -0,0 +1,215 @@ + + +# Plan - USAGE_ID + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 마지막 단계다. 구현 후 active 파일을 유지하고 리뷰 준비 상태로 보고한다. 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청`을 채우고 멈춘다. 직접 사용자에게 질문하거나 `USER_REVIEW.md`, `complete.log`, archive log를 만들지 않는다. 환경/secret/service 차단과 검증 증거 공백은 일반 후속 이슈로 기록한다. + +## 배경 + +현재 OpenAI-compatible auth는 단일 `openai.bearer_token` 문자열 일치만 수행한다. 이번 Milestone은 `metadata.user`가 아니라 IOP bearer token 운영 매핑에서 `principal_ref`, 내부 alias, `token_ref`를 파생해야 한다. 이 plan은 raw token을 저장하지 않는 config schema와 request context propagation을 먼저 만든다. + +## 사용자 리뷰 요청 흐름 + +구현 중 직접 사용자 프롬프트를 띄우지 않는다. 선택된 Milestone lock decision만 active review stub의 `사용자 리뷰 요청` 섹션에 기록하며, code-review가 검증 후 실제 `USER_REVIEW.md` 작성 여부를 결정한다. + +## 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: + - `identity-scope`: OpenAI-compatible 호출 주체는 IOP bearer token 운영 매핑으로 판별한다. + - `token-alias`: `token_ref -> principal_ref/internal_alias` 매핑을 raw token 없이 관리한다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-roadmap/phase/operational-observability-provider-management/PHASE.md` +- `agent-roadmap/phase/operational-observability-provider-management/milestones/usage-token-log-ops-mvp.md` +- `agent-roadmap/sdd/operational-observability-provider-management/usage-token-log-ops-mvp/SDD.md` +- `agent-contract/outer/openai-compatible-api.md` +- `agent-contract/inner/edge-config-runtime-refresh.md` +- `agent-spec/input/openai-compatible-surface.md` +- `agent-spec/runtime/provider-pool-config-refresh.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` +- `packages/go/config/config.go` +- `packages/go/config/config_test.go` +- `configs/edge.yaml` +- `apps/edge/internal/openai/routes.go` +- `apps/edge/internal/openai/server.go` +- `apps/edge/internal/openai/chat_handler.go` +- `apps/edge/internal/openai/responses_handler.go` +- `apps/edge/internal/openai/server_test.go` + +### SDD 기준 + +- SDD: `agent-roadmap/sdd/operational-observability-provider-management/usage-token-log-ops-mvp/SDD.md`, 상태 `[승인됨]`, SDD 잠금 `해제`. +- 대상 Acceptance Scenario: S01(`identity-scope`), S02(`token-alias`). +- Evidence Map: S01은 bearer token 기반 alias 판별 test/code evidence, S02는 raw token 미저장과 `principal_ref`/alias/`token_ref` 산출 test/config evidence를 요구한다. +- 구현 checklist는 S01/S02에서 역산해 config schema, constant-time hash match, request context propagation, no-`metadata.user` test로 제한한다. + +### 테스트 환경 규칙 + +- test_env: `local`. +- 읽은 규칙: `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/platform-common-smoke.md`. +- 적용 명령: `go test -count=1 ./packages/go/config ./apps/edge/internal/openai`, `go test -count=1 ./apps/edge/...`, `git diff --check`. +- 외부 provider/remote runner는 사용하지 않는다. full-cycle Edge/Node 구동은 이 plan의 auth/config 단위 PASS 조건이 아니라 02 usage metric plan의 runtime 검증에서 다룬다. + +### 테스트 커버리지 공백 + +- config: `TestLoadEdge_OpenAIDefaults`와 `TestLoadEdge_OpenAIOverride`가 legacy bearer token만 검증한다. `principal_tokens` load/validation test가 없다. +- auth: `TestRoutesRequireBearerTokenWhenConfigured`는 raw `BearerToken`만 검증한다. hash 기반 token mapping과 principal context test가 없다. +- dispatch metadata: chat/responses/tunnel request에 principal context가 들어가는 test가 없다. + +### 심볼 참조 + +- renamed/removed symbol: none. +- 새 helper 후보: `openAIPrincipal`, `principalFromRequest`, `principalContextFromRequest`, `principalMetadata`. + +### 분할 판단 + +- split policy를 먼저 적용했다. +- shared task group: `m-usage-token-log-ops-mvp`. +- sibling subtasks: + - `01_identity_alias_auth`: 독립 선행 작업. config/auth/principal context. + - `02+01_usage_metrics`: 01의 principal labels가 필요하다. predecessor `01`은 아직 `complete.log` 없음. + - `03+02_grafana_rollup_docs`: 02의 metric names/labels가 필요하다. predecessor `02`는 아직 `complete.log` 없음. +- 이 plan은 단일 ownership boundary가 config/auth/openai input surface에 한정되고, proto/metric emit은 후속 subtask로 분리되어 단일 plan이 적절하다. + +### 범위 결정 근거 + +- 제외: Prometheus collectors, proto `Usage` 확장, Node adapter usage parsing, Grafana docs. 모두 02/03에서 처리한다. +- 제외: 사용자 CRUD/tenant/org/RBAC, token limit enforcement, `metadata.user` support. Milestone 범위 제외와 SDD 금지사항이다. +- 유지: legacy `openai.bearer_token`은 backward compatibility auth로 유지하되 principal context는 `unknown` 또는 empty로 둔다. + +### 빌드 등급 + +- `cloud-G05`: config schema와 auth/security 경계를 함께 바꾸며 외부 API auth 동작에 영향이 있다. 범위는 작지만 잘못 구현하면 secret/raw token 노출이나 auth regression이 생긴다. + +## 구현 체크리스트 + +- [ ] `packages/go/config`에 raw token 없는 OpenAI principal token mapping schema와 validation을 추가한다. +- [ ] `configs/edge.yaml`에 secret 없는 예시와 운영 주석을 추가한다. +- [ ] `apps/edge/internal/openai` auth를 hash 기반 principal mapping 우선, legacy bearer fallback으로 재구성하고 request context metadata를 만든다. +- [ ] Chat Completions, Responses, provider tunnel dispatch metadata에 `principal_ref`, `principal_alias`, `token_ref`, source를 추가하고 `metadata.user`를 사용하지 않는 test를 추가한다. +- [ ] `go test -count=1 ./packages/go/config ./apps/edge/internal/openai`와 `go test -count=1 ./apps/edge/...`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [USAGE_ID-1] Principal Token Config + +문제: [config.go](/config/workspace/iop/packages/go/config/config.go:376)의 `EdgeOpenAIConf`는 raw `BearerToken`만 가진다. [LoadEdge](/config/workspace/iop/packages/go/config/config.go:609)는 OpenAI token alias mapping을 검증하지 않는다. + +해결 방법: + +```go +// before packages/go/config/config.go:376 +type EdgeOpenAIConf struct { + BearerToken string `mapstructure:"bearer_token" yaml:"bearer_token"` +} + +// after +type OpenAIPrincipalTokenConf struct { + TokenRef string `mapstructure:"token_ref" yaml:"token_ref"` + TokenHashSHA256 string `mapstructure:"token_hash_sha256" yaml:"token_hash_sha256"` + PrincipalRef string `mapstructure:"principal_ref" yaml:"principal_ref"` + PrincipalAlias string `mapstructure:"principal_alias" yaml:"principal_alias,omitempty"` +} +type EdgeOpenAIConf struct { + BearerToken string `mapstructure:"bearer_token" yaml:"bearer_token"` + PrincipalTokens []OpenAIPrincipalTokenConf `mapstructure:"principal_tokens" yaml:"principal_tokens,omitempty"` +} +``` + +수정 파일 및 체크리스트: +- [ ] `packages/go/config/config.go`: struct 추가, `validateOpenAIPrincipalTokens` 추가, `LoadEdge`에서 호출. +- [ ] `packages/go/config/config_test.go`: valid mapping, duplicate `token_ref`, empty principal, non-hex/non-64 hash, raw-looking token field 없음 테스트. +- [ ] `configs/edge.yaml`: `principal_tokens` 예시 주석 추가. raw token 값은 넣지 않는다. + +테스트 작성: +- `TestLoadEdge_OpenAIPrincipalTokens`와 `TestLoadEdge_OpenAIPrincipalTokensRejectInvalid` 추가. + +중간 검증: + +```bash +go test -count=1 ./packages/go/config +``` + +### [USAGE_ID-2] Auth Principal Context + +문제: [routes.go](/config/workspace/iop/apps/edge/internal/openai/routes.go:20)는 `openai.bearer_token` 문자열만 비교하고, 주체 정보를 request context에 남기지 않는다. + +해결 방법: +- `principal.go`를 추가해 Authorization bearer 추출, SHA-256 hash 계산, constant-time hash compare, request context 저장 helper를 둔다. +- `principal_tokens`가 있으면 hash mapping으로 인증한다. 없으면 legacy `BearerToken`을 유지한다. +- raw token, provider token, prompt/response를 logger/metadata에 넣지 않는다. + +수정 파일 및 체크리스트: +- [ ] `apps/edge/internal/openai/principal.go`: principal type/context/hash matching. +- [ ] `apps/edge/internal/openai/routes.go`: `withAuth`가 `principalFromBearer`를 호출하고 context를 주입. +- [ ] `apps/edge/internal/openai/identity_metering_test.go`: valid hash, missing/wrong token, legacy fallback, healthz no-auth 테스트. + +테스트 작성: +- `TestRoutesResolvePrincipalTokenMapping`. +- `TestRoutesDoNotUseMetadataUserForPrincipal`. + +중간 검증: + +```bash +go test -count=1 ./apps/edge/internal/openai -run 'TestRoutes' +``` + +### [USAGE_ID-3] Dispatch Metadata Propagation + +문제: [chat_handler.go](/config/workspace/iop/apps/edge/internal/openai/chat_handler.go:42)와 [responses_handler.go](/config/workspace/iop/apps/edge/internal/openai/responses_handler.go:50)는 caller metadata만 파싱한다. principal context가 SubmitRun/SubmitProviderTunnel metadata에 없다. + +해결 방법: +- `principalMetadata(r.Context())`를 `runMeta`에 merge한다. +- metadata keys는 `iop_principal_ref`, `iop_principal_alias`, `iop_token_ref`, `iop_principal_source`로 둔다. +- `metadata.user`는 무시하거나 caller metadata로만 보존하지 말고 사용자 식별 source로 쓰지 않는 test를 둔다. + +수정 파일 및 체크리스트: +- [ ] `apps/edge/internal/openai/chat_handler.go`: normalized/tunnel metadata에 principal metadata 추가. +- [ ] `apps/edge/internal/openai/responses_handler.go`: SubmitRun metadata에 principal metadata 추가. +- [ ] `apps/edge/internal/openai/identity_metering_test.go`: chat, responses, provider tunnel metadata 검증. + +테스트 작성: +- `TestChatCompletionsAddsPrincipalMetadataFromBearerToken`. +- `TestProviderTunnelAddsPrincipalMetadataFromBearerToken`. +- `TestResponsesAddsPrincipalMetadataFromBearerToken`. + +중간 검증: + +```bash +go test -count=1 ./apps/edge/internal/openai -run 'Principal|Metadata' +``` + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `packages/go/config/config.go` | USAGE_ID-1 | +| `packages/go/config/config_test.go` | USAGE_ID-1 | +| `configs/edge.yaml` | USAGE_ID-1 | +| `apps/edge/internal/openai/principal.go` | USAGE_ID-2, USAGE_ID-3 | +| `apps/edge/internal/openai/routes.go` | USAGE_ID-2 | +| `apps/edge/internal/openai/chat_handler.go` | USAGE_ID-3 | +| `apps/edge/internal/openai/responses_handler.go` | USAGE_ID-3 | +| `apps/edge/internal/openai/identity_metering_test.go` | USAGE_ID-2, USAGE_ID-3 | + +## 최종 검증 + +```bash +go test -count=1 ./packages/go/config ./apps/edge/internal/openai +go test -count=1 ./apps/edge/... +git diff --check +``` + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-usage-token-log-ops-mvp/02+01_usage_metrics/CODE_REVIEW-cloud-G07.md b/agent-task/m-usage-token-log-ops-mvp/02+01_usage_metrics/CODE_REVIEW-cloud-G07.md new file mode 100644 index 0000000..2ad3c87 --- /dev/null +++ b/agent-task/m-usage-token-log-ops-mvp/02+01_usage_metrics/CODE_REVIEW-cloud-G07.md @@ -0,0 +1,143 @@ + + +# Code Review Reference - USAGE_METRIC + +> **[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 `구현 체크리스트`; 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 by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-07-09 +task=m-usage-token-log-ops-mvp/02+01_usage_metrics, plan=0, tag=USAGE_METRIC + +## 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 + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-usage-token-log-ops-mvp/02+01_usage_metrics/`로 이동한다. +4. PASS complete.log에는 `Roadmap Completion` 섹션을 포함하고 completed task ids로 `openai-scope`, `passthrough-safe`, `token-breakdown`, `reasoning-observed`, `prometheus-metrics`를 적는다. +5. PASS이고 task group이 `m-usage-token-log-ops-mvp`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정이나 `update-roadmap` 직접 호출은 런타임 책임이다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [USAGE_METRIC-1] Runtime Usage Schema | [ ] | +| [USAGE_METRIC-2] Provider Usage Parsing | [ ] | +| [USAGE_METRIC-3] Prometheus Metric Emit | [ ] | + +## 구현 체크리스트 + +- [ ] 선행 `01_identity_alias_auth`의 active 또는 same-task-group archive `complete.log`와 PASS evidence를 확인한다. +- [ ] Runtime usage schema를 `reasoning_tokens`, `cached_input_tokens`까지 확장하고 Go generated code를 갱신한다. +- [ ] Node adapters와 Edge usage mapping이 input/output/reasoning/cached_input을 보존하도록 수정한다. +- [ ] pure passthrough response body를 바꾸지 않는 provider usage 관측 경로를 추가한다. +- [ ] OpenAI-compatible 경로 전용 Prometheus collectors와 label allowlist test를 추가한다. +- [ ] reasoning token 미보고 시 token 추정 없이 보조 metric만 emit하는 test를 추가한다. +- [ ] 최종 검증 명령을 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] active `CODE_REVIEW-*-G??.md`와 `PLAN-*-G??.md`를 `.log`로 아카이브한다. +- [ ] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리를 archive로 이동한다. +- [ ] PASS이고 task group이 `m-usage-token-log-ops-mvp`이면 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- pure `passthrough` response body에 IOP field/event가 추가되지 않았는지 확인한다. +- `iop_openai_usage_tokens_total`에 provider-reported token type만 들어가고 reasoning token 추정이 없는지 확인한다. +- Prometheus labels에 raw token, provider token, request_id, session_id, raw prompt/response가 없는지 확인한다. +- metric emit이 OpenAI-compatible 경로에만 연결되고 A2A/CLI/local-only usage로 확장되지 않았는지 확인한다. +- proto/generated Go 변경과 adapter mapping이 서로 맞는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +### USAGE_METRIC-1 중간 검증 +```bash +$ make proto +(output) +$ go test -count=1 ./apps/node/internal/node ./apps/edge/internal/openai +(output) +``` + +### USAGE_METRIC-2 중간 검증 +```bash +$ go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm ./apps/edge/internal/openai -run 'Usage|Passthrough|Reasoning|Cached' +(output) +``` + +### USAGE_METRIC-3 중간 검증 +```bash +$ go test -count=1 ./apps/edge/internal/openai -run 'Metric|Usage|Reasoning' +(output) +``` + +### 최종 검증 +```bash +$ make proto +(output) +$ go test -count=1 ./apps/edge/internal/openai ./apps/node/internal/node ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm +(output) +$ go test -count=1 ./apps/edge/... ./apps/node/... +(output) +$ git diff --check +(output) +``` + +--- + +> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** diff --git a/agent-task/m-usage-token-log-ops-mvp/02+01_usage_metrics/PLAN-cloud-G07.md b/agent-task/m-usage-token-log-ops-mvp/02+01_usage_metrics/PLAN-cloud-G07.md new file mode 100644 index 0000000..33fa3e9 --- /dev/null +++ b/agent-task/m-usage-token-log-ops-mvp/02+01_usage_metrics/PLAN-cloud-G07.md @@ -0,0 +1,254 @@ + + +# Plan - USAGE_METRIC + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 마지막 단계다. 구현 후 active 파일을 유지하고 리뷰 준비 상태로 보고한다. 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청`을 채우고 멈춘다. 직접 사용자에게 질문하거나 `USER_REVIEW.md`, `complete.log`, archive log를 만들지 않는다. 환경/secret/service 차단과 검증 증거 공백은 일반 후속 이슈로 기록한다. + +## 배경 + +이 plan은 OpenAI-compatible 사용량 측정의 핵심 구현이다. 선행 `01_identity_alias_auth`가 request context에 `principal_ref`, `principal_alias`, `token_ref`, `edge_id` 후보를 전달할 수 있게 만든 뒤, 이 plan은 OpenAI-compatible 경로만 metric 대상으로 삼고, pure passthrough body를 바꾸지 않으며, provider/runtime usage를 Prometheus counter로 노출한다. + +## 선행 의존성 + +- 선행 subtask: `agent-task/m-usage-token-log-ops-mvp/01_identity_alias_auth` +- 시작 조건: active 경로 또는 같은 task group archive 후보의 선행 `complete.log`가 존재하고, `identity-scope`/`token-alias`가 code-review PASS로 완료되어 있어야 한다. +- 선행 완료 전에는 이 plan을 구현하지 말고 active 파일만 유지한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 직접 사용자 프롬프트를 띄우지 않는다. 선택된 Milestone lock decision만 active review stub의 `사용자 리뷰 요청` 섹션에 기록하며, code-review가 검증 후 실제 `USER_REVIEW.md` 작성 여부를 결정한다. + +## 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`, provider-reported `reasoning`, provider-reported `cached_input` token type을 구분한다. + - `reasoning-observed`: provider가 reasoning token을 보고하지 않으면 token 추정 없이 관측 여부와 character count만 남긴다. + - `prometheus-metrics`: low-cardinality Prometheus counter와 label allowlist를 구현한다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-roadmap/phase/operational-observability-provider-management/PHASE.md` +- `agent-roadmap/phase/operational-observability-provider-management/milestones/usage-token-log-ops-mvp.md` +- `agent-roadmap/sdd/operational-observability-provider-management/usage-token-log-ops-mvp/SDD.md` +- `agent-contract/outer/openai-compatible-api.md` +- `agent-contract/inner/edge-node-runtime-wire.md` +- `agent-contract/inner/edge-config-runtime-refresh.md` +- `agent-spec/input/openai-compatible-surface.md` +- `agent-spec/runtime/edge-node-execution.md` +- `agent-spec/runtime/provider-pool-config-refresh.md` +- `agent-ops/rules/project/domain/edge/rules.md` +- `agent-ops/rules/project/domain/node/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/node-smoke.md` +- `agent-test/local/platform-common-smoke.md` +- `proto/iop/runtime.proto` +- `packages/go/observability/observability.go` +- `apps/edge/internal/input/manager.go` +- `apps/edge/internal/bootstrap/runtime.go` +- `apps/edge/internal/openai/server.go` +- `apps/edge/internal/openai/chat_handler.go` +- `apps/edge/internal/openai/responses_handler.go` +- `apps/edge/internal/openai/run_result.go` +- `apps/edge/internal/openai/stream.go` +- `apps/edge/internal/openai/types.go` +- `apps/edge/internal/openai/server_test.go` +- `apps/node/internal/runtime/types.go` +- `apps/node/internal/node/node.go` +- `apps/node/internal/adapters/openai_compat/openai_compat.go` +- `apps/node/internal/adapters/vllm/vllm.go` + +### SDD 기준 + +- SDD: `agent-roadmap/sdd/operational-observability-provider-management/usage-token-log-ops-mvp/SDD.md`, 상태 `[승인됨]`, SDD 잠금 `해제`. +- 대상 Acceptance Scenario: S04(`openai-scope`), S05(`passthrough-safe`), S06(`token-breakdown`), S07(`reasoning-observed`), S08(`prometheus-metrics`). +- Evidence Map은 OpenAI-compatible route만 metric 대상인 근거, passthrough body 불변성, token type별 metric test, reasoning token 미추정 test, Prometheus label allowlist/secret 제외 test를 요구한다. + +### 테스트 환경 규칙 + +- test_env: `local`. +- 읽은 규칙: `agent-test/local/rules.md`, `agent-test/local/edge-smoke.md`, `agent-test/local/node-smoke.md`, `agent-test/local/platform-common-smoke.md`. +- proto 변경 시 `make proto`를 실행한다. +- 적용 명령: + +```bash +make proto +go test -count=1 ./apps/edge/internal/openai ./apps/node/internal/node ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm +go test -count=1 ./apps/edge/... ./apps/node/... +git diff --check +``` + +### 테스트 커버리지 공백 + +- `proto/iop/runtime.proto`의 `Usage`는 현재 `input_tokens`, `output_tokens`만 가진다. +- Edge `openAIUsage`와 Node `UsageStats`도 input/output만 보존한다. +- provider tunnel pure passthrough는 raw bytes를 유지하지만 provider body/SSE usage를 metric으로 관측하는 test가 없다. +- OpenAI-compatible provider normal path의 `prompt_tokens_details.cached_tokens`, `completion_tokens_details.reasoning_tokens` parsing test가 없다. +- custom Prometheus collector, label allowlist, secret/high-cardinality exclusion test가 없다. + +### 심볼 참조 + +- 변경 대상 후보: + - [runtime.proto](/config/workspace/iop/proto/iop/runtime.proto:113) + - [types.go](/config/workspace/iop/apps/node/internal/runtime/types.go:71) + - [node.go](/config/workspace/iop/apps/node/internal/node/node.go:325) + - [run_result.go](/config/workspace/iop/apps/edge/internal/openai/run_result.go:77) + - [stream.go](/config/workspace/iop/apps/edge/internal/openai/stream.go:551) + - [openai_compat.go](/config/workspace/iop/apps/node/internal/adapters/openai_compat/openai_compat.go:349) + - [vllm.go](/config/workspace/iop/apps/node/internal/adapters/vllm/vllm.go:318) + - [observability.go](/config/workspace/iop/packages/go/observability/observability.go:1) + - [manager.go](/config/workspace/iop/apps/edge/internal/input/manager.go:21) +- 새 helper 후보: `usageObservation`, `providerUsageFromJSON`, `providerUsageFromSSEData`, `openAIUsageMetrics`, `emitUsageMetrics`, `usageLabelValues`. + +### 분할 판단 + +- split policy를 먼저 적용했다. +- shared task group: `m-usage-token-log-ops-mvp`. +- sibling subtasks: + - `01_identity_alias_auth`: predecessor. 아직 active plan 상태면 이 plan 구현을 시작하지 않는다. + - `02+01_usage_metrics`: 이 plan. proto/usage observation/metrics ownership. + - `03+02_grafana_rollup_docs`: metric names/labels가 확정된 뒤 docs/query를 작성한다. +- 이 plan은 여러 파일을 건드리지만 하나의 runtime behavior slice다. Grafana 문서와 limit follow-up은 03으로 분리해 code/proto/metric 구현에 집중한다. + +### 범위 결정 근거 + +- 포함: OpenAI-compatible `/v1/chat/completions`와 `/v1/responses` normalized runtime 경로, provider tunnel chat passthrough 경로의 usage 관측. +- 포함: `edge.id`를 OpenAI server에 전달해 `edge_id` label 후보로 사용한다. `apps/edge/internal/input/manager.go`에서 `cfg.Edge.ID`를 `openai.Server`에 주입하는 방식이 가장 작다. +- 포함: Prometheus counter label allowlist. 후보 labels는 `edge_id`, `principal_ref`, `principal_alias`, `token_ref`, `model_group`, `endpoint`, `response_mode`, `token_type`, `usage_source`, `status`다. +- 제외: CLI usage status, A2A, local-only adapter usage, Control Plane/Client dashboard, request-level ledger, Loki, token limit enforcement, 가격/ROI 금액 산출. +- 제외: pure passthrough response body 변경. usage frame이나 IOP sideband event를 provider body에 섞지 않는다. + +### 빌드 등급 + +- `cloud-G07`: proto, Edge/Node runtime, OpenAI passthrough stream, Prometheus metric을 함께 바꾸는 cross-runtime 작업이다. 외부 API body는 유지해야 하지만 runtime wire와 metric schema 영향이 크다. + +## 구현 체크리스트 + +- [ ] 선행 `01_identity_alias_auth`의 active 또는 same-task-group archive `complete.log`와 PASS evidence를 확인한다. +- [ ] Runtime usage schema를 `reasoning_tokens`, `cached_input_tokens`까지 확장하고 Go generated code를 갱신한다. +- [ ] Node adapters와 Edge usage mapping이 input/output/reasoning/cached_input을 보존하도록 수정한다. +- [ ] pure passthrough response body를 바꾸지 않는 provider usage 관측 경로를 추가한다. +- [ ] OpenAI-compatible 경로 전용 Prometheus collectors와 label allowlist test를 추가한다. +- [ ] reasoning token 미보고 시 token 추정 없이 보조 metric만 emit하는 test를 추가한다. +- [ ] 최종 검증 명령을 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [USAGE_METRIC-1] Runtime Usage Schema + +문제: `Usage`/`UsageStats`/OpenAI usage response type은 input/output만 보존한다. provider가 보고하는 cached input 또는 reasoning token을 metric까지 전달할 수 없다. + +수정 파일 및 체크리스트: +- [ ] `proto/iop/runtime.proto`: `Usage`에 `reasoning_tokens`, `cached_input_tokens`를 optional-compatible scalar field로 추가한다. +- [ ] `proto/gen/iop/runtime.pb.go`: `make proto`로 재생성한다. +- [ ] `apps/node/internal/runtime/types.go`: `UsageStats`에 `ReasoningTokens`, `CachedInputTokens` 추가. +- [ ] `apps/node/internal/node/node.go`: session sink와 provider tunnel sink의 proto mapping을 확장한다. +- [ ] `apps/edge/internal/openai/run_result.go`, `types.go`: OpenAI-compatible response usage 구조와 mapping을 확장한다. + +테스트 작성: +- runtime usage mapping 단위 test가 있으면 확장하고, 없으면 가장 가까운 existing Edge/Node package test에 regression case를 추가한다. + +중간 검증: + +```bash +make proto +go test -count=1 ./apps/node/internal/node ./apps/edge/internal/openai +``` + +### [USAGE_METRIC-2] Provider Usage Parsing + +문제: OpenAI-compatible provider payload에는 `usage.prompt_tokens_details.cached_tokens`와 `usage.completion_tokens_details.reasoning_tokens`가 있을 수 있지만 현재 adapter와 Edge stream assembler는 이를 metric용 관측값으로 보존하지 않는다. + +해결 방법: +- normalized runtime path에서 `openai_compat`와 `vllm` adapter가 provider usage details를 `UsageStats`에 채운다. +- raw provider tunnel path에서는 response body를 변경하지 않고 Edge stream/non-stream processing 중 provider usage JSON/SSE data를 관측해 내부 `usageObservation`으로 저장한다. +- `usage_source`는 provider가 해당 token type을 보고한 경우 `provider_reported`, 값이 없거나 알 수 없으면 `unavailable`로 둔다. + +수정 파일 및 체크리스트: +- [ ] `apps/node/internal/adapters/openai_compat/openai_compat.go`: stream complete event와 non-stream response usage details parsing. +- [ ] `apps/node/internal/adapters/vllm/vllm.go`: OpenAI-compatible usage details parsing. +- [ ] `apps/edge/internal/openai/stream.go`: pure passthrough body를 그대로 relay하면서 provider usage를 관측하는 parser/assembler 확장. +- [ ] `apps/edge/internal/openai/server_test.go`: streaming/non-stream passthrough body byte-equivalence와 usage observation test. + +테스트 작성: +- `TestProviderTunnelPassthroughPreservesBodyAndObservesUsage`. +- `TestProviderUsageParsesReasoningAndCachedInputTokens`. + +중간 검증: + +```bash +go test -count=1 ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm ./apps/edge/internal/openai -run 'Usage|Passthrough|Reasoning|Cached' +``` + +### [USAGE_METRIC-3] Prometheus Metric Emit + +문제: metrics endpoint는 promhttp만 제공하고 OpenAI-compatible usage/request counters가 없다. + +해결 방법: +- `apps/edge/internal/openai/usage_metrics.go`를 추가해 package-level Prometheus collectors를 등록한다. +- `NewServer`에 `edge_id`를 주입할 수 있는 setter 또는 option을 추가하고, `apps/edge/internal/input/manager.go`에서 `cfg.Edge.ID`를 전달한다. +- request terminal status별 `iop_openai_requests_total`을 증가시킨다. +- usage 관측값이 있으면 token type별 `iop_openai_usage_tokens_total`을 증가시킨다. +- reasoning text/content를 관측했지만 reasoning token이 provider-reported가 아니면 `iop_openai_reasoning_observed_total`, `iop_openai_reasoning_chars_total`만 증가시키고 token counter에는 reasoning 추정치를 넣지 않는다. +- label allowlist에서 `request_id`, `session_id`, raw token, raw prompt/response, provider token/provider identity를 제외한다. + +수정 파일 및 체크리스트: +- [ ] `apps/edge/internal/openai/usage_metrics.go`: collectors, label builder, emit helpers. +- [ ] `apps/edge/internal/openai/server.go`: `edge_id`/metric emitter wiring. +- [ ] `apps/edge/internal/input/manager.go`: `cfg.Edge.ID` 전달. +- [ ] `apps/edge/internal/openai/chat_handler.go`, `responses_handler.go`, `stream.go`: success/error/cancel terminal status와 usage emit 호출. +- [ ] `apps/edge/internal/openai/server_test.go` 또는 `usage_metrics_test.go`: collector output and label allowlist. + +테스트 작성: +- `TestOpenAIUsageMetricsLabelsExcludeSecretsAndHighCardinality`. +- `TestOpenAIUsageMetricsCountTokenTypes`. +- `TestOpenAIReasoningObservedDoesNotEstimateTokens`. +- `TestOpenAIScopeExcludesA2AAndNonOpenAISurfaces`는 code evidence 또는 package-level docs/test로 대체 가능하다. 이 plan에서 A2A 코드를 건드리지 않음을 명시한다. + +중간 검증: + +```bash +go test -count=1 ./apps/edge/internal/openai -run 'Metric|Usage|Reasoning' +``` + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `proto/iop/runtime.proto` | USAGE_METRIC-1 | +| `proto/gen/iop/runtime.pb.go` | USAGE_METRIC-1 | +| `apps/node/internal/runtime/types.go` | USAGE_METRIC-1 | +| `apps/node/internal/node/node.go` | USAGE_METRIC-1 | +| `apps/node/internal/adapters/openai_compat/openai_compat.go` | USAGE_METRIC-2 | +| `apps/node/internal/adapters/vllm/vllm.go` | USAGE_METRIC-2 | +| `apps/edge/internal/openai/run_result.go` | USAGE_METRIC-1 | +| `apps/edge/internal/openai/types.go` | USAGE_METRIC-1 | +| `apps/edge/internal/openai/stream.go` | USAGE_METRIC-2, USAGE_METRIC-3 | +| `apps/edge/internal/openai/server.go` | USAGE_METRIC-3 | +| `apps/edge/internal/input/manager.go` | USAGE_METRIC-3 | +| `apps/edge/internal/openai/chat_handler.go` | USAGE_METRIC-3 | +| `apps/edge/internal/openai/responses_handler.go` | USAGE_METRIC-3 | +| `apps/edge/internal/openai/usage_metrics.go` | USAGE_METRIC-3 | +| `apps/edge/internal/openai/*_test.go` | USAGE_METRIC-2, USAGE_METRIC-3 | +| `apps/node/internal/adapters/*/*_test.go` | USAGE_METRIC-2 | + +## 최종 검증 + +```bash +make proto +go test -count=1 ./apps/edge/internal/openai ./apps/node/internal/node ./apps/node/internal/adapters/openai_compat ./apps/node/internal/adapters/vllm +go test -count=1 ./apps/edge/... ./apps/node/... +git diff --check +``` + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-usage-token-log-ops-mvp/03+02_grafana_rollup_docs/CODE_REVIEW-local-G03.md b/agent-task/m-usage-token-log-ops-mvp/03+02_grafana_rollup_docs/CODE_REVIEW-local-G03.md new file mode 100644 index 0000000..bc63331 --- /dev/null +++ b/agent-task/m-usage-token-log-ops-mvp/03+02_grafana_rollup_docs/CODE_REVIEW-local-G03.md @@ -0,0 +1,135 @@ + + +# Code Review Reference - USAGE_GRAFANA + +> **[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 `구현 체크리스트`; 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 by a selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record only Milestone lock decisions in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-07-09 +task=m-usage-token-log-ops-mvp/03+02_grafana_rollup_docs, plan=0, tag=USAGE_GRAFANA + +## 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: + - `grafana-surface`: Grafana query를 1차 조회 표면으로 둔다. + - `limit-followup`: 제한 enforcement는 후속 범위로 남기고 daily/monthly rollup 기준을 문서화한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 문서와 대조하고, `검증 결과` 섹션의 출력이 문서와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-local-G03.md` -> `code_review_local_G03_N.log`, `PLAN-local-G03.md` -> `plan_local_G03_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-usage-token-log-ops-mvp/03+02_grafana_rollup_docs/`로 이동한다. +4. PASS complete.log에는 `Roadmap Completion` 섹션을 포함하고 completed task ids로 `grafana-surface`, `limit-followup`을 적는다. +5. PASS이고 task group이 `m-usage-token-log-ops-mvp`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정이나 `update-roadmap` 직접 호출은 런타임 책임이다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [USAGE_GRAFANA-1] Grafana Operator Guide | [ ] | +| [USAGE_GRAFANA-2] Limit Follow-up Rollup | [ ] | + +## 구현 체크리스트 + +- [ ] 선행 `02+01_usage_metrics`의 active 또는 same-task-group archive `complete.log`와 PASS evidence를 확인한다. +- [ ] 02에서 확정된 metric 이름과 label allowlist를 실제 코드에서 확인한다. +- [ ] `docs/openai-usage-grafana.md`를 작성한다. +- [ ] Grafana query examples가 principal/token/model/token type별 사용량을 보여주도록 작성한다. +- [ ] daily/monthly rollup과 limit follow-up 경계를 명확히 문서화한다. +- [ ] 검증 명령을 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] active `CODE_REVIEW-*-G??.md`와 `PLAN-*-G??.md`를 `.log`로 아카이브한다. +- [ ] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리를 archive로 이동한다. +- [ ] PASS이고 task group이 `m-usage-token-log-ops-mvp`이면 완료 이벤트 메타데이터를 보고하고 roadmap을 직접 수정하지 않는다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 연결 대상: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- 문서 query가 실제 구현된 metric 이름과 label만 쓰는지 확인한다. +- Control Plane/Client dashboard 구현을 범위에 끌어들이지 않았는지 확인한다. +- limit enforcement가 구현된 것처럼 표현하지 않고 후속 범위로 남겼는지 확인한다. +- raw token, provider token, request_id, session_id, raw payload를 Grafana label/group-by로 권장하지 않는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +### USAGE_GRAFANA-1 중간 검증 +```bash +$ rg --fixed-strings "iop_openai_usage_tokens_total" docs/openai-usage-grafana.md +(output) +$ rg --fixed-strings "iop_openai_reasoning_observed_total" docs/openai-usage-grafana.md +(output) +``` + +### USAGE_GRAFANA-2 중간 검증 +```bash +$ rg --fixed-strings "daily" docs/openai-usage-grafana.md +(output) +$ rg --fixed-strings "monthly" docs/openai-usage-grafana.md +(output) +$ rg --fixed-strings "후속" docs/openai-usage-grafana.md +(output) +``` + +### 최종 검증 +```bash +$ rg --fixed-strings "iop_openai_usage_tokens_total" docs/openai-usage-grafana.md +(output) +$ rg --fixed-strings "daily" docs/openai-usage-grafana.md +(output) +$ rg --fixed-strings "monthly" docs/openai-usage-grafana.md +(output) +$ git diff --check +(output) +``` + +--- + +> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** diff --git a/agent-task/m-usage-token-log-ops-mvp/03+02_grafana_rollup_docs/PLAN-local-G03.md b/agent-task/m-usage-token-log-ops-mvp/03+02_grafana_rollup_docs/PLAN-local-G03.md new file mode 100644 index 0000000..c874406 --- /dev/null +++ b/agent-task/m-usage-token-log-ops-mvp/03+02_grafana_rollup_docs/PLAN-local-G03.md @@ -0,0 +1,149 @@ + + +# Plan - USAGE_GRAFANA + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 마지막 단계다. 구현 후 active 파일을 유지하고 리뷰 준비 상태로 보고한다. 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청`을 채우고 멈춘다. 직접 사용자에게 질문하거나 `USER_REVIEW.md`, `complete.log`, archive log를 만들지 않는다. 환경/secret/service 차단과 검증 증거 공백은 일반 후속 이슈로 기록한다. + +## 배경 + +이 plan은 Prometheus metric 구현이 끝난 뒤 운영자가 Grafana에서 사용자별 OpenAI-compatible token 사용량을 볼 수 있게 하는 문서 표면이다. Control Plane/Client dashboard, request-level ledger, 사용자별 제한 enforcement는 만들지 않고, 02에서 확정한 metric 이름과 label을 기준으로 PromQL query와 daily/monthly rollup 기준을 문서화한다. + +## 선행 의존성 + +- 선행 subtask: `agent-task/m-usage-token-log-ops-mvp/02+01_usage_metrics` +- 시작 조건: active 경로 또는 같은 task group archive 후보의 선행 `complete.log`가 존재하고, metric 이름/label이 code-review PASS로 확정되어 있어야 한다. +- 선행 완료 전에는 이 plan을 구현하지 말고 active 파일만 유지한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 직접 사용자 프롬프트를 띄우지 않는다. 선택된 Milestone lock decision만 active review stub의 `사용자 리뷰 요청` 섹션에 기록하며, code-review가 검증 후 실제 `USER_REVIEW.md` 작성 여부를 결정한다. + +## 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: + - `grafana-surface`: Control Plane/Client dashboard 없이 Grafana query를 1차 조회 표면으로 둔다. + - `limit-followup`: 사용자별 token 제한은 후속 범위로 남기고, daily/monthly warn/reject 정책에 재사용 가능한 rollup 기준을 문서화한다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-roadmap/phase/operational-observability-provider-management/PHASE.md` +- `agent-roadmap/phase/operational-observability-provider-management/milestones/usage-token-log-ops-mvp.md` +- `agent-roadmap/sdd/operational-observability-provider-management/usage-token-log-ops-mvp/SDD.md` +- `agent-contract/outer/openai-compatible-api.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` +- `docs/openai-compatible-api-contract.md` +- `docs/edge-local-dev-guide.md` + +### SDD 기준 + +- SDD: `agent-roadmap/sdd/operational-observability-provider-management/usage-token-log-ops-mvp/SDD.md`, 상태 `[승인됨]`, SDD 잠금 `해제`. +- 대상 Acceptance Scenario: S09(`grafana-surface`), S10(`limit-followup`). +- Evidence Map은 Grafana query/dashboard 예시 또는 Prometheus scrape evidence, 제한 enforcement가 후속 범위로 남고 metric 재사용 기준이 문서화된 근거를 요구한다. + +### 테스트 환경 규칙 + +- test_env: `local`. +- 읽은 규칙: `agent-test/local/rules.md`. +- 이 plan은 docs/query 중심이므로 Go test는 코드 변경이 있을 때만 실행한다. +- 적용 명령: + +```bash +rg --fixed-strings "iop_openai_usage_tokens_total" docs/openai-usage-grafana.md +rg --fixed-strings "daily" docs/openai-usage-grafana.md +rg --fixed-strings "monthly" docs/openai-usage-grafana.md +git diff --check +``` + +### 테스트 커버리지 공백 + +- `docs/`에는 OpenAI-compatible API contract와 Edge local dev guide만 있고 usage/Grafana operator guide가 없다. +- metric 이름과 labels가 02에서 확정되기 전에는 정확한 PromQL을 작성할 수 없다. +- limit enforcement는 Milestone 범위 제외이므로 문서에는 재사용 가능한 rollup 기준과 후속 경계만 남겨야 한다. + +### 분할 판단 + +- split policy를 먼저 적용했다. +- shared task group: `m-usage-token-log-ops-mvp`. +- predecessor `02+01_usage_metrics`가 metric schema를 확정해야 이 docs plan이 의미 있다. +- 이 plan은 문서/운영 query만 다루므로 `local-G03`이 적절하다. + +### 범위 결정 근거 + +- 포함: `docs/openai-usage-grafana.md` 신규 작성. +- 포함: principal alias/token ref별 token type breakdown, reasoning 보조 metric, request status query, daily/monthly rollup query, label cardinality/secret 금지 기준. +- 포함: limit follow-up 기준. warn/reject enforcement는 후속 Milestone으로 남기되, `principal_ref`/`token_ref`/`model_group`/`token_type` rollup이 재사용 가능하다는 점을 문서화한다. +- 제외: Grafana JSON dashboard import file, Control Plane/Client dashboard, billing/cost model, user CRUD, token limit enforcement. + +### 빌드 등급 + +- `local-G03`: code-free docs/query 작업이다. 단, 02 metric schema와 직접 맞아야 하므로 선행 완료 확인이 필요하다. + +## 구현 체크리스트 + +- [ ] 선행 `02+01_usage_metrics`의 active 또는 same-task-group archive `complete.log`와 PASS evidence를 확인한다. +- [ ] 02에서 확정된 metric 이름과 label allowlist를 실제 코드에서 확인한다. +- [ ] `docs/openai-usage-grafana.md`를 작성한다. +- [ ] Grafana query examples가 principal/token/model/token type별 사용량을 보여주도록 작성한다. +- [ ] daily/monthly rollup과 limit follow-up 경계를 명확히 문서화한다. +- [ ] 검증 명령을 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +### [USAGE_GRAFANA-1] Grafana Operator Guide + +문제: 운영자가 Prometheus metric을 직접 알더라도 어떤 query를 Grafana panel로 써야 하는지, 어떤 label을 group by 해야 하는지 문서화되어 있지 않다. + +수정 파일 및 체크리스트: +- [ ] `docs/openai-usage-grafana.md`: 목적, metric 목록, label allowlist, 금지 label, PromQL examples, dashboard panel 후보를 작성한다. +- [ ] `docs/openai-usage-grafana.md`: `principal_alias`, `principal_ref`, `token_ref`, `model_group`, `endpoint`, `response_mode`, `token_type`, `usage_source`, `status` 기준 query를 포함한다. +- [ ] `docs/openai-usage-grafana.md`: provider가 reasoning token을 보고하지 않는 경우 token 추정 없이 `iop_openai_reasoning_observed_total`/`iop_openai_reasoning_chars_total`를 보는 기준을 포함한다. + +검증: + +```bash +rg --fixed-strings "iop_openai_usage_tokens_total" docs/openai-usage-grafana.md +rg --fixed-strings "iop_openai_reasoning_observed_total" docs/openai-usage-grafana.md +``` + +### [USAGE_GRAFANA-2] Limit Follow-up Rollup + +문제: 사용자별 token 제한은 이 Milestone에서 구현하지 않지만, 1차 metric이 후속 daily/monthly warn/reject 정책에 재사용 가능한 형태인지 문서 evidence가 필요하다. + +수정 파일 및 체크리스트: +- [ ] `docs/openai-usage-grafana.md`: daily/monthly token rollup PromQL examples를 작성한다. +- [ ] `docs/openai-usage-grafana.md`: warn/reject enforcement는 후속 Milestone이며, 현재 문서는 조회와 운영 판단 기준까지만 제공한다고 명시한다. +- [ ] `docs/openai-usage-grafana.md`: billing/price/ROI amount 산출과 request-level ledger는 범위 제외라고 명시한다. + +검증: + +```bash +rg --fixed-strings "daily" docs/openai-usage-grafana.md +rg --fixed-strings "monthly" docs/openai-usage-grafana.md +rg --fixed-strings "후속" docs/openai-usage-grafana.md +``` + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `docs/openai-usage-grafana.md` | USAGE_GRAFANA-1, USAGE_GRAFANA-2 | + +## 최종 검증 + +```bash +rg --fixed-strings "iop_openai_usage_tokens_total" docs/openai-usage-grafana.md +rg --fixed-strings "daily" docs/openai-usage-grafana.md +rg --fixed-strings "monthly" docs/openai-usage-grafana.md +git diff --check +``` + +모든 문서 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.