Skip to content

Two packages/spec route response schemas declare a data NARROWER than the contract their route relays — AnalyticsResultResponseSchema and TriggerFlowResponseSchema #13078

Description

@os-litant

Found while binding the five in-repo return res.json() methods of #12104. Out of that
card's declared scope and deliberately not fixed there
— that card binds client return types;
this is a producer-side declaration defect in packages/spec, and it changed what #12104 could
bind.

ⓘ Angle brackets are spaced throughout (Promise< any >) on purpose — GitHub's body sanitizer
strips tag-shaped spans, backticks and fenced code included.

The defect

Both schemas below look like the authoritative response contract for their route. Both declare a
data that is a strict subset of what the route's producer declares and actually serves. A
consumer that binds either — which is exactly what a sweep reaches for — ships a declaration
that refuses reads the wire really carries.

1. AnalyticsResultResponseSchema (packages/spec/src/api/analytics.zod.ts)

POST /analytics/query ends deps.success(await analyticsService.query(body, ctx)), so data
is IAnalyticsService.query's declared return, AnalyticsResult
(packages/spec/src/contracts/analytics-service.ts).

declared on the schemadeclared on AnalyticsResult
rowsrows
fields: { name, type }[]fields: { name, type, label?, format?, currency?, percentScale? }[]
sql?sql?
totals?: { dimensions, rows }[]

fields[].label is not hypothetical: it is served by a real AnalyticsService, measured in
packages/client/src/analytics-automation-json-erasure.test.ts. currency and percentScale
are the ADR-0053 / objectui#3136 renderer chains, and totals is the ADR-0021 marginal-aggregate
channel — every one of them a key a dashboard reads.

2. TriggerFlowResponseSchema (packages/spec/src/api/automation-api.zod.ts)

POST /automation/:name/trigger (and the legacy POST /automation/trigger/:name) end
deps.success(result) where result is IAutomationService.execute's declared return,
AutomationResult.

declared on the schemadeclared on AutomationResult
success, output?, error?, durationMs?those four, plus code?, status?, runId?, screen?, successMessage?, errorMessage?, summary?

The missing members are not decoration. status: 'paused' + runId + screen is the whole
third state of the #9378 / #9510 trigger contract — the payload a caller resumes a screen flow
with — and the SDK's own docblock on automation.trigger tells callers to read exactly those.

Why this is the same shape #6442 already fixed once, in this same file

AnalyticsMetadataResponseSchema used to declare { cubes: Cube[] } for an endpoint that has
always answered a bare CubeMeta[]. The maintainer ruled (2026-08-08) "narrow the declaration",
and the schema's own docblock records the reasoning: the TS contract already agreed with the
runtime and the schema was the lone outlier. These two are the same class, caught the same
way — by reading the producer's declared return next to the schema.

Remedy shape (not prescribed — this is the question the card carries)

Either bring each schema's data up to its producer's contract, or state in each docblock that
the schema is a deliberate PROJECTION and name what a consumer must not bind it to. What must
not stay is the current state: a response schema that reads as the route's contract, is
narrower than it, and has no note saying so. AnalyticsResultResponse additionally has no
exported type at all
(protocol.zod.ts keeps a module-local z.infer alias), so a consumer
cannot name it even if it were right.

What #12104 did about it, so this is not re-derived

client.analytics.query and client.automation.trigger were bound to the producer contracts
(BaseResponse & { data: AnalyticsResult } / BaseResponse & { data: AutomationResult })
rather than to these two schemas, and the near-miss is pinned in
packages/client/src/return-type-precision.test.ts so a later sweep cannot quietly retarget
them. Their two siblings, AnalyticsMetadataResponseSchema and AnalyticsSqlResponseSchema,
DO agree with their producer and were used as-is.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions