# Data Proxy API 迁移与验收状态

> 最后更新：2026-08-10
> 本文面向 reviewer 和交付验收；详细设计、历史调查与排障记录见[完整迁移方案](./plans/2026-07-17-data-proxy-clinic-api-migration.md)。

## 当前结论

Clinic #1594 中已实现迁移的 Metadata、Activity Events 和 Components 都由 Data Proxy 调用 Clinic internal API。权威数据源和核心业务逻辑位于 Clinic，Data Proxy 只负责公开路径、参数/DTO、鉴权边界、错误映射和观测。

因此可以说两条访问链路的 **权威数据源和核心业务语义一致**；不能说 Clinic legacy API 与 Data Proxy API 的 HTTP 参数、响应和兼容行为逐字一致。

Top RU 属于 O11y 数据面能力，不在 Data Proxy 或 Clinic 重写聚合逻辑：Clinic internal API 只复用既有 ownership 与 O11y resolver 返回 capability-specific target，Data Proxy 以固定 path 和安全 Header 直接调用 O11y，summary/summary-plan/user-stats 的聚合语义仍只维护在 O11y。

Top RU summary 与 user-stats 已完成 prod 验收：active Premium 与 active Essential 均与 legacy NGM 完整 JSON parity；Premium 的省略 `user`、`user=` 和 named user 三态也一致。summary-plan follow-up 已完成本地实现与测试，Staging/Prod 仍 Pending；部署后必须与 NGM `summaryplan` 做完整 JSON parity。此前错误 org、非法 query、缺少必需参数、deferred config 与伪造 placement Header 的结论继续适用于已部署接口。

SQL Statements 本批是 Data Proxy 数据面的增量能力：复用 `POST /data-proxy/query` 和新逻辑表 `clinic_data_proxy.statements`，由 Clinic internal cluster data-URL API 返回服务端解析的 `persisted_statements_summary` 位置，再由 Data Proxy 选择 Athena、BigQuery 或 Synapse 查询。prod AWS Premium 已完成有数据正向查询和安全边界验收；AliCloud unsupported 契约通过。prod 没有 GCP Premium 样本，现有 Azure Premium 样本没有 Statements 数据源，因此这两个 provider 仍是 **Not exercised**，不能宣称多云 E2E 全通过。

Data Proxy REST 规范化 Batch 1 为 SQL query、same-cluster batch、table schemas 和 service capabilities 增加 `/data-proxy/api/v1` canonical facade。它是纯增量 HTTP adapter：四个 legacy method/path、caller options、跨集群 legacy batch 和响应结构都保持不变；canonical 请求把 TiDB Cloud/DBAAS org 与 logical cluster 放在 PATH，由服务端完成 ownership 和 authoritative target 解析。当前为 **Prod Pass**：四组 legacy/canonical 在同一 active AWS Premium 样本上完成 parity；Staging 仍 Pending。

Batch 2 为 cluster metrics 和 business logs 增加 org/cluster-scoped canonical alias，复用现有 Prometheus/Loki handler 与 service core；canonical adapter 增加 ownership、typed target identity check，并拒绝 caller placement。当前为 **Prod Pass**：Metrics 6 个 method/operation 和 Business Logs 4 个 operation 均完成 legacy/canonical 稳定 payload parity；Staging 仍 Pending。

Batch 3 为 privileged System Logs 增加 datasource-scoped canonical alias。System Logs 不属于 business org/cluster，新 PATH 使用 catalog 返回的 `{datasourceUID}`（配置 UID，或 legacy datasource 名称的稳定 fallback），并继续复用现有 system-logs Loki proxy。当前为 **Prod Pass**：catalog、labels、label values、series 和 query-range 在默认 AWS us-west-2 datasource 上完成 parity；Staging 仍 Pending。

Batch 4 当前为 **Blocked by resolver**：仓库尚无不依赖 caller `control_plane_info` 的 authoritative child org/cluster → live parent metrics/logs target resolver，因此不创建伪 canonical alias。Batch 5 已完成 **internal semantic review**，但 org/tenant ID 和双集群授权语义尚未定案，结论为 **no aliases**；既有 `/clinic-internal` API 不变。

## API 访问与实现映射

Data Proxy 的统一调用链是：

```text
caller
  -> /data-proxy/api/v1/...
  -> clinic_api_base（clinic-internal-api Service）
  -> /clinic/api/v1/internal/data-proxy/v1/...
  -> Clinic 既有 service / datasource core
```

Top RU 的调用链是：

```text
caller
  -> /data-proxy/api/v1/.../top-ru/...
  -> Clinic internal target resolver（ownership + O11y target）
  -> O11y /internal/diagnosis/api/v1/topru/{summary|summaryplan|user_stats}
```

SQL Statements 通用查询的调用链是：

```text
caller
  -> canonical POST /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/sql-queries
     或 legacy POST /data-proxy/query
     （SQL logical table: clinic_data_proxy.statements）
  -> Clinic GET /clinic/api/v1/internal/clusters/{clusterID}/cluster-dataurls
  -> server-resolved persisted_statements_summary URL
  -> AWS Athena / GCP BigQuery / Azure Synapse
```

REST 规范化 Batch 1 的调用链是：

```text
internal Clinic key caller
  -> /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/{sql-queries|sql-query-batches|table-schemas}
  -> Clinic canonical org-cluster ownership lookup
  -> Clinic GET /clinic/api/v1/internal/clusters/{clusterID}/cluster-dataurls
  -> existing DataProxy Service / provider engine

internal Clinic key caller
  -> GET /data-proxy/api/v1/capabilities
  -> existing DataProxy service-info core
```

Canonical facade 不接受 caller 提供的 cluster/org、provider、region、dataSource 或 optimization options；legacy path 仍按原契约处理这些字段。当前 audience 是 Investibook/受信服务使用的 internal Clinic key，不代表普通 Clinic user 或 DBAAS credential 已获得访问权限。

Batch 2/3 的调用链是：

```text
internal Clinic key caller
  -> Batch 2 /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/{metrics|logs}/...
  -> Clinic canonical ownership + typed cluster target
  -> existing MetricsQueryService / LokiProxyService

privileged internal Clinic key caller
  -> Batch 3 /data-proxy/api/v1/system-log-datasources/{datasourceUID}/logs/...
  -> catalog path ID lookup（configured UID；缺失时使用 stable datasource-name fallback）
  -> existing system-logs Loki proxy
```

Batch 2 保留 Prometheus query/form 和 Loki query 参数，不接受 caller scope/placement。Batch 3 不增加 org/cluster；datasource catalog 不接受 query/body，日志请求只接受 Loki 业务 query，datasource/provider/region routing aliases 不再出现在 query。

完整 staging facade 验收涉及三个独立 Deployment：`clinic-internal-api` 承载 internal facade，`clinic-data-service` 承载 Data Proxy public API，`clinic` 用于被改动 legacy API 的回归。Top RU 没有修改 NGM Handler，但验收仍需对照现有 NGM path。#1596 已把 `internal_api` 和 `clinic/data-service` 两个 staging image 更新到 merge commit `112ae191`。

TOPRU-04 follow-up 复用已部署的 target resolver，只修改 `clinic-data-service`；本次发布不需要重发 `clinic`、`clinic-internal-api` 或 `ngm-api`。

| ID | 现有 / Legacy 访问 | Canonical Data Proxy 访问 | 最终权威实现 / 数据源 |
| --- | --- | --- | --- |
| META-01 精确 Metadata | `GET /clinic/api/v1/dashboard/clusters?cluster_id={clusterID}` | `GET /data-proxy/api/v1/clusters/{clusterID}`<br>→ `GET /clinic/api/v1/internal/data-proxy/v1/clusters/{clusterID}` | `datasource.ListOrganizationAndCluster` |
| META-02 Org cluster list | `GET /clinic/api/v1/orgs/{clinicOrgID}/clusters?pid={clinicProjectID}` | `GET /data-proxy/api/v1/orgs/{orgID}/clusters`<br>→ `GET /clinic/api/v1/internal/data-proxy/v1/orgs/{orgID}/clusters` | `datasource.GetOrganizationByCloudID` + `datasource.ListClusters` |
| META-04 Org cluster detail | `GET /clinic/api/v1/orgs/{clinicOrgID}/clusters/{clusterID}` | `GET /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}`<br>→ `GET /clinic/api/v1/internal/data-proxy/v1/orgs/{orgID}/clusters/{clusterID}` | `datasource.GetOrganizationByCloudID` + `datasource.GetCluster` |
| ACT-01 Activity Events | `GET /clinic/api/v1/activityhub/applications/{applicationID}/targets/{clusterID}/activities` | `GET /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/activities`<br>→ `GET /clinic/api/v1/internal/data-proxy/v1/orgs/{orgID}/clusters/{clusterID}/activities` | 均由 Clinic `ActivityHubClient` 访问同一 ActivityHub；legacy 使用原 `SearchActivitiesByTimeRange` 契约，internal 使用 strict `SearchActivities` 契约 |
| TOPO-01 Components | `GET /clinic/api/v1/orgs/{clinicOrgID}/clusters/{clusterID}/resource-pool/components` | `GET /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/components`<br>→ `GET /clinic/api/v1/internal/data-proxy/v1/orgs/{orgID}/clusters/{clusterID}/components` | `ClusterService.ListResourcePoolComponents`，继续使用既有 dedicated/resource-pool/premium datasource |
| TOPRU-02 Summary | `GET /ngm/api/v1/topru/summary` | `GET /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/top-ru/summary`<br>→ Clinic target resolver → O11y `GET /internal/diagnosis/api/v1/topru/summary` | O11y Top RU aggregation；Data Proxy 仅做 capability resolver、query allowlist 与安全转发 |
| TOPRU-04 Summary by plan | `GET /ngm/api/v1/topru/summaryplan` | `GET /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/top-ru/summary-plan`<br>→ Clinic target resolver → O11y `GET /internal/diagnosis/api/v1/topru/summaryplan` | O11y 按 SQL/plan digest 聚合；Data Proxy 原样返回 plan rows，调用方按需 drill-down |
| TOPRU-03 User stats | `GET /ngm/api/v1/topru/user_stats` | `GET /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/top-ru/user-stats`<br>→ Clinic target resolver → O11y `GET /internal/diagnosis/api/v1/topru/user_stats` | O11y Top RU aggregation；成功 body 原样保留 |
| STMT-SQL 灵活查询 | 无一对一 Clinic API；旧 `/ngm/api/v1/statements/*` GET 继续保留 | Canonical：`POST /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/sql-queries`<br>Legacy：`POST /data-proxy/query`<br>SQL 使用 `clinic_data_proxy.statements` | Clinic 只解析 authoritative data URL；Data Proxy 负责 provider 路由、Delta availability 检查和 OLAP SQL 查询 |
| DP-REST-QUERY | Legacy Data Proxy：`POST /data-proxy/query`，cluster 和 options 在 body | `POST /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/sql-queries`；body 只含 `sql`、可选 `timeout` | 同一 `DataProxyService.Query` core；canonical adapter 负责 strict JSON、ownership 和 authoritative target policy |
| DP-REST-BATCH | Legacy Data Proxy：`POST /data-proxy/batch-query`，允许 item-level cluster 和跨集群 | `POST /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/sql-query-batches`；所有 item 使用 PATH cluster | 同一 `DataProxyService.BatchQuery` core；canonical 每批只解析一次 ownership/target，再按 item query type 选择数据源 |
| DP-REST-SCHEMA | Legacy Data Proxy：`GET /data-proxy/schema?clusterId=...&tables=...` | `GET /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/table-schemas?tables=...` | 同一 schema/table-management core；canonical 不接受 caller dataSource/vendor/region |
| DP-REST-CAP | Legacy Data Proxy：`GET /data-proxy/info` | `GET /data-proxy/api/v1/capabilities` | 同一 service info core；这是服务级资源，因此不增加无意义 org/cluster PATH |
| DP-REST-METRICS | `GET, POST /data-proxy/metrics/{cluster_id}/api/v1/{query,query_range,series}` | `GET, POST /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/metrics/{query,query-range,series}` | 同一 `MetricsQueryHandler` / `MetricsQueryService`；canonical 先校验 ownership 与 typed target identity |
| DP-REST-LOGS | `GET /data-proxy/loki/{cluster_id}/api/v1/{query_range,labels,series}`<br>`GET .../label/{name}/values` | `GET /data-proxy/api/v1/orgs/{orgID}/clusters/{clusterID}/logs/{query-range,labels,series}`<br>`GET .../logs/labels/{name}/values` | 同一 `LokiProxyService`；canonical 只新增 scope/target adapter，不复制 LogQL 逻辑 |
| DP-REST-SYSLOG-CATALOG | `GET /data-proxy/system-logs/api/v1/datasources` | `GET /data-proxy/api/v1/system-log-datasources` | 同一配置 datasource catalog；不暴露 upstream URL |
| DP-REST-SYSLOG | `GET /data-proxy/system-logs/api/v1/{query_range,labels,series}`<br>`GET .../label/{name}/values`，datasource 在 query | `GET /data-proxy/api/v1/system-log-datasources/{datasourceUID}/logs/{query-range,labels,series}`<br>`GET .../logs/labels/{name}/values` | 同一 privileged System Logs proxy；canonical 用 PATH UID 选择配置 target |
| DP-REST-PARENT | `/data-proxy/serverless-parent-{metrics,logs}/api/v1/...`，依赖 `control_plane_info` | **无 alias：Blocked by resolver** | 待 authoritative child → live parent resolver；legacy protocol 保留 |
| DP-INTERNAL-REVIEW | 既有 5 个 `/clinic-internal/...` POST API | **无 alias：Semantic review complete** | Traffic 双集群授权、healthcheck routing/ID 和 AI org domain 尚需定案 |

这里的 `{orgID}` 是 TiDB Cloud/DBAAS org ID；Clinic legacy path 中的 `{clinicOrgID}` 是 Clinic organization 主键。

## 有意保留的 HTTP 契约差异

- Metadata 返回 Investibook 诊断所需的 canonical DTO，不复制 Dashboard envelope，也不包含 legacy detail 的 Jira、quota、feature gates 等 enrichment。
- Clinic legacy cloud-org list 需要 Clinic project ID；Data Proxy canonical list 使用 DBAAS org ID，按 org 返回分页结果。
- Data Proxy Metadata 使用 DBAAS org ID，并对 cluster ID、Clinic org 主键以及 `cluster.tenantID == organization.dbaasOrgID` invariant 做 fail-closed ownership 校验。
- Activity 的成功数据仍来自同一 ActivityHub search；legacy cursor 位于 Header，Data Proxy cursor 位于 query，Data Proxy 还增加严格参数和 org ownership 校验。
- Components 使用同一 deploy-type 分派和 datasource；Data Proxy 不返回 legacy 无 CSRF 场景下追加的 mocked TiCDC。
- Top RU 不接受 caller placement Header，不透传 Authorization/Cookie；只允许固定 summary/summary-plan/user-stats path 和已知 query。`digest` 只允许用于 summary-plan；summary 与 summary-plan 的“省略 `user`”“`user=`”“指定 user”三态保持不同语义。
- Top RU 成功 JSON 不 decode/re-marshal；未知 O11y 字段得以保留。上游错误 body 不向 caller 暴露，而是映射为 Data Proxy resource error。
- Top RU config 仍为 Deferred Candidate，本批不注册。
- Statements 只接受完整小写逻辑表 `clinic_data_proxy.statements`，cluster scope 在 canonical API 中来自 PATH、在 legacy API 中来自 body `clusterId`，SQL 必填 `date = 'YYYY-MM-DD'` 分区条件。Canonical body 不接受 `options`；legacy caller 即使提供 `options.dataSource/vendor/region/skipSQLRewrite/skipTableManagement`，Statements 也会无条件使用 Clinic 解析结果和受管 SQL/物理表。Batch 与 schema 暂不支持 Statements。
- Statements 范围是 Premium、Premium Reserved、Premium Resource Pool 和 Premium Reserved Resource Pool。AWS/GCP/Azure 已实现；AliCloud 显式 unsupported，不会用 MaxCompute/OSS Parquet 路径猜测 Delta 语义。
- Statements URL 已解析但存储根目录为空时，返回 `200` 空 collection；Clinic 未返回 URL 时返回 `400/DATASOURCE_NOT_CONFIGURED`。存储权限、超时、网络和损坏 Delta 错误不会被吞掉为空结果。
- STMT-SQL 不保证旧 NGM `available_fields`、`stmt_types`、`list`、`plans`、`plan/detail` 的 response parity；这五个 GET 契约未被本批替代，兼容迁移继续单独跟踪。
- 已知非阻塞边界：Clinic 现有 `cluster-dataurls` 会把 resolver 失败记录到日志后仍返回空 URL，因此临时 O11y 故障与“未开启持久化”都可能表现为 `DATASOURCE_NOT_CONFIGURED`，排查时必须结合 Clinic 日志；source URL 变化后新查询会使用 source-hash 隔离的新物理表，旧表元数据清理由后续运维任务处理；GCP/Azure 对象存储 list 权限仍以 prod E2E 为准。
- DP-REST Batch 1 不改变 legacy/shared SQL dialect、standard table、provider、建表、rewrite、engine query 或成功响应结构；canonical adapter 额外要求 UNION、CTE 和子查询中的每个物理表扫描各自在 `WHERE` 中具备 `date` 分区条件，以阻止无界扫描。JOIN 的每个物理 alias 都要显式过滤；仅位于 `ON` 的过滤会被保守拒绝。
- Canonical query item 只允许大小写精确的 `sql` 和可选 `timeout`；重复 key、显式 `null`、`clusterId`/`cluster_id`、`orgId`、`options`、未知字段和 trailing JSON 都拒绝。Canonical schema 只允许 `tables` query；batch 固定为 PATH 中的同一 cluster。Legacy API 不继承这些 strict policy。
- Canonical warehouse cluster ID 当前继续限制为纯数字，因为既有 provider 会把它用于物理 identifier；opaque identifier encoding 不属于本批。
- Canonical facade 不依赖 redirect，也不设置 legacy Sunset。只有在部署验收和 consumer/skill 切换完成后，才讨论后续 deprecation。
- Batch 2 metrics 在 canonical path 将 legacy `query_range` 改为 `query-range`，但 GET/POST query/form 字段和 Prometheus success/error payload 保持；business logs 同理保留 Loki query 与 payload。canonical 会拒绝 caller scope 和 placement，而不是把它们传入 shared core。
- Batch 3 以 datasource UID 表达真实 scope，不人为增加 org/cluster。canonical catalog 不接受 query/body；日志入口拒绝 `datasource`、`datasource_uid`、`datasourceUid`、`datasourceUID`、`uid`、provider/region 等 routing query，legacy aliases 和既有行为不变。
- Batch 4 在可信 resolver 就绪前不注册 `/parent-metrics/...` 或 `/parent-logs/...`；Batch 5 本轮也不注册任何 `/clinic-internal` alias。没有新入口即没有可宣称的环境 Pass。

## 当前验收状态

状态规则：只有目标环境已经部署当前 facade，并使用该环境 Clinic key 同时完成 Data Proxy E2E 和 Clinic legacy 回归，才能标记为 `Pass`。代码合并、CI 通过或历史 direct 实现通过都不能替代环境验收。

| API | Local / CI | Staging | Prod | 历史基线（不计入当前验收） |
| --- | --- | --- | --- | --- |
| META-01 / META-02 / META-04 | **Pass**：Clinic [#1594](https://github.com/pingcap-inc/clinic/pull/1594) 实现提交 `ff2d4982`，全部代码 CI 通过 | **Pass**：2026-07-21 facade/legacy 六条路径均 `200`；essential 与 premium 的 exact/detail/list 核心字段 parity 通过 | **Pass**：2026-07-22 legacy exact/detail 与 canonical exact/detail/list 均 `200`；canonical DTO 映射、list membership 与 ownership 通过；错误 org `404`、无 key `401` | #1572 direct-DB 版本于 2026-07-20 在 staging 通过，仅作为字段和错误 parity 基线；2026-07-17 prod 新路径未部署，探测为 `404` |
| ACT-01 | **Pass**：strict internal、legacy compatibility 与 #1594 最终 CI 通过 | **Pass（有覆盖缺口）**：非空、filter、未来空窗口均 `200` 且 parity；cursor 为 **Not exercised**，16 个 90 天样本均无 live cursor | **Pass（cursor 未覆盖）**：7 天窗口返回 3 条活动，legacy/canonical 完整 JSON parity；本次无分页 cursor | #1591 direct 版本完成 isolated staging smoke，但不等于当前 facade 或正式持久环境通过 |
| TOPO-01 | **Pass**：#1594 shared-core tests/build/CI 通过 | **Pass（有覆盖缺口）**：resource-pool、premium 与 mocked TiCDC/CSRF compatibility 通过；dedicated 为 **Not exercised** | **Pass**：active Dedicated 样本返回 PD/TiDB/TiKV 三类 component；排除文档约定的 legacy mock TiCDC 后完整 parity | 无 direct 历史基线 |
| TOPRU-02 / TOPRU-03 | **Pass**：[Clinic #1596](https://github.com/pingcap-inc/clinic/pull/1596)；Clinic target、typed client、safe O11y transport、query/response/error 与 Router focused tests 通过 | **Partial Pass**：Premium summary/user-stats 与 omitted/empty/named user 完整 parity；安全矩阵通过。Essential **Blocked / Not exercised**：唯一 active 样本 legacy `500`、new `502`，共同根因为 O11y registry 无 cluster target | **Pass**：active Premium 与 Essential 的 summary/user-stats 均 `200` 且完整 JSON parity；Premium user 三态、错误 org、非法参数、deferred config 和 placement 隔离通过 | NGM legacy path 是现网基线；本批未修改 NGM Handler。staging Essential 无 target 是环境历史缺口 |
| TOPRU-04 | **Pass**：summary-plan handler/service/router focused tests 与自动生成 OpenAPI 通过 | **Pending** | **Pending** | 与已部署 NGM `summaryplan` 对比；同一 SQL 两个 plan 的样本必须返回两条 plan row |
| STMT-SQL | **Pass**：[Clinic #1600](https://github.com/pingcap-inc/clinic/pull/1600) 已合并；Clinic URL resolver、server-side override、AWS/GCP/Azure provider 与安全边界测试/CI 通过 | **Pending / 无正向数据源**：可做 route/错误 smoke，不计为数据 E2E | **Partial Pass**：AWS Premium legacy/canonical 单日 `LIMIT 1` 均返回 1 row，Athena 路由与安全边界通过；AliCloud unsupported 通过。GCP 无 Premium 样本；Azure 样本无 Statements 数据源，二者 **Not exercised** | 无；旧 NGM GET 结果不是本增量 SQL 契约的 parity 基线 |
| DP-REST Batch 1 | **Pass**：#1600 的四个 canonical facade、strict adapter、legacy characterization、OpenAPI 与 CI 通过 | **Pending** | **Pass**：info/capabilities、schema、单 query 和 2-item batch 均完成 legacy/canonical parity；canonical server-resolved target 和严格 body 边界通过 | legacy batch 继续要求其原有 caller-routing options；不带 options 的现有 `500` 未由本批引入 |
| DP-REST Batch 2 | **Pass**：cluster metrics/business logs canonical adapter、ownership/typed target、safe transport、legacy coexistence 与 Router/OpenAPI 检查通过 | **Pending** | **Pass**：Metrics instant/range/series 的 GET/POST 6 项和 Logs query-range/labels/label-values/series 4 项均为 `200`，稳定 payload parity；错误 org `404` | Loki series 具体 label-value 集合自身非确定；legacy/canonical 重复请求都存在，按稳定 status/count/schema 验收 |
| DP-REST Batch 3 | **Pass**：datasource-scoped System Logs alias、catalog fallback、strict routing、安全 Header、legacy coexistence 与 Router/OpenAPI 检查通过 | **Pending** | **Pass**：53 个 datasource catalog 一致；默认 AWS us-west-2 的 labels、values、series、query-range parity；未知 UID `404`、routing query `400` | 仅抽样默认 datasource，未逐一查询其他 52 个 datasource |
| DP-REST Batch 4 | **Blocked by resolver**：缺少 authoritative child org/cluster → live parent target contract；未注册 alias | **Pending / Not deployable** | **Pending / Not deployable** | legacy parent metrics/logs 继续依赖 `control_plane_info` |
| DP-REST Batch 5 | **Semantic review complete / no aliases**：org/tenant 与双资源授权语义待定 | **N/A：无新入口** | **N/A：无新入口** | 5 个 `/clinic-internal` POST API 保持原样 |

当前交付状态：

- Clinic [#1594](https://github.com/pingcap-inc/clinic/pull/1594)：已于 2026-07-20 合并为 `e5ea86bc`；最终 CI 通过。
- Clinic [#1596](https://github.com/pingcap-inc/clinic/pull/1596)：已合并为 `112ae191`；Top RU summary/user-stats 已部署 staging，当前为 Partial Pass。
- Clinic [#1600](https://github.com/pingcap-inc/clinic/pull/1600)：已于 2026-07-21 合并为 `59a35e0e`；SQL Statements 与 REST Batch 1/2/3 已部署 prod，并于 2026-07-22 完成本文记录的只读 E2E。
- prod `clinic`、`clinic-internal-api` 和 `clinic-data-service` 三个服务均已部署；Metadata/Activity/Components 的成功链路证明 Data Service → Clinic internal facade 可用，Top RU 证明 capability target resolver 可用。
- Canonical facade 继续复用现有 `/data-proxy` Prefix 和 `clinic_api_base`；没有为本批新增 app-delivery 配置、Secret、IAM 或 GitHub workflow。
- AWS Statements runtime 已由正向 Athena 查询证明可用；GCP/Azure 的 provider 级 prod 正向覆盖仍受样本约束。
- DP-REST Batch 4：resolver gate 未满足，不创建 canonical alias；Batch 5：semantic review 已完成，本轮不创建 alias。

## Data Proxy legacy compatibility 状态

| Legacy API | Compatibility guarantee | Local / Review | Staging | Prod |
| --- | --- | --- | --- | --- |
| `POST /data-proxy/query` | method/path、`clusterId`/`cluster_id`、caller options、默认 timeout 和错误/响应保持 | Pass：legacy query characterization 通过 | Pending：与 canonical query 对同一数据做稳定字段 parity | **Pass**：普通 SQL 与 Statements 均为 `200`；稳定字段与 canonical 一致 |
| `POST /data-proxy/batch-query` | item-level cluster、跨集群 batch 和 caller routing options 保持 | Pass：跨集群与 legacy batch shape characterization 通过 | Pending：legacy 单独回归；canonical 只验证 same-cluster 新契约 | **Pass（既有 options 契约）**：提供 caller-routing options 后 2-item batch `200`；只传 clusterId 的既有 `500` 不属于本批回归 |
| `GET /data-proxy/schema` | query `clusterId`、dataSource/vendor/region aliases 和 local-mode 行为保持 | Pass：legacy schema aliases/routing characterization 通过 | Pending：与 canonical schema 比较逻辑 schema | **Pass**：logical schema、columns 与 partition 和 canonical 一致 |
| `GET /data-proxy/info` | payload 保持；canonical `/capabilities` 只新增等价入口 | Pass：legacy info 与 canonical capabilities parity 测试通过 | Pending | **Pass**：service/version/vendors 与 canonical 等价 |
| `GET, POST /data-proxy/metrics/{cluster_id}/api/v1/{query,query_range,series}` | method/path、query/form 参数、Prometheus payload 和 cluster-only scope 保持 | Pass：新旧路由共存，canonical 调用同一 handler/service core | Pending | **Pass**：GET/POST 六项均与 canonical 稳定 payload parity |
| `GET /data-proxy/loki/{cluster_id}/api/v1/...` | 四条 Loki method/path、query 与响应保持 | Pass：新旧路由共存，canonical 调用同一 Loki proxy core | Pending | **Pass**：四项均为 `200`；稳定 status/count/schema parity |
| `GET /data-proxy/system-logs/api/v1/...` | 五条 privileged method/path、datasource aliases 与响应保持 | Pass：legacy route contract 与 canonical strict-routing tests 通过 | Pending | **Pass**：catalog 和默认 datasource 四项 Loki 请求与 canonical parity |
| `/data-proxy/serverless-parent-{metrics,logs}/api/v1/...` | 本轮不改；`control_plane_info` 及既有 query/form/Header 兼容保持 | Not modified；resolver gate 阻止新增 alias | N/A for canonical | N/A for canonical |

Clinic Slow Query Explorer 和 health-check 可以继续使用这些 legacy path。配套 nutshell skill 以 canonical helper/示例作为默认入口，并用显式 `clinic_legacy_*` helper 保留旧路径兼容。

## Clinic 主服务回归状态

| Clinic legacy API | #1594 改动与风险 | Local / CI | Staging | Prod |
| --- | --- | --- | --- | --- |
| Metadata dashboard / org list / detail | legacy Handler 与 datasource 未改；新增 internal adapter 只复用它们。风险低，主要关注共享 datasource 负载 | **Pass**：相关 package test/build 和 #1594 CI 通过 | **Pass**：exact、带 `pid` 的 list、detail enrichment 均为 `200`；canonical 核心字段一致 | **Pass**：dashboard exact 与 org detail 均 `200`，关键字段与 canonical DTO 映射一致 |
| Activity list | legacy Handler 与原 search client 已恢复为 `origin/master` 实现；strict transport policy 只供 internal/Data Proxy 使用。风险低 | **Pass**：legacy `null`、Header cursor、宽松参数及原 upstream 语义均有回归测试；strict 路径和 #1594 最终 CI 通过 | **Pass（有覆盖缺口）**：有数据、filter、legacy `null`/canonical `[]` 空窗口通过；cursor **Not exercised** | **Pass（cursor 未覆盖）**：7 天非空窗口 legacy/canonical 完整 JSON parity |
| Resource-pool components | legacy Handler 抽取到共享 core。风险低～中，必须保持无 CSRF 时的 mocked TiCDC 兼容 | **Pass**：deploy-type 分派和 mocked helper 测试、build、CI 通过 | **Pass（有覆盖缺口）**：resource-pool/premium parity 及 TiCDC mock compatibility 通过；dedicated **Not exercised** | **Pass**：Dedicated PD/TiDB/TiKV 实际 component 与 canonical parity；mock TiCDC 差异按既有契约排除 |
| NGM Top RU summary / user stats | NGM Handler 未改；新链只复用原有 O11y resolver package。风险低 | **Pass**：NGM package compile 与 O11y resolver tests 通过 | **Partial Pass**：Premium summary/user-stats 和 user 三态均为 `200` 且与新链完整 parity；Essential legacy 返回 `500 common.internal`，根因为 O11y registry 无 cluster target，新链将同一 resolver 失败稳定映射为 `502 BadGateway` | **Pass**：active Premium/Essential summary 与 user-stats 均为 `200`，固定历史窗口与 canonical 完整 JSON parity |
| NGM Statements GET suite | 本批不修改 NGM Statements Handler；新能力是独立 SQL 查询，不复制或替换五个 legacy GET 聚合契约。风险低 | **Not modified**：#1600 CI 通过；旧 GET 不在 canonical SQL parity 范围 | **Not exercised**：staging 不能验证 STMT-SQL 正向数据 | **Not exercised / out of scope**：本轮仅验证新增 SQL Statements；旧 GET Handler 无代码改动 |

Activity 的 URL 转义、默认 10 秒超时、拒绝 redirect/非 2xx/尾随 JSON 和响应大小限制只作用于 internal/Data Proxy；Clinic legacy 继续保留原 transport 和 JSON 契约。这是有意隔离的兼容策略，不是在 Data Proxy 重复实现 Activity 业务逻辑。

## 最近一次 staging 验收

2026-07-21 使用 staging Clinic key 对 `master-e5ea86bc…` 完成 Metadata、Activity 和 Components 正式公网验收，结论为 **Staging Pass with known coverage gaps**。Activity cursor 与 dedicated Components 因无可用 live 样本标记为 Not exercised。

同日对 Top RU `master-112ae191...` 完成公网验收，结论为 **Staging Partial Pass**：Premium legacy/new summary、user-stats、omitted `user`、`user=`、named user 均为 `200` 且规范化完整 JSON 一致，omitted 与 empty 在含 named-user 的样本上确实保持不同结果。无 key `401`、错误 org `404/NotFound`、未知 query 和缺必需参数 `400/InvalidArgument`、deferred config `404`、Starter/Resource Pool `400/InvalidArgument` 均符合契约；伪造全部 placement Header 仍为 `200` 且 body 不变。唯一 active Essential 样本在 legacy/new 两条链共同卡在 O11y registry 无 target，尚不能计为成功 parity。验收后两个新服务仍 Ready、restart=`0`。

## 最近一次 prod 验收

2026-07-22 使用 prod Clinic key 对 #1600 merge commit `59a35e0e` 部署后的公网入口完成只读验收。证据只记录状态、数量和稳定字段 parity，不保存 key、生产 SQL/log line、数据源 URL 或完整响应。

| 范围 | Prod 结果 | 覆盖与结论 |
| --- | --- | --- |
| Metadata | **Pass** | legacy exact/detail 与 canonical exact/detail/list 均 `200`；字段映射、list membership、错误 org `404`、无 key `401` 通过 |
| Activity | **Pass with gap** | 7 天窗口 3 条活动，legacy/canonical 完整 JSON parity；未产生 cursor，分页未覆盖 |
| Components | **Pass** | active Dedicated 返回 PD/TiDB/TiKV，排除 documented mock TiCDC 后完整 parity |
| Top RU | **Pass** | active Premium/Essential summary 与 user-stats 完整 JSON parity；Premium user 三态、ownership、参数和 placement 隔离通过 |
| Statements | **Partial Pass** | AWS Premium 有数据查询、authoritative routing 和 strict boundary 通过；AliCloud unsupported 通过；GCP 无样本，Azure 无 datasource |
| REST Batch 1 | **Pass** | info/capabilities、schema、query、2-item batch 四组 legacy/canonical parity；canonical 不依赖 caller routing options |
| REST Batch 2 | **Pass** | Metrics 6 项、Business Logs 4 项通过；Loki series 的具体 label set 上游自身非确定，稳定 status/count/schema 一致 |
| REST Batch 3 | **Pass** | catalog 返回 53 个 datasource；默认 AWS us-west-2 的 labels、values、series、query-range parity；未知 UID 与 routing alias 拒绝通过 |

关键 ID 边界：Dashboard 响应中的 `orgID` 是 Clinic internal ID，只用于 legacy Clinic path；canonical `{orgID}` 必须使用同一记录的 `tenantID`，即 TiDB Cloud/DBAAS org ID。把 internal `orgID` 放入 canonical path 会按预期返回 `404`。

## Staging / Prod 验收清单

每个环境一次性完成本批 API 验收，不再为单个接口重复创建临时 rollout：

1. Clinic legacy 回归：dashboard exact、带 `pid` 的 org list、detail enrichment、Activity 有/无数据与 cursor、Components 三类 tier 和 mocked TiCDC 兼容均符合确认后的旧契约。
2. Data Proxy 正向：所有 public path 返回 `200` 和预期 DTO；Metadata 共享字段一致，Activity payload/filter/cursor 正确，Components 覆盖真实 tier。
3. 隔离：错误 org、错误 cluster 返回 `404`；非法参数返回 `400`；无 key 返回 `401`。
4. Header：caller Authorization 只由 Gateway 消费且不透传 Clinic internal/upstream；伪造 placement/project/Cookie/legacy cursor Header 不得覆盖服务端目标；无效或缺失鉴权返回 `401`。
5. Top RU：Premium 与 Essential 分别对比 legacy/new summary、summaryplan/summary-plan 和 user-stats 的 status、完整 JSON 与关键聚合字段；使用同一 SQL 两个 plan 的样本确认返回两条 plan row，并覆盖省略 `user`、`user=`、named user、错误 org、非法 query、无 key、伪造 placement Header 和 O11y 错误映射。
6. REST Batch 1：使用同一 internal Clinic key 比较 `/query` 与 `/sql-queries`、`/batch-query` 与 `/sql-query-batches`、`/schema` 与 `/table-schemas`、`/info` 与 `/capabilities`。成功场景比较稳定 payload 字段和实际 provider/region，不比较 query ID/执行时长；覆盖无 key、错误 org、unknown/ownership mismatch、unknown JSON/query、body/count/timeout 上限、target identity mismatch 和伪造 placement Header。旧跨集群 batch 只做回归，canonical batch 只承诺 same-cluster。
7. REST Batch 2：同一 org/cluster 分别比较 3 组 metrics 与 4 组 business logs legacy/canonical；覆盖 GET/POST form、`match[]`、时间参数、错误 org/cluster、target identity mismatch、伪造 placement/routing 参数及上游非 2xx。只比较稳定 Prometheus/Loki payload，不比较动态执行元数据。
8. REST Batch 3：先比较 datasource catalog，再对同一 datasource UID 比较 4 组 legacy/canonical Loki 请求；覆盖未知 UID、非法 label、routing aliases、无 key、上游超时/非 2xx，并确认没有引入 org/cluster scope 或扩大 privileged audience。
9. SQL Statements：staging 部署后做 canonical/legacy 路由、缺 source、无 key 和 AliCloud unsupported smoke；prod 使用 AWS/GCP/Azure Premium 样本覆盖有数据、空根目录、canonical strict body、legacy 伪造 `options.dataSource/vendor/region`、缺 `date`、存储权限失败和损坏 Delta。旧 NGM GET 只做回归，不要与 STMT-SQL 做 response parity。
10. 运行状态：`clinic-internal-api`、`clinic-data-service` 及涉及 legacy 回归的 Deployment Ready，restart 无新增；验收窗口内无相关 panic/fatal/持续 5xx。
11. 记录：只保存脱敏的状态码、字段 parity、时间窗口、镜像/commit 和日志结论，不记录 key、真实 tenant/cluster ID 或完整敏感响应。

## 更新约定

后续每批 API 或每次环境验收只需更新本文五处：

- API 映射表；
- Local / Staging / Prod 状态；
- Clinic legacy 回归状态；
- 当前实现、部署和 consumer PR；
- 最近一次验收的日期、commit/image 与脱敏证据。

面向调用方与最终 skill 的稳定说明维护在[Data Proxy API 调用指南](./data-proxy-api-caller-guide.md)。

历史架构的结果必须留在“历史基线”栏；当前实现重新部署后必须重新验收。
