Skip to content

Commit ceb4877

Browse files
claude[bot]claude
andauthored
docs(spec): the element-number D3 entry names the route hop — arrays are lowered by parseFilterAST before the analytics wire (#15828) (#15941)
The `element-number-filter-rule-array` semantic migration entry recorded its runtime prerequisite at the wrong hop: "authored array → adapter lowering → filter AST → accepted by `lowerAnalyticsWhere`". Per the `FilterArray` docblock (#5158 ruling C) a `FilterArray` is input-only sugar lowered at the single sink `parseFilterAST`; the wire's door is `POST /analytics/query`, which parses `where` with `AnalyticsQueryRequestSchema` (a `FilterCondition` only) before any service code runs. `lowerAnalyticsWhere` is the in-process door (#5334) for direct `analyticsService.query` callers, not the wire's. Prose only: the entry's reason clause, one sentence on `AnalyticsQuerySchema.where`'s `.describe()`, the regenerated migration registry and the two regenerated docs reference pages. No schema, accept-set, runtime or test-behaviour change. The adapter-side fix is objectui#7752. Claude-Session: https://claude.ai/code/session_01TezFG8ZMrNH6n5VTNpPpdH Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0cf0867 commit ceb4877

6 files changed

Lines changed: 49 additions & 13 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
Documentation: the analytics `where` contract and the `element:number` D3 entry now name the hop an array filter is lowered at.
6+
7+
Text only — no schema, accept-set, runtime or test behaviour changes. `AnalyticsQuerySchema.where` is still `FilterConditionSchema` and still refuses an array, which is the protocol working as `FilterArray`'s docblock (#5158 ruling C) declares it: a `FilterArray` is input-only authoring sugar, lowered to a `FilterCondition` at the single sink `parseFilterAST` (`@objectstack/spec/data`) the moment it arrives, and only the lowered `FilterCondition` travels any further.
8+
9+
- `AnalyticsQuerySchema.where`'s `.describe()` gains one sentence pointing array authors at that lowering: an authored `FilterArray` is lowered by `parseFilterAST` on the client before the wire, and this field admits only the lowered `FilterCondition`. It lands in the generated `content/docs/references/{api,data}/analytics.mdx` prop tables, which is where an author reads it.
10+
- The `element-number-filter-rule-array` semantic migration entry recorded its runtime prerequisite one hop too late: "authored array → adapter lowering → filter AST → accepted by `lowerAnalyticsWhere`". `lowerAnalyticsWhere` (`service-analytics`) is the in-process door (#5334) for callers reaching `analyticsService.query` directly. The wire's door is the runtime route `POST /analytics/query`, which parses `where` with `AnalyticsQueryRequestSchema` before any service code runs, so an un-lowered array is refused there. The entry's reason clause now names that route hop and the `parseFilterAST` lowering the adapter owes before the wire (#15828; the adapter-side fix is objectui#7752).
11+
12+
The sibling entry `element-record-picker-filter-rule-array` was read for the same claim and does not make it — its measured path is `find()` / `convertQueryParams`, not the analytics wire — so it is unchanged.

content/docs/references/api/analytics.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ const result = AnalyticsEndpoint.parse(data);
8282
| **cube** | `string` || Target cube name |
8383
| **measures** | `string[]` || List of metrics to calculate |
8484
| **dimensions** | `string[]` | optional | List of dimensions to group by |
85-
| **where** | `any` | optional | Filtering criteria (canonical Query DSL FilterCondition) |
85+
| **where** | `any` | optional | Filtering criteria (canonical Query DSL FilterCondition). An authored `FilterArray` is lowered by `parseFilterAST` on the client before the wire; this field admits only the lowered `FilterCondition` (see `FilterArray` in `data/filter.zod.ts`). |
8686
| **timeDimensions** | `{ dimension: string; granularity?: Enum<'second' \| 'minute' \| 'hour' \| 'day' \| 'week' \| 'month' \| 'quarter' \| 'year'>; dateRange?: string \| string[] }[]` | optional | |
8787
| **order** | `Record<string, Enum<'asc' \| 'desc'>>` | optional | |
8888
| **limit** | `number` | optional | |

content/docs/references/data/analytics.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const result = AggregationMetricType.parse(data);
5555
| **cube** | `string` | optional | Target cube name (optional when provided externally, e.g. in API request wrapper) |
5656
| **measures** | `string[]` || List of metrics to calculate |
5757
| **dimensions** | `string[]` | optional | List of dimensions to group by |
58-
| **where** | `any` | optional | Filtering criteria (canonical Query DSL FilterCondition) |
58+
| **where** | `any` | optional | Filtering criteria (canonical Query DSL FilterCondition). An authored `FilterArray` is lowered by `parseFilterAST` on the client before the wire; this field admits only the lowered `FilterCondition` (see `FilterArray` in `data/filter.zod.ts`). |
5959
| **timeDimensions** | `{ dimension: string; granularity?: Enum<'second' \| 'minute' \| 'hour' \| 'day' \| 'week' \| 'month' \| 'quarter' \| 'year'>; dateRange?: string \| string[] }[]` | optional | |
6060
| **order** | `Record<string, Enum<'asc' \| 'desc'>>` | optional | |
6161
| **limit** | `number` | optional | |

packages/spec/src/data/analytics.zod.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,11 @@ export const AnalyticsQuerySchema = lazySchema(() => strictObject(
296296
* { where: { is_active: true, stage: { $nin: ['lost'] } } }
297297
* ```
298298
*/
299-
where: FilterConditionSchema.optional().describe('Filtering criteria (canonical Query DSL FilterCondition)'),
299+
where: FilterConditionSchema.optional().describe(
300+
'Filtering criteria (canonical Query DSL FilterCondition). An authored `FilterArray` is '
301+
+ 'lowered by `parseFilterAST` on the client before the wire; this field admits only the '
302+
+ 'lowered `FilterCondition` (see `FilterArray` in `data/filter.zod.ts`).'
303+
),
300304

301305
/**
302306
* Time-bucketed dimensions. Strict as of #4001 batch D — and this item is

packages/spec/src/migrations/entries/semantic/18.element-number-filter-rule-array.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,21 @@ export const entry: SemanticMigration = {
2424
+ 'the filter a list view stores and renders was refused by the KPI element beside it, '
2525
+ 'and the objectui parity gate had to carry a reasoned exemption to look away. The '
2626
+ 'convergence was sequenced consumer-first (ruling recorded 2026-08-25, Option A): '
27-
+ 'objectui#6828 made `ObjectStackAdapter.aggregate()` lower a rule array through the '
28-
+ 'same `translateFilterArray` its `find()` path runs before the analytics wire, and the '
29-
+ 'objectui pin carrying it was re-measured before this entry moved — authored array → '
30-
+ 'adapter lowering → filter AST → accepted by `lowerAnalyticsWhere`, which still refuses '
31-
+ 'a RAW rule-object array by design. The ruled migration check ran with the change: the '
27+
+ 'objectui#6828 made `ObjectStackAdapter.aggregate()` run the same `translateFilterArray` '
28+
+ 'its `find()` path runs, and the objectui pin carrying it was re-measured before this '
29+
+ 'entry moved — but that measurement named the wrong hop, and #15828 corrects it here. '
30+
+ '`translateFilterArray` yields AST tuples, which are still a `FilterArray` — input-only '
31+
+ 'sugar — so the real path is: authored array → `translateFilterArray` → lowered by '
32+
+ '`parseFilterAST` (`@objectstack/spec/data`, the single sink the `FilterArray` docblock '
33+
+ 'names, #5158 ruling C) in the adapter, BEFORE the wire → a `FilterCondition` on the '
34+
+ 'body. The hop that decides it is the runtime route `POST /analytics/query`, which '
35+
+ 'parses `where` with `AnalyticsQueryRequestSchema` — a `FilterCondition` and nothing '
36+
+ 'else — so an un-lowered array is refused there before any service code runs. '
37+
+ '`lowerAnalyticsWhere` (`service-analytics`), where that earlier measurement stopped, '
38+
+ 'is the IN-PROCESS door (#5334) for callers reaching `analyticsService.query` '
39+
+ "directly, not the wire's; it too still refuses a RAW rule-object array by design. "
40+
+ 'objectui#7752 lands the adapter-side lowering. '
41+
+ 'The ruled migration check ran with the change: the '
3242
+ 'sweep of first-party corpora (examples/, skills/, create-objectstack, content/docs/, '
3343
+ 'packages/apps/, spec fixtures) found ONE `element:number` author writing a record-form '
3444
+ '`filter` — a spec test fixture, rewritten to the array form in the same change — and '

packages/spec/src/migrations/registry.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6441,11 +6441,21 @@ const step18: MigrationStep = {
64416441
+ 'the filter a list view stores and renders was refused by the KPI element beside it, '
64426442
+ 'and the objectui parity gate had to carry a reasoned exemption to look away. The '
64436443
+ 'convergence was sequenced consumer-first (ruling recorded 2026-08-25, Option A): '
6444-
+ 'objectui#6828 made `ObjectStackAdapter.aggregate()` lower a rule array through the '
6445-
+ 'same `translateFilterArray` its `find()` path runs before the analytics wire, and the '
6446-
+ 'objectui pin carrying it was re-measured before this entry moved — authored array → '
6447-
+ 'adapter lowering → filter AST → accepted by `lowerAnalyticsWhere`, which still refuses '
6448-
+ 'a RAW rule-object array by design. The ruled migration check ran with the change: the '
6444+
+ 'objectui#6828 made `ObjectStackAdapter.aggregate()` run the same `translateFilterArray` '
6445+
+ 'its `find()` path runs, and the objectui pin carrying it was re-measured before this '
6446+
+ 'entry moved — but that measurement named the wrong hop, and #15828 corrects it here. '
6447+
+ '`translateFilterArray` yields AST tuples, which are still a `FilterArray` — input-only '
6448+
+ 'sugar — so the real path is: authored array → `translateFilterArray` → lowered by '
6449+
+ '`parseFilterAST` (`@objectstack/spec/data`, the single sink the `FilterArray` docblock '
6450+
+ 'names, #5158 ruling C) in the adapter, BEFORE the wire → a `FilterCondition` on the '
6451+
+ 'body. The hop that decides it is the runtime route `POST /analytics/query`, which '
6452+
+ 'parses `where` with `AnalyticsQueryRequestSchema` — a `FilterCondition` and nothing '
6453+
+ 'else — so an un-lowered array is refused there before any service code runs. '
6454+
+ '`lowerAnalyticsWhere` (`service-analytics`), where that earlier measurement stopped, '
6455+
+ 'is the IN-PROCESS door (#5334) for callers reaching `analyticsService.query` '
6456+
+ "directly, not the wire's; it too still refuses a RAW rule-object array by design. "
6457+
+ 'objectui#7752 lands the adapter-side lowering. '
6458+
+ 'The ruled migration check ran with the change: the '
64496459
+ 'sweep of first-party corpora (examples/, skills/, create-objectstack, content/docs/, '
64506460
+ 'packages/apps/, spec fixtures) found ONE `element:number` author writing a record-form '
64516461
+ '`filter` — a spec test fixture, rewritten to the array form in the same change — and '

0 commit comments

Comments
 (0)