Repository navigation
Commit f64724a
fix(service-analytics)!: the dataset draft preview refuses a runtimeFilter on a field the object does not have, and a dimension over a JSON column, as the live strategies do (#28264)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DLMAJ9kZMBicdMLygftCdLd)_
domain:server#3 · os-elon-musk · session_016WV4cyQNVxpxREGfKkTsqs
Fixes #28257
Clause-②: no (narrowing) — a draft-preview dataset query (`queryDataset`
with `previewDrafts: true` over a pending seed draft) now refuses a
`runtimeFilter` member whose field the object does not have, and a
grouped dimension over a `json` column (#20807), both of which the
native and ObjectQL strategies refuse and the preview served before. No
export, answer member, status or error code is added: both refusals are
the live faces' existing `INVALID_FIELD` / 400.
Metadata-protocol: none
## 维护者速读
- 改了什么:数据集草稿预览遇到过滤对象上不存在的字段(如 `runtimeFilter: { bogus: 'x' }`),过去返回
`[]`,放在 `$or` 里还会返回另一分支的行;现在与实时两种策略一样按 400 `INVALID_FIELD`(`param:
where`)拒,消息逐字相同。triage 并入的 #20807:按 `json` 字段分组,预览过去按对象值分组,实时拒;实测确有差异,同一
PR 一并对齐。
- 为什么改:草稿里过滤字段拼错只显示空图,发布后才被拒;这是同一接缝上 #28210(度量)、#28259(维度)之后的第三处缺口。
- 风险与回滚:只影响草稿预览;对真实字段的过滤(已声明维度、未声明的真实字段、`$in`)三面照旧返回同样的行,实时策略不变。回滚即
revert。
- 先看:`packages/services/service-analytics/src/analytics-service.ts` 里
`answerDataset` 的 `previewService.query`(新增两道闸门及其位置)。
- 两分钟验证:`pnpm --filter @objectstack/service-analytics exec vitest run
src/__tests__/preview-filter-field-json-dimension-parity.test.ts`,预期 11
passed。
- 需要你做的:无,席位落地。
## What changes
**The draft preview's gate list (`analytics-service.ts`, `answerDataset`
→ `previewService.query`).** Two gates `ensureCube` asks on the live
path are now asked on the preview, on the cube with the minted measures,
in the order #28259 set up:
1. measure fields (#4437)
2. dimension fields (#5520)
3. **structured-JSON dimension door `assertDimensionsGroupScalarColumns`
(#20807), new here**
4. aggregate × field-type (#21044)
5. record currency (ADR-0144 D6)
6. **where fields `assertWhereFields` (#5669), new here, last of
`ensureCube`'s**
7. then `query()`'s: caller-measure column reference (#21156), stored
body (#21120), internal fields, field-level read (#20917).
The where gate takes the compiled cube's declared dimensions as its
vocabulary, as live does, so a filter on a declared dimension or on any
of the object's own fields stays legal. It reads every leaf of the
normalized filter tree, so the object spelling, an `$or` arm and the
array triple are refused alike. The JSON door refuses a `json` column
whether the dataset declares the dimension over it or the caller names
the field.
`preview-evaluator.ts` is unchanged: the gates run before any seed row
is read.
## Why
Measured on `main` `4159c701`, real SQLite engine, object fields
`region` (text), `amount` (number), `payload` (json); dataset dimensions
`region`, `payload_dim` (over `payload`), measures `total` (sum of
`amount`), `deals` (count):
| selection | native | ObjectQL | draft preview (before) | draft preview
(after) |
|:---|:---|:---|:---|:---|
| `runtimeFilter: { bogus: 'x' }` | `INVALID_FIELD` / 400, `param:
where` | same | `[]` | same refusal, same message |
| `runtimeFilter: { $or: [{ bogus: 1 }, { region: 'amer' }] }` |
`INVALID_FIELD` / 400 | same | amer, 150 | same refusal, same message |
| `runtimeFilter: [['bogus', '=', 'x']]` | `INVALID_FIELD` / 400 | same
| `[]` | same refusal, same message |
| filter `region`, `amount: { $gt: 40 }`, `region: { $in }` | rows |
same rows | same rows | same rows |
| dims `['payload']` (json, undeclared) | `INVALID_FIELD` / 400, `param:
dimensions` | same | one group per object value | same refusal, same
message |
| dims `['payload_dim']` (json, declared) | `INVALID_FIELD` / 400,
`param: dimensions` | same | one group per object value | same refusal,
same message |
| dims `['payload']` + filter `{ ghost: 1 }` | dimension 400 | dimension
400 | `[]` | dimension 400 |
| `amount + 1` + filter `{ ghost: 1 }` | where 400 | where 400 |
column-reference 403 | where 400 |
## Verification
-
`packages/services/service-analytics/src/__tests__/preview-filter-field-json-dimension-parity.test.ts`
(new, 11 cases, each over native / ObjectQL / preview through
`queryDataset` on real SQLite): the unknown filter field refused
`INVALID_FIELD` / 400 with `param: 'where'`, `field` and a message
identical on all three faces, in three spellings; CONTROLS: three
real-field filters serve the same rows on all three; the json dimension
refused with `param: 'dimensions'` and one message, declared and
undeclared; three two-mistake cases answer the same refusal first on all
three (json dimension + unknown filter → dimension; json dimension +
`region_sum` → the JSON door ahead of the field-type door; `amount + 1`
+ unknown filter → where 400 ahead of the 403).
- Ablations (source changed, the new file rerun): `analytics-service.ts`
reset to `main` ⇒ 8 red (all but the three controls);
`assertWhereFields` removed ⇒ 4 red (three spellings, `amount + 1` +
filter); `assertDimensionsGroupScalarColumns` removed ⇒ 4 red (both json
dimensions, both json two-mistake cases); the where gate moved after the
caller-measure gate ⇒ 1 red (`amount + 1` + filter); the JSON door moved
after the currency door ⇒ 1 red (json dimension + `region_sum`).
- `pnpm --filter @objectstack/service-analytics exec vitest run
--maxWorkers=2` at head: 200 files, 4806 passed, 282 skipped, 0 failed
(the #28210 and #28259 parity files included). `pnpm --filter
@objectstack/service-analytics typecheck` and eslint on the touched
files clean.
- Every `dispatch-gates --commands` family, `pnpm lint` and the dogfood
battery run at this head: the readings are posted on the card with the
os-dev report.
Skills-docs: none. `git grep -n -i -E "previewDrafts|draft (data
)?preview" -- skills content/docs` (references and releases excluded)
finds only `content/docs/data-modeling/currency.mdx:146`, still true;
`git grep -n runtimeFilter` finds
`content/docs/data-modeling/analytics.mdx` and
`content/docs/ui/{dashboards,reports}.mdx`, which describe the filter
vocabulary and say the Studio preview shares the governed path ("so the
numbers match everywhere", `analytics.mdx:263`), which this PR makes
true for filters; nothing describes the preview serving an unknown
filter field.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (No formatter ran; the
lint reading is posted on the card with the gate battery.)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: not touched.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English (the 维护者速读 block is the
lane's maintainer summary).
---
_Generated by [Claude
Code](https://claude.ai/code/session_016WV4cyQNVxpxREGfKkTsqs)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016WV4cyQNVxpxREGfKkTsqs
Co-authored-by: Claude <noreply@anthropic.com>1 parent ba5c754 commit f64724a
3 files changed
Lines changed: 212 additions & 0 deletions
File tree
- .changeset
- packages/services/service-analytics/src
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2772 | 2772 | | |
2773 | 2773 | | |
2774 | 2774 | | |
| 2775 | + | |
| 2776 | + | |
| 2777 | + | |
| 2778 | + | |
2775 | 2779 | | |
2776 | 2780 | | |
2777 | 2781 | | |
| |||
2780 | 2784 | | |
2781 | 2785 | | |
2782 | 2786 | | |
| 2787 | + | |
| 2788 | + | |
| 2789 | + | |
| 2790 | + | |
| 2791 | + | |
| 2792 | + | |
2783 | 2793 | | |
2784 | 2794 | | |
2785 | 2795 | | |
| |||
0 commit comments