# FinOps Query Gateway Design

本文档定义一个新的集中式多云账单查询入口，面向 OpenClaw、Claude Code、Codex、OpenCode 等 AI agent 工具提供统一的查询、权限控制、审计与结果汇总能力。

该方案替代“先做一个完整 Web Agent 平台”的交付路径，优先交付一个可被现有 AI agent 生态直接消费的中心服务。

---

## Changelog

| Version | Date | Changes |
|---------|------|---------|
| v0.2 | 2026-03-12 | Keep existing AWS mart names (`mart_cost_monthly`, `mart_cost_summary`) for compatibility. Add cloud-specific marts only for GCP and Alibaba in MVP. |
| v0.1 | 2026-03-12 | Initial draft — 物理 mart 按云独立，网关统一语义；GCP 不复制，按 region 分片查询并在网关层合并 |

---

## 1. 背景与目标

### 1.1 当前问题

现有多云账单分析存在以下现实约束：

1. **原始多云账单表过于复杂**
   - 原始 AWS / GCP / Alibaba 账单 schema 差异大，直接暴露给 LLM 会显著降低 SQL 质量和稳定性。
   - 原始表字段命名、分区方式、折扣语义均不一致，不适合作为统一用户态查询面。

2. **并非所有用户都有 BigQuery 查询权限**
   - 团队中许多人已经在使用 OpenClaw、Claude Code、Codex、OpenCode 等 agent 工具。
   - 如果继续让用户直接连 BigQuery，会带来权限分发、口径漂移、审计缺失的问题。

3. **BigQuery location 不能跨区直接查询**
   - AWS / Alibaba 账单分析层位于 `us-west2`
   - GCP 计费原始数据位于 `US`
   - BigQuery 不支持一条 SQL 同时查询 `US` 与 `us-west2`

4. **生产账单数据敏感**
   - 开发/测试账户与生产账户不能共享同一访问策略
   - 需要在执行层强制做行级授权，而不是仅依赖 prompt 约束

### 1.2 目标

建设一个 **FinOps Query Gateway**，满足以下目标：

| 目标 | 描述 |
|------|------|
| 集中查询入口 | 所有 AI agent 通过统一 API / CLI / Skill 查询账单数据 |
| 统一数据口径 | 用户态查询只基于 dbt mart，不允许直接查 raw billing tables |
| 多云统一体验 | 支持 AWS / GCP / Alibaba 的统一问答与汇总分析 |
| 执行层权限控制 | 生产账户默认受限，开发/测试默认公开，按用户授权精确控制 |
| 完整审计 | 记录提问、执行计划、扫描量、结果行数、调用来源、执行结果 |
| 一天内可启动实现 | 先交付中心服务能力，不依赖先做完整 Web 平台 |

### 1.3 非目标

第一版明确不做以下内容：

- 不做新的 Web UI
- 不做飞书 Bot
- 不做订阅推送
- 不做异常检测
- 不做任意自由 SQL 执行
- 不支持 2026-01-01 之前的数据查询
- 不支持 finance / invoice / tax / refund / credit 对账视角

---

## 2. 关键设计决策

### 2.1 物理 mart 按云独立

**决策**：每个云单独构建 staging / intermediate / mart，不做跨云统一物理 mart。

**原因**：

- 各云账单语义差异明显，强行做一个统一物理表会让 schema 越来越别扭
- GCP 位于 `US`，AWS / Alibaba 位于 `us-west2`，无法用单条 BigQuery SQL 做真正跨云 union
- 每云独立后更容易测试、回填、修复与增量构建

### 2.2 网关统一语义

**决策**：虽然物理 mart 独立，但网关对外暴露一套统一语义模型。

**结果**：

- AI agent 不需要理解三套物理表
- 网关根据请求自动选择对应云的物理 mart
- 跨云比较通过“多条 region-local SQL + 服务端合并”实现

### 2.3 GCP 不复制到 us-west2

**决策**：第一版不复制 GCP 原始数据到 `us-west2`。

**原因**：

- 复制链路属于额外的数据工程与运维负担
- 第一版目标是统一查询体验，不是统一 BigQuery 物理执行面
- 用网关按 region 分片执行后再合并，可以更快落地

### 2.4 只允许查询 dbt mart

**决策**：所有用户态查询都只允许命中 approved dbt marts / views。

**结果**：

- 不暴露 raw billing datasets
- 避免 LLM 直接面向原始多云复杂 schema
- 查询成本和数据语义都更稳定

### 2.5 统一时间边界

**决策**：所有查询必须限制在 `2026-01-01` 及之后。

**原因**：

- 降低扫描量
- 限定第一版的口径范围
- 避免各云历史数据接入状态不一致带来的歧义

### 2.6 权限模型固定为 public + restricted

**决策**：

- 开发/测试账户默认 `public`
- 生产账户默认 `restricted`
- `restricted` 账户只能由 admin 或显式授权用户访问

**原因**：

- 第一版不引入 `team` 等更复杂层级
- 先建立最小但严格的执行层权限模型

---

## 3. 数据层设计

### 3.1 物理数据集与 Region

物理数据集布局固定为两套：

| Dataset | Location | 内容 |
|---------|----------|------|
| `gcp-rd-research-dev.finops_cloud_billing` | `us-west2` | AWS + Alibaba marts |
| `gcp-rd-research-dev.finops_cloud_billing_us` | `US` | GCP marts |

### 3.2 物理 mart 命名

物理表命名固定为：

#### us-west2

- `mart_cost_monthly` (AWS, existing)
- `mart_cost_summary` (AWS, existing)
- `mart_alibaba_cost_monthly`
- `mart_alibaba_cost_summary`

#### US

- `mart_gcp_cost_monthly`
- `mart_gcp_cost_summary`

### 3.3 dbt 模型组织

每个云单独建模：

#### AWS

- `stg_aws_cost`
- `int_aws_billing_ledger`
- `int_aws_billing`
- `mart_cost_monthly`
- `mart_cost_summary`

#### GCP

- `stg_gcp_cost`
- `int_gcp_billing_ledger`
- `int_gcp_billing`
- `mart_gcp_cost_monthly`
- `mart_gcp_cost_summary`

#### Alibaba

- `stg_alibaba_cost`
- `int_alibaba_billing_ledger`
- `int_alibaba_billing`
- `mart_alibaba_cost_monthly`
- `mart_alibaba_cost_summary`

### 3.4 dbt Target 设计

dbt profile 新增两个 target：

| Target | Dataset | Location |
|--------|---------|----------|
| `prod_usw2` | `finops_cloud_billing` | `us-west2` |
| `prod_us` | `finops_cloud_billing_us` | `US` |

构建规则固定为：

- `account_mapping` seed 在两个 target 都要构建一份
- AWS + Alibaba 只在 `prod_usw2` 构建
- GCP 只在 `prod_us` 构建
- 不允许任何 model 跨 region 引用 source 或 ref

### 3.5 统一语义字段

虽然每个云的物理 mart 独立，但每个云的 mart 都必须输出以下统一字段：

| 类别 | 字段 |
|------|------|
| 时间 | `billing_period_month` |
| 云标识 | `cloud_provider` |
| 账户 | `account_id`, `account_display_name`, `account_purpose`, `access_level`, `cost_center`, `account_owner` |
| 核心维度 | `region`, `service`, `service_category` |
| 标签 | `tag_project` |
| 指标 | `unblended_cost_usd`, `usage_cost_usd`, `effective_cost_usd`, `sp_covered_usage_cost_usd` |

### 3.6 指标语义对齐

三云指标口径固定如下：

| 指标 | AWS | GCP | Alibaba |
|------|-----|-----|----------|
| `unblended_cost_usd` | 现有 usage 分析默认成本 | 用户态默认使用成本 | 用户态默认使用成本 |
| `usage_cost_usd` | usage 成本 | 与默认使用成本一致 | 与默认使用成本一致 |
| `effective_cost_usd` | SP/RI-aware effective cost | 第一版等于 `unblended_cost_usd` | 第一版等于 `unblended_cost_usd` |
| `sp_covered_usage_cost_usd` | 保留现有含义 | `0` | `0` |

### 3.7 账户映射与敏感性分级

`dbt/seeds/account_mapping.csv` 继续作为唯一的业务属性与权限属性来源。

固定要求：

- 所有 AWS / GCP / Alibaba 账户都必须有映射
- 生产账户默认 `access_level = restricted`
- 开发/测试账户默认 `access_level = public`
- `account_purpose` 只做业务分类，不直接充当授权条件

---

## 4. 网关架构设计

### 4.1 总体结构

```
AI Agent (OpenClaw / Claude Code / Codex / OpenCode)
  -> Skill (HTTP API) / CLI
    -> FinOps Query Gateway
      -> AuthN / AuthZ
      -> Semantic Planner
      -> SQL Compiler
      -> Dry-run & Budget Guard
      -> BigQuery Executor
      -> Cross-Cloud Merger
      -> Answer Renderer
      -> Audit Logger
```

### 4.2 核心职责

Gateway 统一负责：

1. 用户鉴权
2. 用户可访问账户解析
3. 自然语言转统一语义请求
4. 查询形态校验
5. 按云/region 生成 SQL
6. dry-run 与预算检查
7. BigQuery 执行
8. 跨云结果合并
9. 中文分析摘要生成
10. 审计日志落库

### 4.3 语义请求模型（SemanticQuery）

网关不对普通用户开放自由 SQL。  
所有自然语言问题都先转成受控的 `SemanticQuery`。

固定结构：

```ts
type SemanticQuery = {
  source: 'summary' | 'detail'
  time_grain: 'month'
  dimensions: string[]
  metrics: string[]
  filters: {
    date_from?: string
    date_to?: string
    clouds?: Array<'aws' | 'gcp' | 'alibaba'>
    account_ids?: string[]
    account_purpose?: string[]
    services?: string[]
    regions?: string[]
    tag_project?: string[]
  }
  sort?: Array<{
    field: string
    direction: 'asc' | 'desc'
  }>
  limit?: number
}
```

允许的维度固定为：

- `billing_period_month`
- `cloud_provider`
- `account_id`
- `account_display_name`
- `account_purpose`
- `cost_center`
- `account_owner`
- `region`
- `service`
- `service_category`
- `tag_project`

允许的指标固定为：

- `unblended_cost_usd`
- `usage_cost_usd`
- `effective_cost_usd`
- `sp_covered_usage_cost_usd`

### 4.4 物理查询编译规则

编译规则固定为：

1. 如果请求只使用 summary mart 支持的字段，优先走 `*_cost_summary`
2. 如果请求需要更细粒度字段，则走 `*_cost_monthly`
3. 所有查询都必须带 `billing_period_month >= DATE '2026-01-01'`
4. 所有查询都必须注入 `account_id IN UNNEST(@authorized_account_ids)`
5. 所有查询都必须绑定到单一 cloud 的单一 physical mart
6. 所有查询都必须是只读、单语句、标准 SQL

物理映射固定为：

| Cloud | Summary Mart | Detail Mart | Location |
|-------|--------------|-------------|----------|
| AWS | `finops_cloud_billing.mart_cost_summary` | `finops_cloud_billing.mart_cost_monthly` | `us-west2` |
| GCP | `finops_cloud_billing_us.mart_gcp_cost_summary` | `finops_cloud_billing_us.mart_gcp_cost_monthly` | `US` |
| Alibaba | `finops_cloud_billing.mart_alibaba_cost_summary` | `finops_cloud_billing.mart_alibaba_cost_monthly` | `us-west2` |

### 4.5 跨云查询执行方式

如果一个请求涉及多个云，Gateway 不会尝试生成跨 region 单 SQL，而是做分片执行：

1. 根据 `clouds` 拆成多个 cloud shard
2. 每个 shard 生成一条物理 SQL
3. 在该云所在 location 执行 dry-run
4. 预算校验通过后执行
5. 将所有 shard 结果拉回 Gateway
6. 在服务端按相同维度做二次聚合
7. 最后再做全局排序、Top N 与 limit

### 4.6 合并规则

跨云结果合并规则固定为：

- 相同维度组合视为同一组
- 所有数值指标按求和合并
- 如果请求包含多个云但未显式要求 `cloud_provider`，Gateway 自动补入 `cloud_provider`
- Top N 和排序必须基于合并后的全局结果，而不是各云局部结果

### 4.7 第一版不支持的查询

以下请求形态第一版固定拒绝：

- 任意自由 SQL
- 跨云 join
- 复杂窗口函数驱动的 ad-hoc 分析
- 资源级/实例级/财务级明细
- 2026-01-01 之前的数据
- 依赖 raw billing schema 的问题

统一错误结构：

```json
{
  "error": "unsupported_query_shape",
  "reason": "query requires cross-cloud join or unsupported grain",
  "rewrite_hint": "Try 'compare AWS, GCP, and Alibaba monthly cost by cloud'",
  "suggested_analysis_id": "cost_by_cloud_monthly"
}
```

---

## 5. API 设计

### 5.1 公开接口

固定公开以下接口：

- `POST /v1/query/ask`
- `POST /v1/query/analyses/{analysis_id}`
- `GET /v1/catalog/semantic-schema`
- `GET /v1/catalog/accounts`
- `GET /v1/me/queries`

### 5.2 管理员接口

- `POST /v1/admin/tokens`
- `DELETE /v1/admin/tokens/{id}`
- `POST /v1/admin/grants`
- `DELETE /v1/admin/grants/{id}`
- `GET /v1/admin/audit`

### 5.3 ask 接口请求体

```json
{
  "question": "比较 2026 年 2 月 AWS、GCP、Alibaba 的总成本",
  "mode": "execute",
  "client_type": "mcp",
  "client_name": "codex"
}
```

### 5.4 ask 接口响应体

```json
{
  "request_id": "qg_20260312_xxx",
  "semantic_query": {},
  "shards": [],
  "answer_markdown": "2026 年 2 月三云总成本如下...",
  "bytes_processed_total": "123456789",
  "row_count": 3,
  "rows": [],
  "truncated": false,
  "applied_policy": {
    "date_floor": "2026-01-01",
    "authorized_account_count": 42
  }
}
```

---

## 6. 认证、权限与审计

### 6.1 认证方式

第一版固定使用 **每用户独立 API token**。

原因：

- CLI / Skill 接入简单
- 审计主体清晰
- 不依赖先做 SSO 或 Web 登录流程

### 6.2 数据模型

数据库模型固定为：

#### `users`

- `id`
- `email`
- `name`
- `role` (`viewer | admin`)
- `status`

#### `api_tokens`

- `id`
- `user_id`
- `token_hash`
- `token_prefix`
- `status`
- `expires_at`
- `last_used_at`

#### `account_access_grants`

- `id`
- `user_id`
- `cloud_provider`
- `account_id`
- `granted_by`
- `expires_at`

#### `query_audit_logs`

- `id`
- `request_id`
- `user_id`
- `client_type`
- `client_name`
- `question`
- `semantic_query_json`
- `shard_plan_json`
- `generated_sql_json`
- `bytes_processed_total`
- `status`
- `error_code`
- `error_message`
- `row_count`
- `started_at`
- `finished_at`

### 6.3 授权规则

固定授权模型：

- `viewer`
  - 可见所有 `public`
  - 加上显式授权的 `restricted`
- `admin`
  - 可见全部账户

Gateway 每次请求都要先解析出本次可访问的 `(cloud_provider, account_id)` 集合，再编译查询。

### 6.4 审计规则

以下请求都必须写入 `query_audit_logs`：

- preview
- execute
- reject
- error

日志中必须能回放：

- 原始问题
- 语义请求
- 各 shard 执行计划
- dry-run 扫描量
- 执行状态
- 错误原因

---

## 7. 安全与预算策略

### 7.1 数据源 allowlist

用户态查询只允许以下物理 mart：

- `mart_cost_summary`
- `mart_cost_monthly`
- `mart_gcp_cost_summary`
- `mart_gcp_cost_monthly`
- `mart_alibaba_cost_summary`
- `mart_alibaba_cost_monthly`

明确禁止以下 raw datasets 出现在用户态执行计划中：

- `gcp_billing_v2`
- `aws_billing_no_payer`
- `alibaba_cloud`

### 7.2 预算策略

固定预算规则：

- 单 shard dry-run 超过 `10 GB` 拒绝执行
- 每用户每日扫描预算默认 `50 GB`
- 查询结果行数上限 `1000`

### 7.3 时间围栏

所有查询必须强制满足：

- `billing_period_month >= DATE '2026-01-01'`

如果用户问题涉及“去年”“2025”“历史全部”等请求，执行层统一拦截或重写。

---

## 8. CLI / Skill 集成

### 8.1 MCP (Removed)

> **Deprecated (2026-03-13)**: MCP server (`finops-mcp/`) has been removed. The MCP layer was a thin pass-through that converted MCP tool calls into Gateway HTTP requests, adding deployment complexity without business logic. AI agents now call the Gateway HTTP API directly via the Skill (`skills/cloudcost/SKILL.md`) or CLI (`finops-cli/`).

### 8.2 CLI

新增 `finops` CLI：

```bash
finops login --gateway-url ... --token ...
finops ask "比较 2026 年 2 月三云总成本" --execute
finops analyses run cost_by_cloud_monthly --month 2026-02
finops accounts
finops queries
```

### 8.3 Skill

新增 `cloudcost` skill，固定行为：

1. 优先调用 `run_analysis`
2. 模板不匹配时调用 `ask`
3. 永远不要直接访问 BigQuery
4. 永远不要尝试生成 raw-table SQL

---

## 9. 实施阶段

### Phase A: 数据底座补齐

1. 新增 GCP staging / intermediate / marts
2. 新增 Alibaba staging / intermediate / marts
3. 将现有 AWS mart 重命名为云专属命名
4. 配置 `prod_us` / `prod_usw2` 双 target
5. 更新 `account_mapping.csv`
   - 补齐 GCP / Alibaba 映射
   - 生产账户统一改为 `restricted`

### Phase B: Gateway 核心服务

1. 新建 `finops-query-gateway/`
2. 实现 token 鉴权与授权解析
3. 实现 `SemanticQuery` 生成与校验
4. 实现 per-cloud SQL planner
5. 实现 cross-cloud merger
6. 实现审计落库与预算控制

### Phase C: CLI / Skill

1. ~~新建 `finops-mcp`~~ (removed 2026-03-13)
2. 新建 `finops` CLI
3. 新建 `cloudcost` skill

### Phase D: 部署

`docker-compose.yml` 新增：

- `finops-query-gateway`
- `postgres`

继续复用现有 service account JSON 挂载方式访问 BigQuery。

---

## 10. 验收标准

### 10.1 dbt 层

- `prod_usw2` 中存在 `mart_aws_*` 与 `mart_alibaba_*`
- `prod_us` 中存在 `mart_gcp_*`
- 三云 summary mart schema 一致
- 三云 detail mart schema 一致

### 10.2 权限层

- 普通用户只能看到 `public`
- 未授权生产账户不会返回结果
- admin 可以看到全部 `restricted`

### 10.3 跨云执行层

- 请求 “比较 2026 年 2 月 AWS / GCP / Alibaba 总成本” 时：
  - 生成 3 个 cloud shard
  - 命中 2 个 region
  - 合并结果正确

- 请求 “列出上个月成本最高的 20 个账户” 时：
  - Top N 必须基于合并后的全局结果
  - 不能先在每个云做局部 limit 再拼接

### 10.4 安全与审计

- 所有请求都能在 `query_audit_logs` 回放
- 所有 raw-table 风格请求都会被拒绝
- 所有 2026-01-01 之前的请求都会被拒绝或收敛

### 10.5 客户端集成

- CLI 可以成功调用 gateway
- Skill (HTTP API) 可以成功调用 gateway
- 同一个 token 在 CLI 与 Skill 上都能得到一致结果

---

## 11. 结论

本方案选择：

- **物理 mart 按云独立**
- **网关统一语义**
- **GCP 不复制**
- **跨 region 分片执行，网关层合并**

这比“先做一个完整平台”更适合当前阶段，因为它能优先解决最重要的三个问题：

1. 让现有 AI agent 直接可用
2. 把 BigQuery 权限和审计集中到服务端
3. 在不引入高复杂度数据搬运工程的情况下支持多云统一查询体验
