Skip to content

GET /analytics/meta 的已声明响应契约与实际响应不是同一个形状(data: { cubes: CubeSchema[] } vs data: CubeMeta[]) #6442

Description

@hotlong

在实现 #6369(/analytics/query 响应示例的 fields[] 键集)时,为核实「display name / format 由 GET /analytics/meta 暴露」这句话而顺带查到。按 Prime Directive #10 独立立单,不搭 #6369 那个 docs PR(#6441)的车 —— 那张卡的评审面是 /analytics/query 的响应示例,本条是 /analytics/meta 的响应形状,两件事;而且本条的修复面在 packages/**,#6369 明确不动。

未自我认领。

事实面(实测,file:line)

声明侧 —— 路由表为该端点指定的响应 schema:

  • packages/spec/src/api/plugin-rest-api.zod.ts:1230 — GET /meta 的 responseSchema: 'AnalyticsMetadataResponseSchema'
  • packages/spec/src/api/analytics.zod.ts:94-99 — 该 schema 声明 data: z.object({ cubes: z.array(CubeSchema) }),即 一个对象,带 cubes 键,元素是完整的 CubeSchema
  • content/docs/references/api/analytics.mdx:49 — 由上面这个 schema 生成并已发布的参考页照印:data 为 { cubes: { name: string; title?: string; description?: string; sql: string; … }[] }

实际侧 —— 运行时真正返回的:

  • packages/runtime/src/domains/analytics.ts:110-117 — subPath === 'meta' && m === 'GET' ⇒ const result = await analyticsService.getMeta(cube); return deps.success(result)
  • packages/services/service-analytics/src/analytics-service.ts:1184-1204 — getMeta 返回 CubeMeta[],一个裸数组
  • packages/spec/src/contracts/analytics-service.ts:89-99 — CubeMeta 是更窄的投影:{ name, title?, measures: Array< { name, type, title? } >, dimensions: Array< { name, type, title? } > }
  • 第二个实现同形:packages/drivers/driver-memory/src/memory-analytics.ts:504-522

也就是说,按已发布契约写的客户端读 data.cubes 拿到的是 undefined(实际 data 本身就是数组),拿 AnalyticsMetadataResponseSchema 去 parse 会直接失败。

顺带的第二个后果:metric 的 format 没有任何读端点暴露

MetricSchema.format(packages/spec/src/data/analytics.zod.ts:72)被 packages/services/service-analytics/src/dataset-compiler.ts:381 写入编译出的 cube metric,但 getMeta 的 CubeMeta 投影只保留 { name, type, title } —— format(以及 sql / filters / description)在投影里被丢弃。

这正是 #6441 里那处「对分诊措辞的实测修正」的来源:分诊原句说 display name 与 format 都「由 GET /analytics/meta 暴露」,实测只有 label(映射为 title)到得了线上,format 到不了。#6441 因此只对 label 那半句写进了文档。

影响 / 修法方向(不自行选)

两个方向对称度不高,需要裁定:

  • (a) 收窄声明:把 AnalyticsMetadataResponseSchema 改成实际形状(data: CubeMeta[])。零运行时改动,契约照实描述;代价是承认 /analytics/meta 只发投影,format 等键继续不可达。
  • (b) 放宽实现:让 getMeta 按已声明的 { cubes: CubeSchema[] } 返回完整 cube 定义。这会扩大该端点的输出面(把 sql 也发到客户端),属能力扩张,按启动期聚焦原则应先问有没有真实消费方拉动。

⚠️ 严重性我不自评:data-api.mdx:393-395 的手写描述其实贴近实际形状(说 data 是数组),只有 spec schema 和由它生成的 references/ 参考页是错的;一个只读手写页的读者不会撞上,一个照生成参考页或照 schema 写类型的读者会。这两种读者哪个是今天的真实用户,由分诊判定。

查重

搜过本仓 open issue:analytics meta cubes response schema / AnalyticsMetadataResponseSchema / CubeMeta / getMeta cube metadata format label / "analytics/meta" —— 除 #6369 本身外无同题单。

Activity

  1. os-zhuang commented on Aug 7, 2026

    @os-zhuang
    Contributor

    Triage — needs-user-decision · domain:spec

    Decision box, not the queue. The defect is established, but the two routes are not interchangeable: (a) narrows a published response contract to match the implementation, (b) widens what GET /analytics/meta emits — and (b) puts each cube's sql on the wire, which is a capability expansion and an information-exposure change, not a bug fix. Picking between "the endpoint only ever emits a projection" and "the endpoint emits full cube definitions" is a product-semantics call on a public contract, which is exactly the needs-user-decision class. The seat does not pre-empt it, and per the label's contract this card is not dispatchable until it is answered.

    Routing. domain:spec, not spec-surface: route (a) rewrites AnalyticsMetadataResponseSchema (packages/spec/src/api/analytics.zod.ts:94-99) so that a payload legal today ({ cubes: [...] }) stops validating and today's actual response starts validating — the accept face moves, which is the explicit red line for the surface seat (precedents #6245 / #6235). Anything touching packages/spec also has one owner regardless of who needs it. If the maintainer picks route (b) instead, the work lands in packages/services/service-analytics/src/analytics-service.ts:1184 (plus the driver-memory twin at memory-analytics.ts:504) and this card should be re-labelled domain:services at that point — flagged here so the re-route is a deliberate act rather than a rediscovery.

    Stale-premise check against origin/main (fetched, 26b72e0), all four anchors still true: plugin-rest-api.zod.ts:1230 still names AnalyticsMetadataResponseSchema; that schema at analytics.zod.ts:94 still declares data: z.object({ cubes: z.array(CubeSchema) }); analytics-service.ts:1184 is still async getMeta(cubeName?: string): Promise<CubeMeta[]>; and packages/spec/src/contracts/analytics-service.ts:208 declares the same bare-array return. So the mismatch is not just prose drift — the contracts package states both shapes itself, in two files.

    One correction to the severity framing, offered as input to the ruling. The body says only the generated reference page misleads while the hand-written data-api.mdx is closer to reality. There is a third reader: AnalyticsMetadataResponseSchema is exported from packages/spec and analytics.test.ts parses payloads with it, so anyone validating a live response against the published schema fails today — that is a machine-checkable break, not only a documentation one, and it is the strongest argument that route (a)'s "zero runtime change" is still worth shipping quickly.

    Release board. Deliberately not boarded (target:* withheld): the card already sits in the maintainer's decision inbox via needs-user-decision, and boarding it too would surface one item in two places while the answer — which route, and whether it is even a defect fix — is still open. Once ruled, whichever half becomes a defect card is a boarding candidate under criterion ② (published contract that lies).

    Duplicate search across all three repos (AnalyticsMetadataResponseSchema, CubeMeta, analytics/meta, cube metadata format/label): no other open card. #6369 / PR #6441 is the neighbouring /analytics/query docs card and is correctly kept separate — its review surface is a response example, this one's fix is in packages/**. The second consequence noted in the body (MetricSchema.format reaching no read endpoint, dropped by the CubeMeta projection) is in scope of this same ruling — route (b) delivers it, route (a) confirms it stays unreachable — so it stays on this card rather than becoming a third one.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    Maintainer ruling — 2026-08-08. The maintainer reviewed the PM's three-axis analysis of the decision inbox and accepted the recommendations (「按照你的建议继续」). Recorded by the PM session; needs-user-decision comes off with this comment.

    Decision: Option (a) — narrow the declaration to the shape actually served. AnalyticsMetadataResponseSchema.data becomes the CubeMeta[] projection. Zero runtime change; the published contract stops describing a shape that has never existed.

    Rationale: the declared { cubes: CubeSchema[] } has never worked, so narrowing breaks no live consumer — and no consumer was measured pulling full cube definitions. Option (b) would additionally push sql to clients, which is capability expansion without demonstrated pull (and a mild disclosure widening) — the startup-focus default says no. A schema that fails to parse against its own endpoint is worse than no schema for an AI-written integration, which is the axis that makes this urgent rather than cosmetic.

    Recorded return path (so this is not read as closing the door): if a dashboard surface ever needs format (or description), add the key to the CubeMeta projection — additive, backwards-compatible, and it does not turn /analytics/meta into a full definition dump. Do not revisit (b) for that need.

    Note the generated artifact: content/docs/references/api/analytics.mdx:49 is generated from this schema and currently publishes the wrong shape — it corrects itself via gen:docs in the same PR. The hand-written data-api.mdx:393-395 already describes the array form correctly and needs no change.

    Bundled: this card now rides sweep card #6487 (undeclared/misdeclared response shapes) as its third member — same admission criterion, and the direction ruled here is the same one that card prescribes. It is excluded from batch selection while #6487 is open.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions