@objectstack/service-analytics 17.2.0 → 17.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,606 @@
1
1
  # Changelog — @objectstack/service-analytics
2
2
 
3
+ ## 17.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 74cee59: Resolve `{current_user_id}` (and every other filter placeholder) on the direct analytics query path, at parity with the list path and the dashboard dataset path.
8
+
9
+ What changes for an app author: a widget or report whose filter says `owner: '{current_user_id}'` used to render `0` for every viewer whenever the query reached the SQL strategy — the literal text was bound into the `WHERE` and matched no row, silently. Now the same filter expression means the same thing on every surface: `AnalyticsService.query` and `generateSql` expand `where`, `timeDimensions[].dateRange`, and a registered dataset's own filter / measure filters against the requesting user before any strategy compiles, so each viewer gets their own rows. A placeholder that cannot be resolved — an unknown spelling, or `{current_user_id}` on an unauthenticated request — now refuses loudly with `FILTER_TOKEN_UNKNOWN` / `FILTER_TOKEN_UNRESOLVED` (HTTP 400) instead of charting a plausible zero.
10
+
11
+ This also closes a gap on the dashboard dataset door: the dataset-scope channel used to hand strategies the registry's unresolved filter copy, which was ANDed in beside the resolved one (`owner = $viewer AND owner = '{current_user_id}'`) and selected nothing.
12
+ - 399ecad: `ObjectQLStrategy` now refuses a cross-object leaf in a compiled measure's own `filter`, on both of its doors, instead of sending it to an engine that cannot join (#11461). This is the third producer of a predicate on that path — after the caller's `where` and the dataset's definition-level `filter` (#10861) — and the one `filterMemberView` did not fold in: #10413 phase 2 lowers `measureFilters[m]` onto that measure's `aggregations[].filter` entry (#10576), and the envelope check enumerated only two origins while its `query.measures` arm read each measure's resolved *field* and never its filter.
13
+
14
+ Measured on one fixture before the change, both doors in one run: a measure declaring `filter: { 'account.region': 'West' }` on a cube with `include: ['account']` was ACCEPTED, `engine.aggregate` received `{field:"*",method:"count",alias:"west_count",filter:{"account.region":"West"}}`, and an honest evaluator answered `west_count: 0` where the truthful answer was `2` — beside a correct `total_count: 3`, so the wrong number came back wearing the same response shape as the right one. The `/analytics/sql` echo rendered `COUNT(CASE WHEN account.region = $1 THEN 1 END)` over a `FROM` carrying no join at all. Both doors now answer `INVALID_FIELD`/400 before the engine is reached, naming the offending field, the dataset, and — the locator neither sibling refusal has — the measure whose declaration holds the leaf.
15
+
16
+ Ordinary per-measure filters are unaffected and still reach the engine carrying their own `aggregations[].filter`, and a cross-object filter declared on a measure a query does not ask for changes nothing: only the measures in `query.measures` are judged, which is exactly the set both doors lower. The same definition remains valid on a native-SQL driver, which the refusal says.
17
+ - d5b330d: feat(spec,analytics): `AnalyticsResult.fields[].builtinAggregate` — a closed discriminator for a measure column whose display name is the server's built-in default (#14492)
18
+
19
+ **What a consumer sees.** `queryDataset()` (and `POST /api/v1/analytics/dataset/query`,
20
+ which relays the result verbatim) now carries an optional
21
+ `fields[].builtinAggregate?: 'count' | 'sum' | 'avg' | 'min' | 'max' | 'count_distinct'`
22
+ on a measure column. It is present exactly when the dataset measure behind the
23
+ column declares an `aggregate` and **no** `label` — the producer then has nothing
24
+ but the aggregate to name the column by, so it says which aggregate that is. It is
25
+ absent whenever the author declared a label (a plain string or an inline locale
26
+ map, even one with no entry for the request locale: an author's text is never
27
+ re-labelled by a consumer), and absent on dimension columns and derived measures.
28
+ The vocabulary is `AggregationFunction` (`data/query.zod.ts`), the one closed
29
+ aggregate enum — no second spelling. `AnalyticsResultResponseSchema`
30
+ (`api/analytics.zod.ts`) mirrors the member, refusing a spelling outside the enum.
31
+
32
+ **Why.** An AI-built dashboard's "count of customers by status" chart showed the
33
+ English axis title "Count" on a Chinese UI. The renderer (objectui
34
+ `buildChartSeries()` / `labelOf()`) treats `fields[].label` as resolved author
35
+ content and passes it through verbatim — correctly, since a real custom label
36
+ ("Tasks") must survive. What it could not tell apart was an author's text from
37
+ the server's built-in default for a bare `count`. Guessing from the label text
38
+ was refused (it would catch an author who really named a field `Count`, and break
39
+ the moment the default is spelled in another language); translating on the
40
+ server was not taken (it copies the front end's language decision into the
41
+ producer and leaves nothing for a per-widget override). The ruling (2026-09-02,
42
+ option B) is a structured discriminator on the contract: the consumer prefers a
43
+ locale lookup keyed by `builtinAggregate` — mirroring its existing
44
+ `report.aggregate.*` keys — and falls back to `label`, then `name`.
45
+
46
+ **Producer-side changes.**
47
+
48
+ - `@objectstack/service-analytics` — `queryDataset`'s measure enrichment sets
49
+ `builtinAggregate` from the dataset measure's own `aggregate` when the measure
50
+ has no authored `label`. Judged on the authored key, never on the resolved
51
+ string.
52
+ - `@objectstack/spec` — the `dataset` create seed (`metadata-create-seeds.ts`)
53
+ drops its hardcoded `label: 'Count'` from the seeded `count` measure, so a
54
+ dataset created from Studio is a built-in default (wire: `builtinAggregate:
55
+ 'count'`) instead of an authored English literal. `getMeta()` for such a
56
+ dataset now titles the metric by its name (`count`) rather than `Count`;
57
+ `CubeMeta.measures[].type` already carried the aggregate there.
58
+
59
+ Purely additive: no key is removed or renamed, no authorable schema changes shape,
60
+ and a consumer that ignores the member sees exactly the response it saw before.
61
+
62
+ ### Patch Changes
63
+
64
+ - c8be110: refactor(service-analytics): derive the analytics auto-bridge's engine view from the declared contracts (#11833)
65
+
66
+ `plugin.ts` named the data engine through a consumer-local structural
67
+ `DataEngineLike` — the second of the two sites #11833 records, after the
68
+ datasource half that landed as PR #12011. It is now derived from the declared
69
+ contracts: `IDataEngine.aggregate` / `execute?` /
70
+ `resolveEffectiveDatasource?` / `getDriverForObject?` and
71
+ `IObjectQLEngine.getObject`. Optionality is preserved exactly — `aggregate`
72
+ required, everything else `Partial<>` — because these probes are the plugin's
73
+ graceful-degradation seam.
74
+
75
+ **Why this is `patch` and not a type-only no-op.** Four of the five members
76
+ substitute with no behaviour change. The fifth does not: the deleted structural
77
+ type declared `aggregations[].function` as `string`, while the contract
78
+ declares the six-value `AggregationFunction`. The bridge therefore forwarded
79
+ whatever method string reached it. That forward is now parsed with the spec's
80
+ own enum, so a method the engine contract does not declare is refused at the
81
+ bridge — loudly, naming the aggregation and the legal vocabulary — instead of
82
+ reaching the engine, where `driver-sql` blamed a `function` key the author
83
+ never wrote and the in-memory evaluator answered `null` for every bucket under
84
+ the author's own measure name.
85
+
86
+ No authored analytics can trigger the new refusal: the one reachable producer
87
+ of a non-aggregate method — a custom-SQL measure (`AggregationMetricType`
88
+ `number` / `string` / `boolean`) — is already refused earlier, caller-facing,
89
+ by `ObjectQLStrategy.resolveMeasureAggregation` (#12209). What is left is host
90
+ drift (a cube object registered without meeting `CubeSchema`), which is why
91
+ the new refusal is a bare `Error` in the undeclared-500 tier rather than an
92
+ ADR-0112 400 that would blame the caller for something they did not write.
93
+ - aa16721: fix(service-analytics): the consumer-local `executeAggregate` config mirrors narrow `aggregations[].method` to `AggregationFunction` (#12940)
94
+
95
+ #12776 narrowed the contract — `StrategyContext.executeAggregate`'s
96
+ `aggregations[].method` went from `string` to the six-value
97
+ `AggregationFunction` — but this package's two CONSUMER-LOCAL config mirrors
98
+ of that same slot kept declaring `string`, so the compile-time vocabulary the
99
+ narrowing bought for strategy authors stopped at the package boundary and
100
+ never reached the people who write a custom bridge.
101
+
102
+ FROM → TO, at all three sites the tree carries (the card enumerated two):
103
+
104
+ - `AnalyticsServicePluginOptions.executeAggregate` (`plugin.ts`) —
105
+ `aggregations[].method: string` → `AggregationFunction`. This is the
106
+ declaration an app author's own `executeAggregate` bridge is typed against.
107
+ - `AnalyticsServiceConfig.executeAggregate` (`analytics-service.ts`) — the
108
+ same narrowing on the config twin whose own comment says it is kept in
109
+ lockstep with `StrategyContext.executeAggregate`; that claim is true again,
110
+ and now names the member so the next drift is visible.
111
+ - `parseEngineAggregateFunction`'s `method` parameter (`plugin.ts`), the
112
+ auto-bridge's runtime parse — narrowed for the same reason: it was the
113
+ third place a reader was told this vocabulary is open.
114
+
115
+ Who breaks at compile time on upgrade: CALLERS that fill `method` with a
116
+ value typed `string` (or a literal outside the six) when invoking one of
117
+ these bridges — the values the bridge already refused at runtime (#11833).
118
+ IMPLEMENTORS are source-compatible: a handler that accepts `method: string`
119
+ accepts a superset and stays assignable to the narrowed member (parameter
120
+ contravariance), which is why the ~nine test doubles in this package that
121
+ declare their own `{ field, method: string, alias }` mirrors still compile
122
+ untouched.
123
+
124
+ No runtime change. The auto-bridge's runtime parse-and-refuse (#11833) stays
125
+ exactly where it was — with both ends of the `method` → `function` rename now
126
+ declaring the same enum, it is defence in depth behind a compile-time check
127
+ rather than the only check, and the two comments that explained it by
128
+ pointing at the old `method: string` declaration say so instead.
129
+ - e7191ce: fix(build): give each `exports` condition its own `types` target in the 28 dual-build packages (#13112)
130
+
131
+ **Published-surface change, zero runtime change.** No emitted byte moves; what
132
+ moves is which declaration file a resolver READS. Maintainer ruling 2026-08-29
133
+ (decision batch #3, verbatim 「同意」) chose declaring the files over deleting
134
+ them.
135
+
136
+ ## What was wrong
137
+
138
+ These 28 packages are `"type": "module"` and dual-built, and each spelled one
139
+ `types` condition as a **sibling** of `import`/`require`:
140
+
141
+ ```json
142
+ "exports": { ".": {
143
+ "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs"
144
+ } }
145
+ ```
146
+
147
+ A sibling `types` answers for **both** conditions, so a CommonJS consumer was
148
+ handed `dist/index.d.ts` — an ES-module declaration, because the package is
149
+ `"type": "module"` — for an entry point it reaches with `require`. Measured with
150
+ `tsc --traceResolution` on a `"type": "commonjs"` fixture at `moduleResolution:
151
+ node16`:
152
+
153
+ ```
154
+ error TS1479: The current file is a CommonJS module whose imports will produce
155
+ 'require' calls; however, the referenced file is an ECMAScript module and cannot
156
+ be imported with 'require'.
157
+ ```
158
+
159
+ The JavaScript at `dist/index.cjs` loads perfectly (`check:dual-build-cjs-loads`
160
+ has asserted that for months). It is the **types** that told the consumer the
161
+ supported `require` entry point could not be required. The `dist/index.d.cts`
162
+ twin tsup emits beside it — 36 files, 5,517,701 B on this build — was named by
163
+ no condition at all and shipped in every tarball unreachable.
164
+
165
+ ## What changed
166
+
167
+ Each condition now names its own declaration, the shape TypeScript documents:
168
+
169
+ ```json
170
+ "exports": { ".": {
171
+ "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" },
172
+ "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
173
+ } }
174
+ ```
175
+
176
+ 33 entry points across 27 packages, subpaths included. The root `types` field is
177
+ untouched, so `node10` resolvers are unaffected; the `import` condition resolves
178
+ exactly what it resolved before, measured as an unchanged control in the same
179
+ run.
180
+
181
+ ## `@objectstack/core` is deliberately NOT changed
182
+
183
+ Splitting a declaration in two makes TypeScript compare it nominally, and
184
+ `ObjectKernel` carries a `private plugins` member that reaches every plugin
185
+ through `PluginContext.getKernel()`. With core split, whole-repo `pnpm build`
186
+ fails in `@objectstack/verify` with 5 × TS2345 ("Types have separate
187
+ declarations of a private property 'plugins'"); with core held back and the
188
+ other 27 split, 71/71 tasks pass. So core keeps the sibling-`types` shape and
189
+ its two `.d.cts` files (220,854 B) stay unreachable, declared as such in
190
+ `check:dual-build-cjs-loads`. Splitting it needs a decision about core's public
191
+ types, not about an exports map.
192
+
193
+ ## For consumers
194
+
195
+ - **ESM consumers: nothing changes.** Same declaration file, byte for byte.
196
+ - **CJS consumers under `node16`/`nodenext`: TS1479 goes away** and the
197
+ declarations they get are the ones built for CommonJS.
198
+ - **`node10` / `moduleResolution: node` consumers: nothing changes** — they never
199
+ read `exports`.
200
+ - Nothing is removed: every path that resolved before still resolves.
201
+
202
+ Packages that are CJS-first (`require` → `./dist/index.js`, no `"type": "module"`)
203
+ were already correct and are untouched — their `dist/index.d.ts` really is the
204
+ CommonJS declaration. Their ESM mirror (an unreachable `.d.mts` under the
205
+ `import` condition) is a separate, larger population and is filed separately per
206
+ the ruling, not fixed here.
207
+
208
+ `check:dual-build-cjs-loads` grew a fourth invariant (TYPED) that reds on the old
209
+ shape, so the drift cannot return silently.
210
+ - 017130a: The ObjectQL analytics strategy now refuses a custom-SQL measure (`AggregationMetricType` `number` / `string` / `boolean`) with a loud `400 INVALID_FIELD` naming the measure and its metric type, instead of forwarding the raw SQL expression into `engine.aggregate` — where `driver-sql` rejected it blaming a `function` key the author never wrote, and the in-memory evaluator silently answered `null` for every bucket under the measure's own name.
211
+
212
+ What stops being served, and for whom: on deployments whose driver has no native SQL capability (the ObjectQL aggregate path — e.g. Mongo or in-memory), a query or dataset widget selecting a custom-SQL measure now answers a 400 that says to use an aggregate measure (count/sum/avg/min/max/count_distinct) or run the cube on a native-SQL driver. Those queries previously "succeeded" with a per-bucket `null` (or a mis-attributed driver error), never with a correct number. Native-SQL driver behaviour is unchanged: custom-SQL measures still run there, emitted verbatim.
213
+ - 466b389: **Fix:** `/api/v1/analytics/query` on the ObjectQL door (MongoDB, the memory driver, or any deployment whose driver reports `objectqlAggregate` but not `nativeSql`) now honours a measure's own scoped `filter` — `won_count` and `won_amount`-style conditional measures answer the same numbers the dashboard door and the native-SQL door already did (#10413 phase 2).
214
+
215
+ `ObjectQLStrategy.execute` lowers each measure's `filter` into the ONE aggregation it belongs to, via the per-aggregation `filter` field #10576 added to `engine.aggregate`'s contract (SQL `FILTER (WHERE …)` semantics) — not into the whole-call filter, which would have narrowed every measure (a fix shaped that way would make a conditional measure right while making every unconditional sibling measure in the same query wrong). An aggregation with no measure filter is unchanged and keeps the native-pushdown-eligible shape.
216
+
217
+ `ObjectQLStrategy.generateSql` (the `/analytics/sql` echo) renders the same conditional aggregate — `COUNT(CASE WHEN … THEN … END)`-style — so the preview stays an honest description of what `execute()` now actually runs, matching the native-SQL strategy's existing echo for the same class of measure.
218
+
219
+ Phase 1 (PR #10758) already ANDed a dataset's definition-level `filter` into the whole-call filter on this door; this closes the remaining half of the two-door disagreement #10413 reported. `NativeSQLStrategy` (#10298 / PR #10411) is unaffected by this change.
220
+ - b0d7d54: `ObjectQLStrategy` now refuses a read scope that does not bind, before handing it to the engine (`READ_SCOPE_COMPILE_FAILED` / 500). That strategy merges `StrategyContext.getReadScope` output straight into the `FilterCondition` it gives `engine.aggregate` and never reaches `compileScopedFilterToSql`, so the empty-`$nin` refusal that compiler gained guarded the NativeSQL path and the `/analytics/sql` echo only. Measured against a real engine, a non-RLS scope provider handing `{ f: { $nin: [] } }`, `{ $not: { f: { $in: [] } } }`, `{ $not: { f: [] } }` or `{ $not: { f: { $in: [], $ne: 'x' } } }` received the WHOLE TABLE on any query this strategy served; all four are now refused, at both engine-bound merges (the aggregate filter and the FK→attribute resolution). Deliberately unchanged: `$in: []` keeps its ruled constant-FALSE fold, so the RLS compiler's live composite — an emptied membership `$or`-ed beside an own-rows grant — still admits exactly the own rows; and the NativeSQL path and the SQL echo keep the disposition they already had.
221
+ - 967402a: The analytics read-scope compiler (`read-scope-sql.ts`) now refuses an empty `$nin` (`READ_SCOPE_COMPILE_FAILED` / 500) instead of folding it to constant TRUE. An emptied exclusion ("NOT IN () excludes nothing") vacated the whole read scope — every row admitted — on the ADR-0021 lowering, where a widening is scope over-reach; no in-repo producer can emit the shape (the CEL lowering never emits `$nin`, and the RLS guard drops even-polarity empty-`$nin` policies upstream), so the refusal costs no live traffic. Deliberately asymmetric: `$in: []` keeps its ruled constant-FALSE fold (#5322/#5243), which the RLS compiler's inert positive composite — an emptied membership `$or`-ed beside an own-rows grant — depends on.
222
+ - 5c7cbe3: Refuse a non-binding (vacating) read scope at the two remaining `getReadScope` merge sites: the `/analytics/sql` echo (`ObjectQLStrategy.generateSql`) and `NativeSQLStrategy.applyReadScope`. The `$not`-over-`$in: []` family compiled to a constant-TRUE predicate on those routes, so the echo rendered — and the native strategy actually executed — a whole-table `WHERE` for a scope the ObjectQL execution path already refused (#13640). All three faces now answer one verdict, in the same `READ_SCOPE_COMPILE_FAILED` / 500 envelope; the ruled `$in: []` zero-rows reduction, the live RLS empty-membership composite, and `compileScopedFilterToSql` itself (the ruled #13571 residue included) are unchanged.
223
+ - a3c4215: fix(service-analytics): wire the `typecheck` script so turbo stops silently no-opping the gate, and clear the 10 type errors it was hiding (#12939)
224
+
225
+ `packages/services/service-analytics/package.json` declared only `build` and
226
+ `test`. Root `typecheck` is `turbo run typecheck`, which **no-ops a package
227
+ that has no such script and reports success** — so no tsc read this package's
228
+ `src/` from the typecheck lane at all. `build` is tsup (esbuild; the DTS pass
229
+ processes declarations only) and `test` is vitest (esbuild transform), and
230
+ neither type-checks. The package was reached only by the `check:type-check-debt`
231
+ ratchet, which asserts the error count does not *grow* — never that it is zero.
232
+
233
+ Adding the one-line script (mirroring its sibling `service-settings`, repaired
234
+ the same way in #7925) makes the task real. The tests are already inside the
235
+ program — the package `tsconfig.json` includes `src` and the tests live in
236
+ `src/__tests__/**` — so `tsc --noEmit --listFiles` lists **83 of the 83**
237
+ `*.test.ts` files on disk. The new gate reads the tests, not just the source.
238
+
239
+ All 10 errors were stale tests, not source defects; no non-test source file
240
+ changed. Nothing was silenced: no `any` added, no `@ts-expect-error`, no
241
+ `@ts-nocheck`, `strict` untouched, and the tsconfig `include`/`exclude` are
242
+ byte-identical — excluding the tests would have converted a missing gate into
243
+ a lying one.
244
+
245
+ - `__tests__/measure-source-field-gate.test.ts` (7 x TS2339). `promise.catch(fn)`
246
+ does not drop the resolved branch from the type, so
247
+ `service.query(...).catch((e) => e as Error)` was `AnalyticsResult | Error`
248
+ and every `err.message` / `err.field` / `err.member` / `err.param` read was a
249
+ property access on `AnalyticsResult`. A local `refusalOf()` helper narrows it
250
+ once via `then<never, Refusal>`; as a bonus the resolved branch now fails by
251
+ name instead of surfacing later as `expect(undefined).toMatch(...)`.
252
+ - `__tests__/objectql-timedimension-projection.test.ts` (2 x TS7053). The
253
+ `TABLE` fixture was inferred as `{ id: number; due_date: string; priority:
254
+ string }[]` and the aggregate stand-in indexes it by a computed `string` key.
255
+ Annotated as the `Row` (`Record<string, unknown>`) the file already declares.
256
+ - `__tests__/analytics-service.test.ts` (1 x TS6133). An unused
257
+ `AnalyticsDriverCapabilities` type import. The capability literals in this
258
+ file are inline `ctx` objects checked contextually at each `canHandle` call
259
+ site, so the import added no coverage and is removed.
260
+
261
+ `service-analytics` graduates out of the `check:type-check-coverage` DEBT
262
+ ledger: 65/78 -> 66/78 workspace packages type-checked, 382 -> 372 frozen raw
263
+ errors, 13 -> 12 ledger entries.
264
+ - d028b37: fix(spec): `StrategyContext.executeAggregate` `aggregations[].method` narrows from `string` to `AggregationFunction` (#12776)
265
+
266
+ <!-- adr-0087: registered strategy-context-aggregation-method-narrowed -->
267
+
268
+ **BREAKING** accept-set narrowing on a published contract, landing after the
269
+ v17.0.0 cut (the lockstep launch-window convention ships it as `minor`).
270
+
271
+ Two spec-declared surfaces described the same slot and disagreed about its
272
+ type: `IDataEngine.aggregate`'s `aggregations[].function` is the closed
273
+ six-value `AggregationFunction` enum, while the analytics strategy contract's
274
+ `StrategyContext.executeAggregate` declared the same value as
275
+ `aggregations[].method: string`. The analytics bridge renames one to the
276
+ other, so nothing on the analytics side of that seam was compile-checked
277
+ against the engine's vocabulary — a strategy author (very often an AI) got
278
+ no compile-time help and hit the bridge's runtime refusal instead.
279
+
280
+ FROM → TO:
281
+
282
+ - `aggregations[].method: string` →
283
+ `aggregations[].method: AggregationFunction`
284
+ (`'count' | 'sum' | 'avg' | 'min' | 'max' | 'count_distinct'`, the spec's
285
+ own enum from `@objectstack/spec/data`). One slot, one declaration.
286
+
287
+ Who breaks at compile time on upgrade:
288
+
289
+ - external CALLERS of `StrategyContext.executeAggregate` that fill `method`
290
+ with a value typed `string` (or a literal outside the six) — the values the
291
+ bridge already refused at runtime (#11833) now fail `tsc`.
292
+ - external IMPLEMENTORS of `StrategyContext` stay source-compatible: a
293
+ handler that accepts `method: string` accepts a superset and remains
294
+ assignable to the narrowed member.
295
+
296
+ The bridge's runtime parse-and-refuse (#11833) stays as defence in depth.
297
+ In-repo, `ObjectQLStrategy`'s aggregation locals now carry the enum
298
+ end-to-end (`@objectstack/service-analytics`, runtime behaviour unchanged —
299
+ the census measured every reachable producer already emitting enum-legal
300
+ values only).
301
+ - a40c0f9: Guard the analytics record-label lookup with `assertReadScopeCannotVacate` — the fourth read-scope door
302
+
303
+ `AnalyticsServicePlugin`'s `fetchRecordLabels` hook `$and`s the **referenced** object's read scope with an `id $in [...]` filter and hands the result straight to `executeAggregate`. Unlike the three faces unified previously (the ObjectQL engine merge, the `/analytics/sql` echo merge, and `NativeSQLStrategy.applyReadScope`), it met neither `compileScopedFilterToSql` nor the vacancy guard, so a read scope that lowers to a boolean constant — the `$not`-over-`$in: []` family reachable from any out-of-repo `StrategyContext.getReadScope` producer — let that per-record read run effectively unscoped for the ids in hand, surfacing the display names the referenced object's RLS exists to hide.
304
+
305
+ The hook now calls the already-exported `assertReadScopeCannotVacate` on the referenced object's scope before composing the filter, refusing in the same envelope as its siblings (`READ_SCOPE_COMPILE_FAILED` / 500). No behaviour changes for scopes that bind: an ordinary referenced-object scope still narrows the label lookup, and the `$in: []` zero-rows reduction (including the live RLS composite that pairs it with an own-rows grant) still passes through untouched. The read-scope SQL compiler is unchanged.
306
+ - Updated dependencies [809d417]
307
+ - Updated dependencies [387e231]
308
+ - Updated dependencies [f794e4e]
309
+ - Updated dependencies [cae2169]
310
+ - Updated dependencies [b812a54]
311
+ - Updated dependencies [2d4fa75]
312
+ - Updated dependencies [0e4e51b]
313
+ - Updated dependencies [e84bbf6]
314
+ - Updated dependencies [effae80]
315
+ - Updated dependencies [efb3513]
316
+ - Updated dependencies [d62f990]
317
+ - Updated dependencies [c45d8e6]
318
+ - Updated dependencies [2e3e8c7]
319
+ - Updated dependencies [e621291]
320
+ - Updated dependencies [655b106]
321
+ - Updated dependencies [40a93b5]
322
+ - Updated dependencies [101ad2c]
323
+ - Updated dependencies [d5b330d]
324
+ - Updated dependencies [dda969c]
325
+ - Updated dependencies [1f45690]
326
+ - Updated dependencies [277948f]
327
+ - Updated dependencies [8bdd955]
328
+ - Updated dependencies [f3bbbef]
329
+ - Updated dependencies [4f24e9d]
330
+ - Updated dependencies [e27583e]
331
+ - Updated dependencies [4bd6faa]
332
+ - Updated dependencies [86cbe37]
333
+ - Updated dependencies [6a180e4]
334
+ - Updated dependencies [474242f]
335
+ - Updated dependencies [63cd487]
336
+ - Updated dependencies [bd4aa4e]
337
+ - Updated dependencies [803eaab]
338
+ - Updated dependencies [f8e8f03]
339
+ - Updated dependencies [983edf1]
340
+ - Updated dependencies [eae824e]
341
+ - Updated dependencies [f6fa22c]
342
+ - Updated dependencies [8a483b3]
343
+ - Updated dependencies [97bcd99]
344
+ - Updated dependencies [df59de0]
345
+ - Updated dependencies [96e25a8]
346
+ - Updated dependencies [f75a38a]
347
+ - Updated dependencies [7a25e7d]
348
+ - Updated dependencies [1fa05a6]
349
+ - Updated dependencies [c85a265]
350
+ - Updated dependencies [dcb10a5]
351
+ - Updated dependencies [773a999]
352
+ - Updated dependencies [35dffea]
353
+ - Updated dependencies [d8024f0]
354
+ - Updated dependencies [8120808]
355
+ - Updated dependencies [776a098]
356
+ - Updated dependencies [5060877]
357
+ - Updated dependencies [4f6325d]
358
+ - Updated dependencies [52954c0]
359
+ - Updated dependencies [2aa8456]
360
+ - Updated dependencies [93809a3]
361
+ - Updated dependencies [7c0d0c3]
362
+ - Updated dependencies [daae7aa]
363
+ - Updated dependencies [8dc22d6]
364
+ - Updated dependencies [279431e]
365
+ - Updated dependencies [948dd6b]
366
+ - Updated dependencies [3b4c56c]
367
+ - Updated dependencies [ae8edd2]
368
+ - Updated dependencies [e25403c]
369
+ - Updated dependencies [a81aa9d]
370
+ - Updated dependencies [64baa68]
371
+ - Updated dependencies [9fa70d7]
372
+ - Updated dependencies [09db64a]
373
+ - Updated dependencies [92916e7]
374
+ - Updated dependencies [a84f3ea]
375
+ - Updated dependencies [f2eaae8]
376
+ - Updated dependencies [56c093c]
377
+ - Updated dependencies [c09451b]
378
+ - Updated dependencies [ba64877]
379
+ - Updated dependencies [7345308]
380
+ - Updated dependencies [79b6a22]
381
+ - Updated dependencies [30d96ab]
382
+ - Updated dependencies [f658793]
383
+ - Updated dependencies [c95ad19]
384
+ - Updated dependencies [e58ea8b]
385
+ - Updated dependencies [4a17645]
386
+ - Updated dependencies [3795c5f]
387
+ - Updated dependencies [8ab926b]
388
+ - Updated dependencies [7317cf2]
389
+ - Updated dependencies [e25e839]
390
+ - Updated dependencies [5997207]
391
+ - Updated dependencies [8b13cc8]
392
+ - Updated dependencies [4a4a35d]
393
+ - Updated dependencies [86e765a]
394
+ - Updated dependencies [1d7e76a]
395
+ - Updated dependencies [53dc739]
396
+ - Updated dependencies [fd289be]
397
+ - Updated dependencies [03bf7b1]
398
+ - Updated dependencies [f90e820]
399
+ - Updated dependencies [18d816a]
400
+ - Updated dependencies [e8bd715]
401
+ - Updated dependencies [b91c351]
402
+ - Updated dependencies [a28a3c0]
403
+ - Updated dependencies [daeaaf9]
404
+ - Updated dependencies [c459da6]
405
+ - Updated dependencies [e914733]
406
+ - Updated dependencies [f887e52]
407
+ - Updated dependencies [881f8d8]
408
+ - Updated dependencies [3bfa1e6]
409
+ - Updated dependencies [0a8ebf3]
410
+ - Updated dependencies [901355c]
411
+ - Updated dependencies [34ce8e7]
412
+ - Updated dependencies [33681ea]
413
+ - Updated dependencies [bfe13c8]
414
+ - Updated dependencies [0fb3044]
415
+ - Updated dependencies [4635f3e]
416
+ - Updated dependencies [fd289be]
417
+ - Updated dependencies [ee3595c]
418
+ - Updated dependencies [b2eab95]
419
+ - Updated dependencies [93940d4]
420
+ - Updated dependencies [3a04b01]
421
+ - Updated dependencies [45b9051]
422
+ - Updated dependencies [b9e9227]
423
+ - Updated dependencies [d395692]
424
+ - Updated dependencies [5894d30]
425
+ - Updated dependencies [a3765f6]
426
+ - Updated dependencies [2d5cee3]
427
+ - Updated dependencies [e22158f]
428
+ - Updated dependencies [7404925]
429
+ - Updated dependencies [0c2334f]
430
+ - Updated dependencies [778c59f]
431
+ - Updated dependencies [d2619fd]
432
+ - Updated dependencies [af56546]
433
+ - Updated dependencies [6acb11a]
434
+ - Updated dependencies [33c5fd3]
435
+ - Updated dependencies [20b0fdb]
436
+ - Updated dependencies [905019b]
437
+ - Updated dependencies [a286411]
438
+ - Updated dependencies [98c0d33]
439
+ - Updated dependencies [368a82e]
440
+ - Updated dependencies [a3d5724]
441
+ - Updated dependencies [93ea19b]
442
+ - Updated dependencies [9ee2dcf]
443
+ - Updated dependencies [8cb96ec]
444
+ - Updated dependencies [8f10a79]
445
+ - Updated dependencies [6269a55]
446
+ - Updated dependencies [a17da05]
447
+ - Updated dependencies [a8c00e2]
448
+ - Updated dependencies [22e5236]
449
+ - Updated dependencies [0fb8760]
450
+ - Updated dependencies [e5ce2ed]
451
+ - Updated dependencies [be21955]
452
+ - Updated dependencies [bc56e18]
453
+ - Updated dependencies [be21955]
454
+ - Updated dependencies [a9ee989]
455
+ - Updated dependencies [4d0d944]
456
+ - Updated dependencies [15d58db]
457
+ - Updated dependencies [d63b014]
458
+ - Updated dependencies [9abe4e4]
459
+ - Updated dependencies [2cc7122]
460
+ - Updated dependencies [50d6c92]
461
+ - Updated dependencies [9e0ba21]
462
+ - Updated dependencies [311433f]
463
+ - Updated dependencies [3e5ad08]
464
+ - Updated dependencies [9abe4e4]
465
+ - Updated dependencies [b7131f3]
466
+ - Updated dependencies [e5812fa]
467
+ - Updated dependencies [7085f90]
468
+ - Updated dependencies [dee4dd4]
469
+ - Updated dependencies [ce7e497]
470
+ - Updated dependencies [51ecb2f]
471
+ - Updated dependencies [9086761]
472
+ - Updated dependencies [42a117b]
473
+ - Updated dependencies [1401ae7]
474
+ - Updated dependencies [4297fe7]
475
+ - Updated dependencies [e398863]
476
+ - Updated dependencies [d16df74]
477
+ - Updated dependencies [f11fc61]
478
+ - Updated dependencies [e808890]
479
+ - Updated dependencies [8f79379]
480
+ - Updated dependencies [e6ca40e]
481
+ - Updated dependencies [0c77ea4]
482
+ - Updated dependencies [52954c0]
483
+ - Updated dependencies [89eb997]
484
+ - Updated dependencies [7131f12]
485
+ - Updated dependencies [aa5994e]
486
+ - Updated dependencies [be93457]
487
+ - Updated dependencies [a65db76]
488
+ - Updated dependencies [2cf5a96]
489
+ - Updated dependencies [15eb2c9]
490
+ - Updated dependencies [5691b07]
491
+ - Updated dependencies [2a6122b]
492
+ - Updated dependencies [225e769]
493
+ - Updated dependencies [8af88dd]
494
+ - Updated dependencies [fb5fbb8]
495
+ - Updated dependencies [d7b3963]
496
+ - Updated dependencies [33184fd]
497
+ - Updated dependencies [7c41693]
498
+ - Updated dependencies [b72db01]
499
+ - Updated dependencies [dce5cd4]
500
+ - Updated dependencies [9688f58]
501
+ - Updated dependencies [556ebc1]
502
+ - Updated dependencies [177ebdc]
503
+ - Updated dependencies [8d237b4]
504
+ - Updated dependencies [2d2e6f0]
505
+ - Updated dependencies [2d8dd8d]
506
+ - Updated dependencies [22d573e]
507
+ - Updated dependencies [b5a2398]
508
+ - Updated dependencies [348860c]
509
+ - Updated dependencies [5383fa6]
510
+ - Updated dependencies [5b3ff63]
511
+ - Updated dependencies [1a6a19c]
512
+ - Updated dependencies [527e050]
513
+ - Updated dependencies [dd33bf9]
514
+ - Updated dependencies [4cb2a90]
515
+ - Updated dependencies [74a7804]
516
+ - Updated dependencies [53d3689]
517
+ - Updated dependencies [b3a63d3]
518
+ - Updated dependencies [49f0dcf]
519
+ - Updated dependencies [033a34c]
520
+ - Updated dependencies [4d25d22]
521
+ - Updated dependencies [1ffee51]
522
+ - Updated dependencies [5ae4303]
523
+ - Updated dependencies [ece4dad]
524
+ - Updated dependencies [e9b377e]
525
+ - Updated dependencies [146f448]
526
+ - Updated dependencies [735f5c7]
527
+ - Updated dependencies [a7e18de]
528
+ - Updated dependencies [366f895]
529
+ - Updated dependencies [dc75ba8]
530
+ - Updated dependencies [cce0aa9]
531
+ - Updated dependencies [e764507]
532
+ - Updated dependencies [cff17af]
533
+ - Updated dependencies [39404f3]
534
+ - Updated dependencies [ca1965f]
535
+ - Updated dependencies [8619f95]
536
+ - Updated dependencies [b706af9]
537
+ - Updated dependencies [db8c288]
538
+ - Updated dependencies [0e5fe7f]
539
+ - Updated dependencies [add4360]
540
+ - Updated dependencies [fc9ba76]
541
+ - Updated dependencies [0f94cc7]
542
+ - Updated dependencies [a11c1a5]
543
+ - Updated dependencies [71f9cd1]
544
+ - Updated dependencies [ee17d86]
545
+ - Updated dependencies [cdbd920]
546
+ - Updated dependencies [18c432e]
547
+ - Updated dependencies [3c418c4]
548
+ - Updated dependencies [fa8715a]
549
+ - Updated dependencies [a933ed7]
550
+ - Updated dependencies [b3ca463]
551
+ - Updated dependencies [a933ed7]
552
+ - Updated dependencies [0d4a6a8]
553
+ - Updated dependencies [518d5e5]
554
+ - Updated dependencies [6643ba1]
555
+ - Updated dependencies [eeba2ef]
556
+ - Updated dependencies [ec4c4d2]
557
+ - Updated dependencies [424f73c]
558
+ - Updated dependencies [cccbe51]
559
+ - Updated dependencies [a8d6b1d]
560
+ - Updated dependencies [e4a7695]
561
+ - Updated dependencies [87075b1]
562
+ - Updated dependencies [fc58a99]
563
+ - Updated dependencies [14cfc00]
564
+ - Updated dependencies [1c6f7b4]
565
+ - Updated dependencies [e854a53]
566
+ - Updated dependencies [dfebfc8]
567
+ - Updated dependencies [d028b37]
568
+ - Updated dependencies [f7b25c5]
569
+ - Updated dependencies [122ef38]
570
+ - Updated dependencies [4a37870]
571
+ - Updated dependencies [428f9b2]
572
+ - Updated dependencies [aa7ff56]
573
+ - Updated dependencies [c41b42e]
574
+ - Updated dependencies [c4db311]
575
+ - Updated dependencies [750fff5]
576
+ - Updated dependencies [c19035e]
577
+ - Updated dependencies [ececf7a]
578
+ - Updated dependencies [d173125]
579
+ - Updated dependencies [8eeca27]
580
+ - Updated dependencies [8425c17]
581
+ - Updated dependencies [a5ef1d8]
582
+ - Updated dependencies [87ad30c]
583
+ - Updated dependencies [772d5de]
584
+ - Updated dependencies [ce80ec2]
585
+ - Updated dependencies [b372318]
586
+ - Updated dependencies [97a2263]
587
+ - Updated dependencies [29d0676]
588
+ - Updated dependencies [0169d49]
589
+ - Updated dependencies [6bd3231]
590
+ - Updated dependencies [d2b5ba8]
591
+ - Updated dependencies [b799ac5]
592
+ - Updated dependencies [8f74307]
593
+ - Updated dependencies [d23dc08]
594
+ - Updated dependencies [644ad50]
595
+ - Updated dependencies [9735662]
596
+ - Updated dependencies [4d5b4f8]
597
+ - Updated dependencies [0da7cd2]
598
+ - Updated dependencies [28a5c3e]
599
+ - Updated dependencies [4bc18e5]
600
+ - @objectstack/spec@17.3.0
601
+ - @objectstack/core@17.3.0
602
+ - @objectstack/types@17.3.0
603
+
3
604
  ## 17.2.0
4
605
 
5
606
  ### Minor Changes