# FinOps Query Gateway — Implementation Tasks

本任务文档面向多 agent 并行实施，基于 [FinOps Query Gateway Design](./finops-query-gateway-design.md) 拆解为可独立认领的工作流。

目标是交付一个可被现有 AI agent 工具直接消费的中心查询入口，支持：

- AWS / GCP / Alibaba
- 仅查询 `2026-01-01` 及之后数据
- 仅查询 dbt mart
- 生产账户默认受限
- 统一审计、统一预算控制、统一 CLI / Skill 接入

> 约束说明：
> MVP 阶段 **不重命名 AWS 现有 mart**（`mart_cost_monthly`, `mart_cost_summary`），以避免影响当前 Superset dashboard、设计文档和 bootstrap 脚本。Gateway 侧直接兼容现有 AWS 表名。

---

## 依赖关系

```
Step 0: Gateway 基础骨架 + PostgreSQL（建议先行）
  │
  ├──→ Step 1: GCP US mart 建模
  ├──→ Step 2: Alibaba us-west2 mart 建模
  ├──→ Step 3: account_mapping 分级与权限基础
  └──→ Step 4: Gateway 认证/审计/配置基础
            │
            ├──→ Step 5: 语义查询模型 + LLM 规划层
            ├──→ Step 6: SQL 编译 + Dry-run + Cross-cloud Merger
            └──→ Step 7: 管理 API + Catalog API
                         │
                         ├──→ Step 8: MCP Server (removed)
                         ├──→ Step 9: CLI
                         └──→ Step 10: Skill + Operator 文档
                                      │
                                      └──→ Step 11: 部署与端到端验收
```

**推荐并行方式**：

- Agent A: Step 1
- Agent B: Step 2
- Agent C: Step 3
- Agent D: Step 0 + Step 4
- Agent E: Step 5
- Agent F: Step 6 + Step 7
- Agent G: Step 8 + Step 9 + Step 10
- 最终由一个整合 agent 或 owner 完成 Step 11

---

## Step 0: Gateway 基础骨架 + PostgreSQL

**目标**：建立新的独立服务 `finops-query-gateway/`，提供最小可运行的 HTTP 服务、数据库连接、配置加载、Docker 构建和本地开发方式。

### 任务清单

- [x] **0.1** 新建 `finops-query-gateway/` 工程 ✅ `f51fe3f`
  - 技术栈固定为 `Node.js + TypeScript + Fastify + Prisma + zod + pino`
  - 初始化 `package.json`, `tsconfig.json`, `src/`, `prisma/`, `Makefile`, `Dockerfile`
- [x] **0.2** 建立基础目录结构 ✅ `f51fe3f`
  - `src/app.ts` — Fastify app 装配
  - `src/server.ts` — 入口
  - `src/config.ts` — 环境变量加载与校验（Zod schema）
  - `src/lib/db.ts` — Prisma Client 单例
  - `src/routes/health.ts` — `/healthz`, `/readyz`
  - `src/lib/logger.ts` — JSON 日志（pino）
- [x] **0.3** 新建 Prisma 初始 schema ✅ `f51fe3f`
  - 已定义 `User`, `ApiToken`, `AccountAccessGrant`, `QueryAuditLog`
  - 暂不定义与 Web UI 或通知相关的表
- [x] **0.4** 更新根目录 `docker-compose.yml` ✅ `f51fe3f`
  - 新增 `postgres` 服务（postgres:16-alpine）
  - 新增 `finops-query-gateway` 服务
  - 继续复用现有 service account JSON 挂载方式
- [x] **0.5** 增加 `.env.example` 或 gateway 专属环境变量模板 ✅ `f51fe3f`
  - `DATABASE_URL`
  - `GATEWAY_PORT`
  - `GATEWAY_TOKEN_DAILY_BYTES_LIMIT`
  - `GATEWAY_QUERY_BYTES_LIMIT`
  - `OPENROUTER_API_KEY` / `GEMINI_API_KEY`
  - `LLM_MODEL`
  - `GOOGLE_APPLICATION_CREDENTIALS`

### 实现方式细节

- Fastify 使用 plugin 方式注册路由和中间件
- Prisma migration 名称固定为 `init_gateway`
- `QueryAuditLog` 中 JSON 字段使用 `Json`
- 健康检查必须不依赖 BigQuery，只校验进程和数据库连接
- Dockerfile 使用 multi-stage build

### 涉及文件

| 文件 | 操作 |
|------|------|
| `finops-query-gateway/package.json` | 新建 |
| `finops-query-gateway/prisma/schema.prisma` | 新建 |
| `finops-query-gateway/src/app.ts` | 新建 |
| `finops-query-gateway/src/server.ts` | 新建 |
| `finops-query-gateway/src/config.ts` | 新建 |
| `finops-query-gateway/src/lib/db.ts` | 新建 |
| `finops-query-gateway/src/routes/health.ts` | 新建 |
| `finops-query-gateway/Dockerfile` | 新建 |
| `finops-query-gateway/Makefile` | 新建 |
| `docker-compose.yml` | 修改 |

### 验证标准

- [x] `docker-compose up -d postgres` 正常启动 ✅ 2026-03-12
- [x] `finops-query-gateway` 本地 `npm run build` 成功 ✅ 2026-03-12
- [x] `GET /healthz` 返回 200 ✅ 2026-03-12
- [x] `GET /readyz` 在数据库可用时返回 200 ✅ 2026-03-12
- [x] Prisma migration `init_gateway` 成功执行 ✅ 2026-03-12

---

## Step 1: GCP US mart 建模

**目标**：在 `US` 数据集构建 GCP usage-only mart，命名为 `mart_gcp_cost_monthly` / `mart_gcp_cost_summary`。

### 任务清单

- [x] **2.1** 新增 dbt `prod_us` target ✅ `25efba4`
  - dataset=`finops_cloud_billing_us`
  - location=`US`
- [x] **2.2** 配置 GCP source ✅ `d44e496`
  - `gcp-digital-bi.gcp_billing_v2`
  - MVP 简化为单表 `gcp_billing_export_v1_01D088_8F9CF2_8AF1C6`（2025-11-20 起的活跃账户）
- [x] **2.3** 新建 `stg_gcp_cost` ✅ `25efba4` `d44e496`
  - MVP 使用单表 + WHERE 过滤（非 UNION ALL）
  - 输出 30 个统一字段
  - 处理 `cost + credits.amount`
- [x] **2.4 + 2.5** 新建 `int_gcp_billing`（合并 ledger 和 usage-only） ✅ `25efba4` `d44e496`
  - MVP 不单独建 `_ledger` 模型，join + usage filter 合并在一个 VIEW
  - 过滤 `charge_type = 'Usage'` 且 `account_id IS NOT NULL`
- [x] **2.6** 新建 `mart_gcp_cost_monthly` ✅ `25efba4`
  - 按统一 detail grain 聚合，17 维度
  - 无法稳定映射的 tag 列统一置 `NULL`，但保留字段
- [x] **2.7** 新建 `mart_gcp_cost_summary` ✅ `25efba4`
  - 从 `mart_gcp_cost_monthly` 重聚合，11 维度
- [x] **2.8** 增加 GCP 测试 ✅ `25efba4`
  - 5 个测试：cloud_provider 一致性、cost 字段一致性、行数健全性、usage-only 过滤

### 实现方式细节

- `account_id` 使用 `project.id`
- `account_display_name` 默认使用 `project.name`，若 `account_mapping` 有映射则优先映射值
- `billing_period_month` 使用 `DATE_TRUNC(DATE(usage_start_time), MONTH)`
- `region` 使用 `COALESCE(location.region, 'global')`
- `service` 使用 `service.description`
- `service_category` 第一版使用 `sku.description`
- `unblended_cost_usd` / `usage_cost_usd` = `cost + SUM(credits.amount)`
- `effective_cost_usd` 第一版等于 `unblended_cost_usd`
- `sp_covered_usage_cost_usd` 第一版固定 `0`
- `tag_project` 提取优先级固定为：
  - `labels.key = 'project'`
  - `project.labels.key = 'project'`
  - 否则 `NULL`
- detail mart 中 AWS 才有的标签列统一保留同名字段，但先填 `NULL`

### 涉及文件

| 文件 | 操作 |
|------|------|
| `dbt/profiles.yml.example` | 修改 |
| `dbt/models/staging/_sources.yml` | 修改 |
| `dbt/models/staging/stg_gcp_cost.sql` | 新建 |
| `dbt/models/intermediate/int_gcp_billing_ledger.sql` | 新建 |
| `dbt/models/intermediate/int_gcp_billing.sql` | 新建 |
| `dbt/models/marts/core/mart_gcp_cost_monthly.sql` | 新建 |
| `dbt/models/marts/core/mart_gcp_cost_summary.sql` | 新建 |
| `dbt/models/intermediate/_int_models.yml` | 修改 |
| `dbt/models/marts/core/_mart_models.yml` | 修改 |
| `dbt/tests/*gcp*` | 新建 |

### 验证标准

- [x] `dbt build --target prod_us` 通过 (PASS=73, WARN=1) ✅ `d44e496`
- [x] `mart_gcp_cost_monthly` / `mart_gcp_cost_summary` 位于 `finops_cloud_billing_us` ✅
- [x] 2026-01 及之后 GCP 数据可查询（4.4k summary rows）✅
- [x] `cloud_provider='gcp'` ✅
- [x] `effective_cost_usd = unblended_cost_usd` ✅
- [x] `sp_covered_usage_cost_usd = 0` ✅

---

## Step 2: Alibaba us-west2 mart 建模

**目标**：在 `us-west2` 数据集构建 Alibaba usage-only mart，命名为 `mart_alibaba_cost_monthly` / `mart_alibaba_cost_summary`。

### 任务清单

- [x] **3.1** 配置 Alibaba source ✅ `25efba4` `d44e496`
  - 使用 `monthly_en_*`（EN 表列名与 CN 不同，已修正映射）
  - 仅覆盖 `2026-01-01` 及之后范围
- [x] **3.2** 新建 `stg_alibaba_cost` ✅ `25efba4` `d44e496` `12f26ee`
  - 基于 EN monthly tables，30 个统一字段
  - EN 表列名映射：owner_account_name→account_name, product_name→service, zone→availability_zone 等
- [x] **3.3 + 3.4** 新建 `int_alibaba_billing`（合并 ledger 和 usage-only） ✅ `25efba4`
  - MVP 不单独建 `_ledger` 模型，join + usage filter 合并在一个 VIEW
- [x] **3.5** 新建 `mart_alibaba_cost_monthly` ✅ `25efba4`
  - 按统一 detail grain 聚合，17 维度
- [x] **3.6** 新建 `mart_alibaba_cost_summary` ✅ `25efba4`
  - 从 detail mart 重聚合，11 维度
- [x] **3.7** 增加 Alibaba 测试 ✅ `25efba4`
  - 5 个测试：cloud_provider 一致性、cost 字段一致性、行数健全性、usage-only 过滤、account_id 映射

### 实现方式细节

- `account_id` 使用 `CAST(owner_account_id AS STRING)`
- `account_display_name` 优先使用 `account_mapping.account_display_name`，否则使用 `account_name`
- `billing_period_month` 使用 monthly EN 表的 `_TABLE_SUFFIX` 解析成 `DATE`
- `region` 使用 `COALESCE(region, 'global')`
- `service` 使用 `product_name`
- `service_category` 第一版使用 `product_type`
- `unblended_cost_usd` / `usage_cost_usd` 使用 `amount_after_discount`
- `effective_cost_usd` 第一版等于 `unblended_cost_usd`
- `sp_covered_usage_cost_usd` 第一版固定 `0`
- `tag_project` 从 `instance_tag` JSON 提取，优先 key：
  - `project`
  - `Project`
  - 否则 `NULL`
- monthly EN 表已经是月级，detail mart 的“detail”仅指统一维度更完整，不代表日粒度

### 涉及文件

| 文件 | 操作 |
|------|------|
| `dbt/models/staging/_sources.yml` | 修改 |
| `dbt/models/staging/stg_alibaba_cost.sql` | 新建 |
| `dbt/models/intermediate/int_alibaba_billing_ledger.sql` | 新建 |
| `dbt/models/intermediate/int_alibaba_billing.sql` | 新建 |
| `dbt/models/marts/core/mart_alibaba_cost_monthly.sql` | 新建 |
| `dbt/models/marts/core/mart_alibaba_cost_summary.sql` | 新建 |
| `dbt/models/intermediate/_int_models.yml` | 修改 |
| `dbt/models/marts/core/_mart_models.yml` | 修改 |
| `dbt/tests/*alibaba*` | 新建 |

### 验证标准

- [x] `dbt build --target prod_usw2` 通过 (PASS=153, WARN=0) ✅ `12f26ee`
- [x] 2026-01 及之后 Alibaba 数据可查询（1.4k detail, 1.0k summary rows）✅
- [x] `cloud_provider='alibaba'` ✅
- [x] `effective_cost_usd = unblended_cost_usd` ✅
- [x] `sp_covered_usage_cost_usd = 0` ✅
- [x] `owner_account_id` 正确映射为 `account_id` ✅

---

## Step 3: account_mapping 分级与权限基础

**目标**：把 `account_mapping` 从“业务属性映射表”提升为 Gateway 可用的权限基础数据。

### 任务清单

- [x] **4.1** 修复当前 active range 即将到期问题 ✅ `25efba4`
  - 所有记录 `effective_to` 已设为 `9999-12-31`
- [x] **4.2** 将生产与生产基础设施账户改为 `restricted` ✅ `25efba4`
  - AWS: dbaas-prod-clinic, pingcap-dbaas, dbaas-prod-dataplane-* 等
  - Alibaba: dbaas-prod-o11y, dbaas-prod-serverless, dbaas-prod-controlplane 等
  - GCP: 生产类项目已标记
- [x] **4.3** 保持开发/测试类账户为 `public` ✅ `25efba4`
  - `Development` / `Others` 类账户保持 `public`
- [x] **4.4** 补齐 GCP / Alibaba 当前活跃账户映射 ✅ `25efba4`
  - GCP 73 行、Alibaba 16 行（含 3 个 UNMAPPED placeholder）
- [x] **4.5** 增加映射校验 ✅ `25efba4`
  - schema tests 覆盖 access_level、account_purpose 等字段

### 实现方式细节

- 不引入 `team`，第一版只允许 `public | restricted`
- `access_level` 分级必须稳定，不允许网关运行时再根据字符串猜测生产/开发
- 账户 owner 与 `cost_center` 尽量补齐，避免 gateway 返回大量 `UNMAPPED`

### 涉及文件

| 文件 | 操作 |
|------|------|
| `dbt/seeds/account_mapping.csv` | 修改 |
| `dbt/seeds/_seeds.yml` | 如需修改校验则更新 |
| `dbt/tests/*account_mapping*` | 修改或新增 |
| `work.md` | 更新状态说明 |

### 验证标准

- [x] `dbt seed --full-refresh --target prod_usw2` 通过 ✅
- [x] `dbt seed --full-refresh --target prod_us` 通过 ✅
- [x] `dbt test --select account_mapping` 通过 ✅
- [x] 当前生产类账户均为 `restricted` ✅
- [x] 当前活跃映射不存在失效风险（effective_to=9999-12-31）✅

---

## Step 4: Gateway 认证 / 审计 / 配置基础

**目标**：建立 Gateway 的用户识别、token 鉴权、审计落库和基础配置加载。

### 任务清单

- [x] **5.1** 实现 token 鉴权中间件 ✅ `f51fe3f`
  - 头部使用 `Authorization: Bearer <token>`
  - onRequest hook 跳过 /healthz, /readyz
  - SHA-256 hash 查询 + constant-time 比对
- [x] **5.2** 实现 `ApiToken` 创建与校验逻辑 ✅ `f51fe3f`
  - 只存 hash（sha256），返回 `fqg_` prefix 供展示
  - `prisma/seed.ts` 可生成初始 admin token
- [x] **5.3** 实现 `User` 与 `AccountAccessGrant` 查询层 ✅ `f51fe3f`
  - Prisma schema 定义完整关联
- [x] **5.4** 实现 `QueryAuditLog` 写入 helper ✅ `f51fe3f`
  - `writeAuditLog()` / `updateAuditLog()` 完整覆盖请求生命周期
- [x] **5.5** 实现基础错误模型 ✅ `f51fe3f`
  - `GatewayError` class + `Errors` factory
  - 6 种错误码：unauthorized(401), forbidden(403), invalidRequest(400), unsupportedQueryShape(422), budgetExceeded(429), executionFailed(500)

### 实现方式细节

- token 使用随机 32+ bytes，持久化时仅存 `sha256`
- 每次请求都必须附带 `client_type`, `client_name`
- preview / execute / reject / error 都写审计
- audit log 记录原始问题、用户、token、模式、状态、错误码

### 涉及文件

| 文件 | 操作 |
|------|------|
| `finops-query-gateway/src/plugins/auth.ts` | 新建 |
| `finops-query-gateway/src/lib/tokens.ts` | 新建 |
| `finops-query-gateway/src/lib/audit.ts` | 新建 |
| `finops-query-gateway/src/lib/errors.ts` | 新建 |
| `finops-query-gateway/prisma/schema.prisma` | 修改 |
| `finops-query-gateway/prisma/migrations/*` | 新建 |

### 验证标准

- [x] 无 token 请求返回 401 ✅ 2026-03-12
- [x] 无效 token 返回 401 ✅ 2026-03-12
- [x] 正常 token 可访问受保护接口（返回 404 而非 401）✅ 2026-03-12
- [ ] 每次请求都能在 `QueryAuditLog` 查到记录 — 待 Step 5+ 实现查询路由后验证

---

## Step 5: 语义查询模型 + LLM 规划层

**目标**：把自然语言问题转换成受控 `SemanticQuery`，而不是直接生成 SQL。

### 任务清单

- [x] **6.1** 定义 `SemanticQuery` zod schema — `src/domain/semantic-query.ts`
- [x] **6.2** 定义支持的维度、指标、过滤器 allowlist — `src/domain/semantic-query.ts`
- [x] **6.3** 新建 semantic catalog — `src/domain/catalog.ts`
  - 每个 cloud 的 summary/detail mart 支持哪些字段
- [x] **6.4** 实现 LLM planner — `src/services/query-planner.ts` + `src/lib/llm/`
  - 输入自然语言
  - 输出结构化 JSON
- [x] **6.5** 实现 planner 校验与修正 — `src/services/query-planner.ts:enforceConstraints()`
  - 不合法字段拒绝
  - 日期范围强制加 `2026-01-01` floor
  - 多云请求若未包含 `cloud_provider` 维度，自动补入
- [x] **6.6** 内置 5 个分析模板 — `src/services/analysis-templates.ts`
  - `cost_by_cloud_monthly`
  - `top_accounts_last_month`
  - `top_services_last_month`
  - `account_mom_change`
  - `prod_vs_dev_split`

### 实现方式细节

- LLM 输出必须是 JSON，不允许自由文本
- Gateway 对 LLM 输出做二次 zod 校验，失败则最多修复 1 次
- `answer_markdown` 第一版不依赖第二次 LLM 总结，由程序模板生成
- planner prompt 只暴露 semantic schema，不暴露 raw table 名称

### 涉及文件

| 文件 | 操作 |
|------|------|
| `finops-query-gateway/src/domain/semantic-query.ts` | 新建 |
| `finops-query-gateway/src/domain/catalog.ts` | 新建 |
| `finops-query-gateway/src/lib/llm/index.ts` | 新建 |
| `finops-query-gateway/src/lib/llm/providers/*` | 新建 |
| `finops-query-gateway/src/services/query-planner.ts` | 新建 |
| `finops-query-gateway/src/services/analysis-templates.ts` | 新建 |

### 验证标准

- [ ] 常见问题能稳定输出合法 `SemanticQuery`
- [ ] 不支持的字段/指标会被拒绝
- [ ] 日期范围会强制收敛到 `2026-01-01` 及之后
- [ ] 5 个内置模板均可正常生成语义请求

---

## Step 6: SQL 编译 + Dry-run + Cross-cloud Merger

**目标**：将 `SemanticQuery` 编译为每云单独 SQL，执行 dry-run / execute，并在服务端合并结果。

### 任务清单

- [x] **7.1** 实现 per-cloud SQL compiler — `src/services/sql-compiler.ts`
  - AWS -> 现有 `mart_cost_*`
  - GCP -> `mart_gcp_*`
  - Alibaba -> `mart_alibaba_*`
- [x] **7.2** 实现 location-aware BigQuery executor — `src/services/bigquery-executor.ts`
  - `aws/alibaba -> us-west2`
  - `gcp -> US`
- [x] **7.3** 实现 dry-run 预算守卫 — `src/services/query-runner.ts`
- [x] **7.4** 实现 cross-cloud merger — `src/services/result-merger.ts`
  - 维度一致时再聚合
  - 排序、Top N 在合并后执行
- [x] **7.5** 实现结果渲染 — `src/services/answer-renderer.ts`
  - rows
  - row_count
  - truncated
  - `answer_markdown`

### 实现方式细节

- BigQuery 执行层可复用 `data-agent-v1/src/lib/bigquery-request.ts` 的思路，但不直接依赖其 Next.js 路由
- 每个 shard 都必须先 dry-run 再 execute
- 单 shard 超过 `10 GB` 直接拒绝整个请求
- 合并后的 Top N 绝不能基于单云局部 limit
- `answer_markdown` 至少包含：
  - 查询范围
  - 使用的云
  - 授权账户数
  - 扫描量
  - 主结果摘要

### 涉及文件

| 文件 | 操作 |
|------|------|
| `finops-query-gateway/src/services/sql-compiler.ts` | 新建 |
| `finops-query-gateway/src/services/bigquery-executor.ts` | 新建 |
| `finops-query-gateway/src/services/query-runner.ts` | 新建 |
| `finops-query-gateway/src/services/result-merger.ts` | 新建 |
| `finops-query-gateway/src/services/answer-renderer.ts` | 新建 |

### 验证标准

- [ ] 三云总成本比较请求会拆成三个 cloud shard
- [ ] GCP shard 在 `US` 执行
- [ ] AWS / Alibaba shard 在 `us-west2` 执行
- [ ] 合并结果等于手工汇总
- [ ] 全局 Top N 正确
- [ ] dry-run 超限时整个请求被拒绝

---

## Step 7: 管理 API + Catalog API

**目标**：提供运维和客户端自省所需的 API。

### 任务清单

- [x] **8.1** 实现 `GET /v1/catalog/semantic-schema` — `src/routes/catalog.ts`
- [x] **8.2** 实现 `GET /v1/catalog/accounts` — `src/routes/catalog.ts`
- [x] **8.3** 实现 `GET /v1/me/queries` — `src/routes/me.ts`
- [x] **8.4** 实现 `POST /v1/admin/tokens` — `src/routes/admin.ts`
- [x] **8.5** 实现 `DELETE /v1/admin/tokens/{id}` — `src/routes/admin.ts`
- [x] **8.6** 实现 `POST /v1/admin/grants` — `src/routes/admin.ts`
- [x] **8.7** 实现 `DELETE /v1/admin/grants/{id}` — `src/routes/admin.ts`
- [x] **8.8** 实现 `GET /v1/admin/audit` — `src/routes/admin.ts`

### 实现方式细节

- `admin` 角色才能访问 `/v1/admin/*`
- `catalog/accounts` 返回当前 token 可访问的账户列表及其 `cloud_provider`, `access_level`
- `semantic-schema` 输出固定字段和支持的分析模板，供 CLI/Skill 做提示

### 验证标准

- [ ] viewer 无法访问 admin API
- [ ] admin 可以签发 token 与 grant
- [ ] 当前用户可访问账户列表返回正确
- [ ] 审计查询可以按用户/状态/时间过滤

---

## Step 8: MCP Server (Removed)

> **Deprecated (2026-03-13)**: MCP server (`finops-mcp/`) has been removed. The MCP layer was a thin pass-through that added deployment complexity (each user must configure and run the server locally) without adding business logic. AI agents now call the Gateway HTTP API directly via the Skill or CLI.

~~**目标**：给现有 agent 工具提供标准 MCP 接口。~~

Previously completed, then removed as part of architecture simplification.

---

## Step 9: CLI

**目标**：提供可脚本化的本地命令行入口，便于开发、排障、演示。

### 任务清单

- [x] **10.1** 新建 `finops-cli/` ✅
- [x] **10.2** 实现命令 ✅
  - `login`
  - `ask`
  - `analyses run`
  - `accounts`
  - `queries`
- [x] **10.3** 支持 `--json` 输出模式 ✅

### 实现方式细节

- 默认配置写入 `~/.config/finops-cli/config.json`
- `ask` 支持 `--preview` 与 `--execute`
- 输出必须包含 `request_id`

### 验证标准

- [x] CLI 可完成登录 ✅
- [x] CLI 可执行 ask 与 analysis ✅
- [x] `--json` 输出可被 shell 脚本消费 ✅

---

## Step 10: Skill + Operator 文档

**目标**：让现有 agent 快速、安全地使用 Gateway。

### 任务清单

- [x] **11.1** 新建 `skills/cloudcost/SKILL.md` ✅ (263 lines)
- [x] **11.2** 编写 operator 使用文档 ✅ (137 lines)
  - token 发放
  - 授权管理
  - 常见问题排查
- [x] **11.3** 编写 10 条示例提问 ✅ (含 3 条中文)
  - 三云对比
  - top account
  - prod vs dev
  - service split

### 实现方式细节

- Skill 明确禁止 agent 直接访问 BigQuery
- Skill 优先调用 `finops_run_analysis`
- Operator 文档写清楚 token / grant / budget / audit 查询路径

### 验证标准

- [x] Skill 可被 agent 正常加载 ✅
- [x] 示例问题都能命中正确工具 ✅
- [x] 运维文档足够支持非开发人员发 token 和查审计 ✅

---

## Step 11: 部署与端到端验收

**目标**：把多云 mart、Gateway、CLI/Skill 串起来，完成一次完整的上线前验证。

### 任务清单

- [x] **12.1** 部署 PostgreSQL + Gateway ✅ docker-compose up postgres + tsx dev on port 4000
- [x] **12.2** 运行 dbt 构建 ✅ marts already built in previous steps
- [x] **12.3** 创建首个 admin 用户与 token ✅ admin@pingcap.com, fqg_ token via seed
- [x] **12.4** 配置 2-3 个 restricted 生产账户授权样例 ✅ 3 grants: aws/143458967504, gcp/tidbcloud-prod-000, alibaba/5108739859105002
- [x] **12.5** 用 CLI 做端到端测试 ✅ CLI ask/analyses/accounts/queries all verified
- [x] **12.6** 记录上线手册 ✅ skills/cloudcost/OPERATOR.md + deployment-runbook.md

### 必测场景

- [x] “比较 2026 年 2 月 AWS、GCP、Alibaba 总成本” ✅ AWS $4.4M, GCP $334K, Alibaba $216K
- [x] “列出上个月成本最高的 20 个账户” ✅ 20 rows, cross-cloud, dbaas-prod-dataplane-000 #1
- [x] “按云和服务查看 2026-02 成本” ✅ 129 rows, 3 clouds
- [x] “比较生产和开发测试账户成本” ✅ via prod_vs_dev_split template (NL needs exact enum values)
- [x] 普通用户访问未授权生产账户 ✅ viewer sees 156 accounts (153 public + 3 granted restricted)
- [x] admin 查看受限账户 ✅ admin sees all 176 accounts

### 验证标准

- [x] Gateway 返回正确结果与 `request_id` ✅
- [x] 三云结果可稳定合并 ✅ AWS + GCP + Alibaba shards merged consistently
- [x] 权限控制生效 ✅ viewer restricted to granted accounts only
- [x] 审计日志完整 ✅ 20+ entries including errors, all with request_id/user/status
- [x] CLI 与 Skill 行为一致 ✅ CLI tested: ask, analyses list/run, accounts, queries

### Bugs Found & Fixed During E2E

1. **OpenRouter model ID**: `google/gemini-2.5-flash-preview` → `google/gemini-2.5-flash` (model renamed on OpenRouter)
2. **Invalid DATE literal**: LLM generates `2026-02-29` but 2026 is not a leap year. Fixed by normalizing all dates to first-of-month in `enforceConstraints()`
3. **account_purpose case mismatch**: LLM guesses lowercase `”production”` but data uses `”Production”`. Added enum values to system prompt

---

## 全局 Definition Of Done

满足以下条件视为 Query Gateway MVP 完成：

- [x] AWS / GCP / Alibaba 均有可查询的云专属 usage marts ✅
- [x] Gateway 可以把自然语言转成受控 `SemanticQuery` ✅
- [x] Gateway 可以跨 region 分片执行并合并结果 ✅
- [x] 普通用户无法访问未授权生产账户 ✅
- [x] 所有请求都有审计记录 ✅
- [x] CLI / Skill 均可正常工作 ✅ (MCP removed 2026-03-13)
- [x] 至少 5 个核心分析模板可直接使用 ✅ (5 templates: cost_by_cloud_monthly, top_accounts_last_month, top_services_last_month, account_mom_change, prod_vs_dev_split)
