# SDD: 요청 실행 로그와 Usage Ledger 기반

## 위치

- Milestone: [request-execution-log-usage-ledger-foundation](../../../phase/operational-observability-provider-management/milestones/request-execution-log-usage-ledger-foundation.md)
- Phase: [PHASE.md](../../../phase/operational-observability-provider-management/PHASE.md)

## 상태

[초안]

## SDD 잠금

- 상태: 잠금
- 사용자 리뷰: [USER_REVIEW.md](USER_REVIEW.md)
- 잠금 항목:
  - [ ] [D01] request ledger의 canonical 저장 책임을 Edge와 Control Plane 중 어디에 둘지 결정한다.
  - [ ] [D02] provider-reported, estimated, mixed, unavailable usage source를 운영 UI와 export에서 어떻게 표시할지 결정한다.
  - [ ] [D03] provider가 hidden think/reasoning token을 보고하지 않는 경우 표시 reasoning text 기반 추정치를 허용할지 결정한다.
  - [ ] [D04] full prompt/response, preview, metadata, error detail의 redaction과 retention 기본값을 결정한다.
  - [ ] [D05] 기존 runtime event 확장과 별도 request ledger/audit event schema 중 기본 구현 경계를 결정한다.
  - [ ] [D06] tool-call argument, provider raw chunk, parser error detail을 preview/hash/raw capture 중 어떤 수준으로 보관하고 어떻게 redaction할지 결정한다.

## 문제 / 비목표

- 문제: 현재 IOP는 OpenAI-compatible 요청의 device/provider dispatch, 시작/종료 시간, queue wait, token breakdown, status/error를 요청 단위로 재구성하기 어렵다. 운영자는 provider 효율, 사용자별 사용량, 문제 요청의 원인, prompt/response 보관 범위를 한 기록에서 확인할 수 있어야 한다. provider/tool-call bridge에서 native tool call이 구조화되었는지, text fallback이 합성되었는지, raw `<tool_call>` 텍스트가 새어 나왔는지도 사후 판별할 수 있어야 한다.
- 비목표:
  - billing, chargeback, 조직 IAM, 장기 retention 정책 구현
  - provider routing 알고리즘 변경
  - provider가 보고하지 않는 hidden reasoning token의 완전 정확한 복원
  - 품질 평가나 route recommendation 자동화

## Source of Truth

| 영역 | 기준 | 메모 |
|------|------|------|
| Roadmap | [request-execution-log-usage-ledger-foundation](../../../phase/operational-observability-provider-management/milestones/request-execution-log-usage-ledger-foundation.md) | Milestone 목표, 기능 Task, 잠금 항목 기준 |
| Code | `apps/edge/internal/openai`, `apps/edge/internal/service`, `apps/node/internal/node`, `apps/node/internal/adapters/openai_compat`, `apps/node/internal/adapters/vllm`, `apps/node/internal/adapters/ollama`, `proto/iop/runtime.proto` | OpenAI-compatible input, dispatch, runtime event, usage/tool-call propagation 구현 기준 |
| External Provider | OpenAI-compatible provider usage chunk | provider-reported token usage가 있으면 우선 사용하고, 없으면 estimated/unavailable로 표시 |
| User Decision | D01-D06 | 저장 책임, usage source 표시, reasoning token 추정, redaction/retention, schema 분리, tool-call raw capture/redaction 결정 필요 |

## State Machine

| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| accepted | Edge OpenAI-compatible 또는 native 실행 요청을 수신했다 | queued 또는 dispatched | request_id/run_id 생성, source metadata |
| queued | model group/provider capacity가 가득 차 요청이 대기한다 | admitted 또는 cancelled/error | queue admission event |
| admitted | scheduler가 provider slot을 예약했다 | dispatched 또는 error | provider_id/node_id/model selection |
| dispatched | Edge가 Node에 RunRequest를 보냈다 | provider_started 또는 error | RunRequest dispatch result |
| provider_started | Node adapter가 provider request를 시작했다 | first_token 또는 completed/error | Node runtime event 또는 adapter execution log |
| first_token | 첫 delta 또는 reasoning_delta가 관측되었다 | completed/error/cancelled | runtime stream event timestamp |
| tool_call_bridge_evaluated | provider stream 또는 CLI output에서 native tool_calls, text fallback, raw tool-call 후보를 관측했다 | completed/error/cancelled | tool-call source, synthesized/leaked flag, parser/fallback metadata |
| completed | provider/adapter가 complete event를 보냈다 | 없음 | complete event, usage, finish_reason |
| error | Edge, Node, provider, queue 중 하나가 실패했다 | 없음 | error event와 error detail |
| cancelled | 사용자 또는 runtime이 취소했다 | 없음 | cancel event |

## Interface Contract

- 계약 원문: 없음
- 입력:
  - `request_id`: 외부 요청 또는 Edge-generated 요청 correlation id
  - `run_id`: Node runtime 실행 correlation id
  - `metadata.user/session/workspace/source`: 사용자, session, workspace, 호출 표면 식별
  - `model` / `served_model`: 외부 model alias와 provider 실제 served model
  - `provider_id` / `node_id` / `device_id`: 선택된 실행 위치 식별
  - `usage`: input, cached input, think/reasoning, output, total token과 source 표시
  - `tool_call_trace`: native tool_calls 수, text_tool_fallback, synthesized_tool_calls, raw_tool_call_leaked, parse failure/retry/fallback 시도, redacted preview/hash 후보
- 출력:
  - 요청별 실행 ledger record
  - provider/device/model별 usage와 latency rollup 후보
  - provider/tool-call bridge 판정 summary
  - 운영 UI/CLI/export에서 조회 가능한 redacted request summary
- 금지:
  - provider가 보고하지 않은 token을 provider-reported처럼 표시하지 않는다.
  - hidden reasoning token을 표시 reasoning text 추정치와 혼동하지 않는다.
  - prompt/response 원문 보관 여부를 SDD 사용자 결정 없이 기본값으로 확정하지 않는다.
  - tool-call argument와 provider raw chunk 원문을 redaction/capture 결정 없이 기본 저장하지 않는다.

## Acceptance Scenarios

| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `event-lifecycle` | OpenAI-compatible 요청이 들어온다 | 요청이 queue, dispatch, provider, stream, complete 또는 error 경로를 지난다 | lifecycle별 timestamp 의미와 event source가 문서화되어 있다 |
| S02 | `identity-correlation` | 요청이 provider pool을 통해 특정 Node/provider/model로 라우팅된다 | 운영자가 run을 조회한다 | request_id, run_id, user/session/workspace/source, node/provider/device/model correlation 기준이 문서화되어 있다 |
| S03 | `token-usage` | provider가 usage를 보고하거나 보고하지 않는다 | complete event 또는 response usage를 구성한다 | provider-reported/estimated/mixed/unavailable source 정책과 token breakdown 필드가 문서화되어 있다 |
| S04 | `latency-metrics` | 요청이 대기, 실행, streaming 단계를 지난다 | 운영자가 latency를 비교한다 | queue wait, TTFT, provider duration, stream duration, total duration 후보가 문서화되어 있다 |
| S05 | `log-redaction` | 요청/응답/metadata/error detail이 ledger에 남는다 | 운영 UI 또는 export가 기록을 표시한다 | preview/redaction/retention 기본값 후보와 사용자 결정 항목이 분리되어 있다 |
| S06 | `storage-query` | Edge와 Control Plane이 모두 운영 기록 후보를 가질 수 있다 | 저장/조회 경계를 설계한다 | canonical owner와 조회/export 후보가 사용자 결정 항목으로 정리되어 있다 |
| S07 | `migration-plan` | 기존 zap log, runtime event, Control Plane operation history가 존재한다 | request ledger를 추가한다 | 병행 운용 또는 migration 전략 후보가 문서화되어 있다 |
| S08 | `tool-call-trace` | provider 또는 CLI route가 tool call을 native tool_calls, text fallback, raw text 중 하나로 반환한다 | Edge가 OpenAI-compatible 응답을 구성하거나 parser/fallback 실패를 만난다 | native/text/synthesized/leaked/parse-failure 판정 필드와 redaction/capture 기준이 문서화되어 있다 |

## Evidence Map

| Scenario | Required Evidence | `agent-task` 연결 | 완료 Evidence 기대 |
|----------|-------------------|------------------|---------------------------|
| S01 | Milestone 문서와 SDD에서 lifecycle 표와 event source 확인 | `agent-task/m-request-execution-log-usage-ledger-foundation/...` | `Roadmap Completion`에 `event-lifecycle`와 S01 충족 근거 |
| S02 | correlation field 목록과 provider/device/model identity mapping 확인 | `agent-task/m-request-execution-log-usage-ledger-foundation/...` | `Roadmap Completion`에 `identity-correlation`와 S02 충족 근거 |
| S03 | usage field/source 정책과 provider-reported/estimated 구분 확인 | `agent-task/m-request-execution-log-usage-ledger-foundation/...` | `Roadmap Completion`에 `token-usage`와 S03 충족 근거 |
| S04 | latency metric 후보와 timestamp 계산 기준 확인 | `agent-task/m-request-execution-log-usage-ledger-foundation/...` | `Roadmap Completion`에 `latency-metrics`와 S04 충족 근거 |
| S05 | redaction/retention 결정 항목과 기본 후보 확인 | `agent-task/m-request-execution-log-usage-ledger-foundation/...` | `Roadmap Completion`에 `log-redaction`와 S05 충족 근거 |
| S06 | canonical owner 사용자 리뷰 해결 또는 결정 기록 확인 | `agent-task/m-request-execution-log-usage-ledger-foundation/...` | `Roadmap Completion`에 `storage-query`와 S06 충족 근거 |
| S07 | 기존 로그/event와 새 ledger 병행 또는 migration 전략 확인 | `agent-task/m-request-execution-log-usage-ledger-foundation/...` | `Roadmap Completion`에 `migration-plan`와 S07 충족 근거 |
| S08 | tool-call bridge 판정 필드, parser/fallback 실패 기록, redaction/capture 기준 확인 | `agent-task/m-request-execution-log-usage-ledger-foundation/...` | `Roadmap Completion`에 `tool-call-trace`와 S08 충족 근거 |

## Cross-repo Dependencies

- 없음

## Drift Check

- [ ] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [ ] Evidence Map이 code-review/complete.log에서 검증 가능하다.
- [ ] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [ ] 사용자 리뷰가 필요한 항목은 [USER_REVIEW.md](USER_REVIEW.md)에만 남겼다.

## 사용자 리뷰 이력

- 없음

## 작업 컨텍스트

- 표준선: Edge는 runtime execution과 provider routing의 원본 이벤트를 가장 먼저 알고, Control Plane은 연결 view와 운영 조회/export 표면을 제공한다.
- 표준선: usage는 provider-reported 값을 우선하고, provider가 주지 않는 값은 estimated 또는 unavailable로 명시해 정확도와 추정을 분리한다.
- 표준선: tool-call 추적은 기본적으로 raw 원문 저장보다 `run_id` 기준 판정 필드, 길이, hash, 짧은 redacted preview를 우선하고, bounded raw capture는 명시적으로 켠 진단 모드로 제한한다.
- 후속 SDD: 없음
