Skip to content

Commit d310c55

Browse files
committed
test+changeset: pin the compareTo.kind refusal and grade it patch
Claude-Session: https://claude.ai/code/session_01ToDPcx9AESFubJkDiFMtKW Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4eb9258 commit d310c55

1 file changed

Lines changed: 54 additions & 0 deletions

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
"@objectstack/service-analytics": patch
3+
---
4+
5+
fix(service-analytics): an unrecognised `compareTo.kind` is refused, not answered with a previous-period window under a 200 (#17550)
6+
7+
`shiftRange` had one branch and a fall-through — `previousYear` was named, and
8+
**everything else** landed in the `previousPeriod` arm. No `default`, no
9+
exhaustiveness check. So `compareTo: { kind: 'previousQuarter' }` came back as a
10+
previous-period comparison under an ordinary **200**, and the caller was told
11+
nothing. The wrong answer is a comparison **window**: a number a dashboard
12+
renders and a person reads as fact, with no status, header or field in the
13+
response to distinguish it from a real answer.
14+
15+
`DatasetCompareTo.kind` has only ever declared two values
16+
(`'previousPeriod' | 'previousYear'`), but `DatasetSelection` is a TypeScript
17+
interface with no Zod schema anywhere, and `/analytics/dataset/query`'s door
18+
parses only the seven members the selection shares with `AnalyticsQuery` —
19+
`compareTo` is one of the four it projects away before its parse, and the route
20+
forwards the caller's selection to the service untouched. So `kind` was checked
21+
by `tsc` inside this repo and by nothing at all on the wire.
22+
23+
## FROM → TO
24+
25+
| Input | Was | Now |
26+
|:--|:--|:--|
27+
| `compareTo: { kind: 'previousPeriod' }` | the equal-length window before | **unchanged** |
28+
| `compareTo: { kind: 'previousYear' }` | the same window one year back | **unchanged** |
29+
| `compareTo: { kind: <anything else> }` | a previous-period window, **200** | `DATASET_INVALID` / **400**, naming the value received and both legal ones |
30+
31+
The fix is to name one of the two declared windows, or drop `compareTo` — which
32+
is what the refusal says. No accept set widens, no new error code is minted: the
33+
refusal is the fourth member of the `datasetInvalidError` family
34+
`resolveCompareDimension` already raises three times for the same document, so it
35+
arrives at the route through the envelope that route already classifies on.
36+
37+
## Why this is a `patch`
38+
39+
It pulls behaviour back onto the contract the type has always declared, rather
40+
than narrowing past it: every input `DatasetCompareTo` permits returns
41+
byte-identical windows, pinned by a control in the same change. What flips from
42+
200 to 400 is input the declared contract never permitted. The reachable-today
43+
population for that input was measured on the tree — the dashboard authoring path
44+
is already doored (`DashboardWidgetSchema` parses the widget's `kind` as a
45+
`z.enum`, so a third kind cannot arrive through a parsed widget), and no producer
46+
in this repository sends a third value. What is not enumerable from here is a
47+
consumer outside it calling the published `shiftRange` export, or posting a
48+
hand-rolled body to the dataset route; for those, the refusal replaces a wrong
49+
answer with a located one.
50+
51+
`alignedCompareBucketKey` reads the same two-valued `kind` and deliberately gains
52+
no refusal of its own: it is not on the package's public surface, and its only
53+
caller runs `shiftRange` first — both pinned, so exporting it turns the pin red
54+
rather than silently reopening this defect.

0 commit comments

Comments
 (0)