Repository navigation
Commit 1756666
domain:server#3 · os-bill · session_01Utp6CbPHd9hdK2yxiVhr84
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgStX19XXyG1z2iVcTwB91bW)_
Fixes #30006
Clause-②: yes (widening)
Metadata-protocol: none
Skills-docs: updated here —
content/docs/deployment/validating-metadata.mdx
## 维护者速读
- 改了什么:仪表盘部件的 `filter` 现在可以直接写数据集维度名(如维度 `state` 对应字段
`status`),发布时不再误报"字段不存在";部件和报表两条规则改为共用同一套可接受键,写度量名的过滤键则给出专门的拒收提示。
- 为什么改:运行时数据集查询本来就按维度绑定这个键,发布检查却拒收,作者被迫改写底层字段名;度量名实测在运行时会被编成逐行比较原始列(sum
度量)、坏 SQL(count 度量)或 400(派生度量),所以继续拒收但讲清原因。
- 风险与回滚:只放宽(以前能发布的仍能发布),只动 `@objectstack/lint` 两条规则、说明文字和
`validating-metadata.mdx` 一句话;回滚即撤销本 PR。
-
先看:`packages/lint/src/validate-widget-bindings.ts`(`judgeDatasetFilterKeys`
的维度/度量分支)、`packages/lint/src/validate-report-runtime-filter.ts`、`packages/metadata-protocol/src/protocol.dashboard-dataset-publish-gate.test.ts`
的 #30006 块。
- 两分钟验证:`pnpm --filter @objectstack/metadata-protocol exec vitest run
src/protocol.dashboard-dataset-publish-gate.test.ts`,预期 19 个测试全绿,其中
#30006 块 4 个。
- 需要你做的:无,席位落地。
## What changes
`widget-filter-field-unknown` and `report-filter-field-unknown` now
judge a bare filter key against one accept set, held in
`judgeDatasetFilterKeys`
(`packages/lint/src/validate-widget-bindings.ts`), so the two rules
cannot drift:
1. a field of the dataset's object, authored or registry-injected
(unchanged, and first: the dataset door binds a key that names a field
to that field even where a member shares its name,
`dataset-filter-field-binding.ts`);
2. **new for the widget rule:** a dimension of the dataset. The report
rule already accepted this through its own local set, which is removed;
3. a bare key naming a **measure** of the dataset gets its own verdict
(`kind: 'measure'`) and its own message in both rules, instead of the
generic unknown-field verdict with a "Did you mean". The fix line names
the measure's column when it aggregates exactly one (`key the filter by
the field "amount"`), and points at fields and dimensions otherwise. A
measure sharing a field's name is still the field, and accepted.
The `os explain` entries for both rule ids state the accept set and why
a measure is refused. Rule ids, severities and every previously accepted
key are unchanged.
`content/docs/deployment/validating-metadata.mdx`: the package-closure
paragraph (added by #30231) said "a filter key the object lacks is
refused there"; it now reads "a filter key that neither the object nor
the dataset's dimensions name is refused there", which is what both
rules answer after this change.
### Which outcome for measures, and the evidence
Refusal with its own message. The card asked to measure whether a
measure filter is a supported run-time shape. Measured on `main` at
`fa9c0aa2` with a throwaway probe through
`AnalyticsService.queryDataset` (dataset `orders_states` over `orders`:
dimension `state` → field `status`; measures `amount_sum` = sum of
`amount`, `order_count` = count, `avg_amt` = derived ratio), both
strategies:
| `runtimeFilter` | NativeSQL | ObjectQL (engine aggregate) |
|:--|:--|:--|
| `{ state: 'open' }` (dimension) | `WHERE status = $1` | filter `{
status: 'open' }` |
| `{ amount_sum: { $gt: 100 } }` (sum measure) | `WHERE amount > $1`, a
row-level filter on the raw column, not on the sum | filter `{ amount: {
$gt: 100 } }` |
| `{ order_count: { $gt: 5 } }` (count measure) | `WHERE * > $1`, broken
SQL | filter on the key `*` |
| `{ avg_amt: { $gt: 1 } }` (derived measure) | `400 INVALID_FIELD`,
listing "Valid filter members: state" | `400 INVALID_FIELD` |
| `{ nope: 1 }` (CONTROL) | `400 INVALID_FIELD` | `400 INVALID_FIELD` |
So the door binds a dimension name the way the author means it, and
binds a measure name to something other than the measure: the raw column
for a sum or avg, no column for a count, nothing at all for a derived or
duration measure. The door's own refusal lists only dimensions as valid
filter members. Accepting measure keys would publish filters that
silently filter rows when the author meant to filter on the total. A
measure key was already refused by both rules (as an unknown field), so
the new message refuses nothing that published before: no narrowing.
## Tests
- `packages/lint/src/validate-widget-bindings.test.ts`, new block
`widget-filter-field-unknown — dataset dimensions and measures
(#30006)`: a dimension key is accepted (bare and under `$or` / `$not`);
a sum measure key is refused at its path with the measure message and
its column in the fix; a count and a derived measure are refused with
the no-single-column message; a measure sharing a field's name is
accepted; CONTROL: an unknown bare key and a dotted key are still
refused as unknown. The existing hint pin now reads "a field …, a
dimension of that dataset, or a relationship path …".
- `packages/lint/src/validate-report-runtime-filter.test.ts`: a report
`runtimeFilter` on a measure name is refused with the measure message;
the twin-parity test now includes the dimension key `state` and the
measure key `hours_sum`, and both rules answer them alike.
-
`packages/metadata-protocol/src/protocol.dashboard-dataset-publish-gate.test.ts`,
new block `#30006`, through the real save and publish path (draft PUT,
then publish): a widget filter on a dimension name publishes; a report
`runtimeFilter` on a measure name is refused `422 INVALID_METADATA` at
`reports[0].runtimeFilter.amount_sum` with the measure message, and
nothing active is stored; the widget twin refuses the same measure key;
CONTROL: an unknown key beside the dimension key is still refused as
unknown, as the only issue.
<details><summary>Test runs on the merged head (after merging main,
which brought #30231); the docs-only commit b498e10 changes no test
input</summary>
```
pnpm --filter @objectstack/lint typecheck exit 0
pnpm --filter @objectstack/lint test Test Files 195 passed (195) · Tests 8097 passed (8097)
pnpm --filter @objectstack/metadata-protocol typecheck exit 0
pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.dashboard-dataset-publish-gate.test.ts
Test Files 1 passed (1) · Tests 19 passed (19)
pnpm --filter @objectstack/spec check:liveness exit 0
```
</details>
<details><summary>Ablation (one, on the dimension accept)</summary>
Committed head `a8b06ca5`. `scripts/ablation-replace.mjs` replaced `if
(bare && dimensions.has(field)) return;` with a guard that never fires,
carrying the marker `ABLATION_30006`; `@objectstack/lint` was rebuilt
and `ablation-dist-preflight.mjs` found the marker in 4 built files,
because the door test reads `@objectstack/lint` from `dist/`. Result: 7
tests red, every one a dimension-key assertion. In lint: the new widget
dimension test, the report rule's dimension test and both of its
`validate` / `build` gate tests. At the door: the #29900 dimension
CONTROL, the new widget dimension test, and the new CONTROL board, which
carries the dimension key. Restore: blob equal to HEAD, `git diff HEAD`
empty, lint rebuilt, preflight `--absent` found the marker in none of 20
built files, and the tree was clean.
</details>
## Acceptance notes
- `skills/**` teaches widget `filter` and report `runtimeFilter` keys
that are fields of the dataset's object, which both rules accept
unchanged, and no page teaches a measure name as a filter key; the one
docs sentence the dimension accept widens is updated here (seat-widened
file surface, that sentence only).
- A dotted `cube.member` spelling (`orders_states.state`) is not given
the dimension accept: the card is about bare keys, and dotted keys keep
resolving as relationship paths.
## Verification
Derived gates (`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`) on head `b498e10f`: 87 families (the docs
commit added the documentation families), each run and recorded as
`COMMAND :: exit CODE`, all 87 exit 0; `--ran` reconciliation: 87
derived, 87 run, 0 NOT-MEASURED, 0 UNRUN. On the code head `a82a3a45`
the same derivation was 58 families, all exit 0. The `os-dev-report`
comment on #30006 carries the record.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(`packages/spec` untouched.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` is CI's.
- [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` 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.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Utp6CbPHd9hdK2yxiVhr84)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 739c972 commit 1756666
8 files changed
Lines changed: 284 additions & 36 deletions
File tree
- .changeset
- content/docs/deployment
- packages
- lint/src
- metadata-protocol/src
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
748 | 748 | | |
749 | 749 | | |
750 | 750 | | |
751 | | - | |
752 | | - | |
753 | | - | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
754 | 755 | | |
755 | 756 | | |
756 | 757 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
485 | 485 | | |
486 | 486 | | |
487 | 487 | | |
488 | | - | |
489 | | - | |
490 | | - | |
491 | | - | |
492 | | - | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
493 | 498 | | |
494 | 499 | | |
495 | 500 | | |
| |||
5570 | 5575 | | |
5571 | 5576 | | |
5572 | 5577 | | |
5573 | | - | |
5574 | | - | |
| 5578 | + | |
| 5579 | + | |
| 5580 | + | |
5575 | 5581 | | |
5576 | 5582 | | |
5577 | 5583 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
123 | 123 | | |
124 | 124 | | |
125 | 125 | | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
126 | 136 | | |
127 | | - | |
128 | | - | |
129 | | - | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
130 | 143 | | |
131 | 144 | | |
132 | 145 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | | - | |
| 20 | + | |
| 21 | + | |
21 | 22 | | |
22 | 23 | | |
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
27 | 28 | | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
33 | 35 | | |
34 | 36 | | |
35 | 37 | | |
| |||
51 | 53 | | |
52 | 54 | | |
53 | 55 | | |
54 | | - | |
| 56 | + | |
55 | 57 | | |
56 | 58 | | |
57 | 59 | | |
| |||
91 | 93 | | |
92 | 94 | | |
93 | 95 | | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | 96 | | |
100 | | - | |
101 | | - | |
| 97 | + | |
102 | 98 | | |
103 | 99 | | |
104 | 100 | | |
105 | 101 | | |
106 | 102 | | |
107 | 103 | | |
108 | | - | |
109 | | - | |
110 | | - | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
111 | 108 | | |
112 | 109 | | |
113 | 110 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1506 | 1506 | | |
1507 | 1507 | | |
1508 | 1508 | | |
1509 | | - | |
1510 | | - | |
| 1509 | + | |
| 1510 | + | |
1511 | 1511 | | |
1512 | 1512 | | |
1513 | 1513 | | |
| |||
1596 | 1596 | | |
1597 | 1597 | | |
1598 | 1598 | | |
| 1599 | + | |
| 1600 | + | |
| 1601 | + | |
| 1602 | + | |
| 1603 | + | |
| 1604 | + | |
| 1605 | + | |
| 1606 | + | |
| 1607 | + | |
| 1608 | + | |
| 1609 | + | |
| 1610 | + | |
| 1611 | + | |
| 1612 | + | |
| 1613 | + | |
| 1614 | + | |
| 1615 | + | |
| 1616 | + | |
| 1617 | + | |
| 1618 | + | |
| 1619 | + | |
| 1620 | + | |
| 1621 | + | |
| 1622 | + | |
| 1623 | + | |
| 1624 | + | |
| 1625 | + | |
| 1626 | + | |
| 1627 | + | |
| 1628 | + | |
| 1629 | + | |
| 1630 | + | |
| 1631 | + | |
| 1632 | + | |
| 1633 | + | |
| 1634 | + | |
| 1635 | + | |
| 1636 | + | |
| 1637 | + | |
| 1638 | + | |
| 1639 | + | |
| 1640 | + | |
| 1641 | + | |
| 1642 | + | |
| 1643 | + | |
| 1644 | + | |
| 1645 | + | |
| 1646 | + | |
| 1647 | + | |
| 1648 | + | |
| 1649 | + | |
| 1650 | + | |
| 1651 | + | |
| 1652 | + | |
| 1653 | + | |
| 1654 | + | |
| 1655 | + | |
1599 | 1656 | | |
1600 | 1657 | | |
1601 | 1658 | | |
| |||
0 commit comments