Skip to content

Commit 1dd11a1

Browse files
committed
feat(core): hold every analytics face to the dateRange array ARITY
The shared conformance kit had exactly one array-arm case — a two-element window — so the arity itself was governed nowhere and each face was free to invent a reading for `['2026-01-01']`, `[]`, `[a, b, c]` and `[null, null]`. Adds `ANALYTICS_DATE_RANGE_NOT_A_WINDOW` and the case that holds every REGISTERED face to the rule PR #17593 already landed on the service-analytics faces: a non-two-bound array is refused with the ADR-0112 ANALYTICS_DATE_RANGE_UNRECOGNIZED / 400 envelope. No existing case is weakened — the two-element window case is this one's control. Claude-Session: https://claude.ai/code/session_01RuoNSXUbBoWHkNS4AknTrM Co-authored-by: Claude <noreply@anthropic.com>
1 parent 49cd715 commit 1dd11a1

1 file changed

Lines changed: 97 additions & 8 deletions

File tree

packages/core/src/utils/analytics-date-range-conformance.ts

Lines changed: 97 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,16 @@ export interface AnalyticsDateRangeFace {
7070
/**
7171
* Lower one `dateRange` and report the window. ⛔ Must let a refusal
7272
* PROPAGATE — the kit reads the thrown envelope's `code` and `status`.
73+
*
74+
* ⚠️ The array arm is `readonly unknown[]`, not `readonly string[]`: the
75+
* ARITY case below drives shapes the schema's `z.array(z.string())` types
76+
* away but a real caller still reaches a face with — `[]` and `[null, null]`
77+
* among them (`POST /analytics/dataset/query` types its selection from
78+
* `AnalyticsQuery` and never Zod-parses it). A runner whose own parameter is
79+
* the narrower type still satisfies this — the declaration is a METHOD, so
80+
* its parameter is bivariant — and needs no change.
7381
*/
74-
lower(range: string | readonly string[]): Promise<LoweredDateRangeWindow>;
82+
lower(range: string | readonly unknown[]): Promise<LoweredDateRangeWindow>;
7583
}
7684

7785
/**
@@ -106,6 +114,49 @@ export const ANALYTICS_DATE_RANGE_EXPLICIT_WINDOW: readonly [string, string] = [
106114
'2026-09-30T00:00:00.000Z',
107115
];
108116

117+
/**
118+
* [#17596] ⛔ Array arms that do not denote a window — the ARITY case's inputs,
119+
* each a shape an author or a generator really writes, ⛔ not fuzz.
120+
*
121+
* The kit's only array case used to be the two-element window above, so the
122+
* arity itself was governed NOWHERE and every face was free to invent a
123+
* reading for the rest. Four faces in one package had invented three —
124+
* MEASURED on `abc4b83ce` (#17124), one authored document over the same rows:
125+
* `['2026-01-01']` was a point window, an upper bound left unwritten, and a
126+
* window dropped to ALL OF HISTORY, depending on which backend answered. A
127+
* fifth face — `driver-memory`'s cube face — dropped it too (#17596, measured
128+
* end to end: the one-element array emitted a pipeline byte-identical to one
129+
* with no `dateRange` at all).
130+
*
131+
* ⭐ The rule asserted here is NOT invented for the kit: it is the one PR
132+
* #17593 already landed on the `service-analytics` faces — a non-two-bound
133+
* array is refused with the ADR-0112 `ANALYTICS_DATE_RANGE_UNRECOGNIZED` / 400
134+
* envelope — stated once here so every REGISTERED face is held to it instead
135+
* of one package pinning it for itself.
136+
*
137+
* ⛔ Why a refusal and not an alignment: all three readings are ungoverned, and
138+
* teaching every face the same guess is the "align them independently" shape
139+
* the kit exists to end. What IS governed is the contract the spec's own
140+
* refusal wording states — *an explicit window is the two-element array
141+
* [start, end]* — and the #16322 migration table, which tells an author to
142+
* write a single day as `['2026-01-20', '2026-01-20']`. TWO bounds.
143+
*
144+
* ⚠️ The two-element window case above is this case's CONTROL and is load
145+
* bearing: without it, "refuse every array" would satisfy the whole array arm.
146+
*/
147+
export const ANALYTICS_DATE_RANGE_NOT_A_WINDOW: readonly (readonly unknown[])[] = [
148+
// The card's own shape: one bound, which is not a window.
149+
['2026-01-01'],
150+
// No bounds at all — a generator that filtered its list to nothing.
151+
[],
152+
// Three bounds: which two? Every face that answered picked a different pair.
153+
['2026-09-01T00:00:00.000Z', '2026-09-30T00:00:00.000Z', '2026-10-31T00:00:00.000Z'],
154+
// Two bounds of the right ARITY that are not dates — the shape that reached
155+
// `parseUTC(null)` as a bare `TypeError` on one face, and lowered to the
156+
// string `'null'` on another. Two bounds is necessary, not sufficient.
157+
[null, null],
158+
];
159+
109160
/** The three presets whose upper bound is NOW rather than a calendar boundary. */
110161
const ROLLING: readonly DateRangePreset[] = ['last_7_days', 'last_30_days', 'last_90_days'];
111162

@@ -117,7 +168,7 @@ interface ThrownEnvelope {
117168

118169
async function attempt(
119170
face: AnalyticsDateRangeFace,
120-
range: string | readonly string[],
171+
range: string | readonly unknown[],
121172
): Promise<{ window: LoweredDateRangeWindow } | { refusal: ThrownEnvelope }> {
122173
try {
123174
return { window: await face.lower(range) };
@@ -185,6 +236,29 @@ export async function analyticsDateRangeConformanceFindings(
185236

186237
// ── Everything else is REFUSED, with one envelope ────────────────────────
187238
const envelopes = new Set<string>();
239+
/**
240+
* ⭐ ONE judgement for every refusal this kit demands — the STRING arm's
241+
* out-of-vocabulary spellings and the ARRAY arm's non-windows — so the two
242+
* arms cannot drift into two envelopes for one condition. ⛔ The thrown
243+
* MESSAGE is quoted only when there is no `code` at all, which is the case
244+
* where the text is the only evidence of what the face actually did (a face
245+
* that emitted no window and threw something of its own reads exactly like
246+
* one that refused, until you read it).
247+
*/
248+
const judgeRefusal = (input: unknown, refusal: ThrownEnvelope): void => {
249+
if (refusal.code !== 'ANALYTICS_DATE_RANGE_UNRECOGNIZED') {
250+
say(
251+
`refused ${JSON.stringify(input)} with code ${String(refusal.code)}, `
252+
+ `not ANALYTICS_DATE_RANGE_UNRECOGNIZED`
253+
+ (refusal.code === undefined ? ` (${refusal.message ?? 'no message'})` : ''),
254+
);
255+
}
256+
if (refusal.status !== 400) {
257+
say(`refused ${JSON.stringify(input)} with status ${String(refusal.status)}, not 400`);
258+
}
259+
envelopes.add(JSON.stringify({ code: refusal.code, status: refusal.status }));
260+
};
261+
188262
for (const bad of ANALYTICS_DATE_RANGE_REFUSED_SPELLINGS) {
189263
const got = await attempt(face, bad);
190264
if ('window' in got) {
@@ -194,14 +268,29 @@ export async function analyticsDateRangeConformanceFindings(
194268
);
195269
continue;
196270
}
197-
if (got.refusal.code !== 'ANALYTICS_DATE_RANGE_UNRECOGNIZED') {
198-
say(`refused ${JSON.stringify(bad)} with code ${String(got.refusal.code)}, not ANALYTICS_DATE_RANGE_UNRECOGNIZED`);
199-
}
200-
if (got.refusal.status !== 400) {
201-
say(`refused ${JSON.stringify(bad)} with status ${String(got.refusal.status)}, not 400`);
271+
judgeRefusal(bad, got.refusal);
272+
}
273+
274+
// ── [#17596] ARITY: an array that is not TWO bounds is not a window ───────
275+
//
276+
// ⭐ The rule PR #17593 landed on the service-analytics faces, stated once
277+
// for every registered face. ⛔ Reported per SHAPE rather than as one
278+
// verdict: which arities a face answers is the finding — a face that
279+
// refuses `[]` and answers `['2026-01-01']` has not adopted the rule, it has
280+
// grown a fourth reading.
281+
for (const bad of ANALYTICS_DATE_RANGE_NOT_A_WINDOW) {
282+
const got = await attempt(face, bad);
283+
if ('window' in got) {
284+
say(
285+
`ANSWERED the ${bad.length}-element array ${JSON.stringify(bad)} with `
286+
+ `[${got.window.start}, ${got.window.end}] instead of refusing — an explicit window is `
287+
+ 'the TWO-element array [start, end], so every other arity is a window this face INVENTED',
288+
);
289+
continue;
202290
}
203-
envelopes.add(JSON.stringify({ code: got.refusal.code, status: got.refusal.status }));
291+
judgeRefusal(bad, got.refusal);
204292
}
293+
205294
if (envelopes.size > 1) {
206295
say(`raised ${envelopes.size} different envelopes for one condition — ADR-0112 asks for one`);
207296
}

0 commit comments

Comments
 (0)