@@ -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. */
110161const ROLLING : readonly DateRangePreset [ ] = [ 'last_7_days' , 'last_30_days' , 'last_90_days' ] ;
111162
@@ -117,7 +168,7 @@ interface ThrownEnvelope {
117168
118169async 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