Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/analytics-where-names-the-route-hop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@objectstack/spec": patch
---

Documentation: the analytics `where` contract and the `element:number` D3 entry now name the hop an array filter is lowered at.

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.

- `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.
- 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).

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.
2 changes: 1 addition & 1 deletion content/docs/references/api/analytics.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const result = AnalyticsEndpoint.parse(data);
| **cube** | `string` | ✅ | Target cube name |
| **measures** | `string[]` | ✅ | List of metrics to calculate |
| **dimensions** | `string[]` | optional | List of dimensions to group by |
| **where** | `any` | optional | Filtering criteria (canonical Query DSL FilterCondition) |
| **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`). |
| **timeDimensions** | `{ dimension: string; granularity?: Enum<'second' \| 'minute' \| 'hour' \| 'day' \| 'week' \| 'month' \| 'quarter' \| 'year'>; dateRange?: string \| string[] }[]` | optional | |
| **order** | `Record<string, Enum<'asc' \| 'desc'>>` | optional | |
| **limit** | `number` | optional | |
Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/data/analytics.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const result = AggregationMetricType.parse(data);
| **cube** | `string` | optional | Target cube name (optional when provided externally, e.g. in API request wrapper) |
| **measures** | `string[]` | ✅ | List of metrics to calculate |
| **dimensions** | `string[]` | optional | List of dimensions to group by |
| **where** | `any` | optional | Filtering criteria (canonical Query DSL FilterCondition) |
| **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`). |
| **timeDimensions** | `{ dimension: string; granularity?: Enum<'second' \| 'minute' \| 'hour' \| 'day' \| 'week' \| 'month' \| 'quarter' \| 'year'>; dateRange?: string \| string[] }[]` | optional | |
| **order** | `Record<string, Enum<'asc' \| 'desc'>>` | optional | |
| **limit** | `number` | optional | |
Expand Down
6 changes: 5 additions & 1 deletion packages/spec/src/data/analytics.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,11 @@ export const AnalyticsQuerySchema = lazySchema(() => strictObject(
* { where: { is_active: true, stage: { $nin: ['lost'] } } }
* ```
*/
where: FilterConditionSchema.optional().describe('Filtering criteria (canonical Query DSL FilterCondition)'),
where: FilterConditionSchema.optional().describe(
'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`).'
),

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