Skip to content

Commit 95ba410

Browse files
objectstack-fleet[bot]os-teslaclaude
authored
docs(skills): the objectstack-data field-types formula row says a formula is no dataset or cube dimension (group key) either (#30921)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoSeQ817HX43HaExq6RE7aZU)_ Fixes #30824 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:`skills/objectstack-data/rules/field-types.md` 的 `formula` 行多了一个短句:formula 既不能做聚合,也不能做 dataset / cube 的维度(分组键),两者都需要一个存储字段。新增以删掉同文件 Formula 示例里的一条站内指路注释付账。 - 为什么改:PR #30815 之后,对 formula 的 dataset 或 `analyticsCubes` 维度在作者侧就被拒(lint `dimension-virtual-field-refused`、dataset 编译 `400 DATASET_INVALID`),查询侧按它分组一律 `400 INVALID_FIELD`;但技能文本只说了不能聚合,AI 作者读完仍可能写出这样的维度。 - 风险与回滚:只改技能文本,不改 schema、键或运行时行为;文件 token 3198 → 3199(上限 3207 不动)。回滚即 revert 本 PR。 - 先看:`skills/objectstack-data/rules/field-types.md` 第 131 行(formula 行);第 204 行删掉的注释;`.changeset/30824-formula-no-group-key.md`。 - 两分钟验证:`node scripts/check-skills-token-ratchet.mjs | grep field-types`,预期 `3199 tokens (ceiling 3207; headroom 8)` 且退出 0。 - 需要你做的:本 PR 触及 `skills/**`(Tier H),席位做契约复审后,请你批准,席位落地。 ## What changes - `skills/objectstack-data/rules/field-types.md`, the `formula` row: `virtual: not aggregatable, a dataset measure needs a stored field` becomes `virtual: neither aggregatable nor a dataset or cube dimension (group key); both need a stored field`. The pointer to objectstack-ui → Dashboards → Dataset-Bound Widgets stays. - The same file, the `### Formula` example: the trailing comment `// see the Calculated table` on the `returnType` line is deleted — it pointed at a table in the same file, 70 lines up, linked from the file's own contents list, so it carried no rule. This deletion pays for the clause. - `.changeset/30824-formula-no-group-key.md`: `'@objectstack/skills': patch`. Nothing else: no ratchet row moves (the count stays under its ceiling), no other file. ## Why Since PR #30815 (card #30773) a dataset or `analyticsCubes` dimension over a `formula` field is refused where it is authored: the authoring rule `dimension-virtual-field-refused` (`packages/lint/src/validate-dataset-measure-aggregates.ts`) on `os validate` / `os build` / `os lint` and the runtime dataset save, and the dataset compile with `400 DATASET_INVALID` (`assertDimensionFieldStored` in `packages/services/service-analytics/src/dataset-compiler.ts`). Since #29304 every analytics query grouping by one answers `400 INVALID_FIELD` at `groupBy.N`. The contract review of #30815 (should-fix 3, comment 6677341880) kept the skill line out of that PR because `skills/**` is Tier H; this is that follow-up. The row teaches only what `main` refuses today, in the words the rule explanation uses (`os explain dimension-virtual-field-refused`: "A dimension is a GROUP KEY"). ## Tests No test changes: a skill text change, with no executable example added. The touched skill's two eval fixtures (`skills/objectstack-data/evals/*.json`) are unchanged and still parse. ## Acceptance notes - The row keeps "not aggregatable" (the pre-existing shorthand). Strictly, a `count` over a formula is accepted by the measure table (`count` reads no value); the sibling rule `objectstack-ui/rules/dashboards.md` already says so under "Total over a computed value", which the row's pointer reaches. Not widened here; noted, not filed (class: none of a/b/c — a reading, no wrong answer from a public door). - Wording: "dataset or cube dimension" follows `dashboards.md`'s own phrase ("refused as a dataset or cube dimension and as `groupBy`"), where the cube is the `analyticsCubes` entry. ## Verification Readings per the skills PR rules (file before/after, bundle before/after), measured on `origin/main` `1697d5ca` vs this head `f89a2740`: | Reading | Before | After | |:--|:--|:--| | `rules/field-types.md` lines | 252 | 252 | | `rules/field-types.md` bytes / tokens (`ceil(bytes/4)`) | 12790 / 3198 | 12794 / 3199 (ceiling 3207, headroom 9 → 8) | | Package (sum of every `skills/**/SKILL.md`) lines / bytes | 4525 / 226885 | 4525 / 226885 | | Bundle total (whole shipped `skills/` tree, the ratchet's own line) | 227012 | 227013 | Growth paid by deletion: +34 bytes for the clause (56 → 90 bytes), −30 bytes for the deleted comment; net +4 bytes = +1 token. No line was re-wrapped; no ceiling moved. - `node scripts/check-skills-token-ratchet.mjs` on `f89a2740`: exit 0 — `skills/objectstack-data/rules/field-types.md is 3199 tokens (ceiling 3207; headroom 8)`; `57 authored bundle file(s) within their ceilings`. - `node scripts/pm/dispatch-gates.mjs --tier skills/objectstack-data/rules/field-types.md .changeset/30824-formula-no-group-key.md`: `claude-fable-5-1` MANDATORY (`skills/**`, no one-line exit) — the tier this thread ran on. - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` on `f89a2740` derived 43 families; the sweep (`</dev/null` per line, exit codes landed in `gates-ran-f89a2740.list`, reconciled with `--ran`) is reported with every exit code in the `os-dev-report` comment on #30824, since this body is written once at creation. - CI: the PR's own check-runs are the record for every family CI runs. Tier H by path (`skills/**`): draft until a governed approver's APPROVED review; the seat's contract review precedes it; the seat lands. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (No schema touched.) - [x] 2. Every name I added uses the casing for its kind. (No name added.) - [x] 3. No formatter ran; `pnpm lint` is CI's run. - [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: n/a. - [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. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. --- _Generated by [Claude Code](https://claude.ai/code/session_01L9qibzWJ43paU98P99Cmu9)_ --------- Co-authored-by: os-tesla <yinlianghui@steedos.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent ae59388 commit 95ba410

4 files changed

Lines changed: 30 additions & 9 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): the `objectstack-data` field-types formula row says a formula is no dataset or cube dimension (group key) either
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Before.** The `formula` row of `skills/objectstack-data/rules/field-types.md` said a formula is virtual and not aggregatable, and that a dataset measure needs a stored field. It said nothing about grouping by one, so an AI author reading the row had nothing telling it not to declare a dataset or `analyticsCubes` dimension over a formula.
11+
- **Now.** The row says a formula is neither aggregatable nor a dataset or cube dimension (group key), and that both need a stored field — what the platform refuses today: the authoring rule `dimension-virtual-field-refused`, the dataset compile's `400 DATASET_INVALID`, and every analytics query grouping by one, `400 INVALID_FIELD`.
12+
- `skills/objectstack-ui/rules/dashboards.md` (Dataset-Bound Widgets, the row's pointer): the multi-value breakdown bullet now names a `formula` beside the multi-value types as refused for a dataset or cube dimension and `groupBy`, so the reader lands on the dimension refusal too.
13+
- No schema, key or behaviour changes; the skill text only states refusals that already ship.

‎scripts/check-skills-token-ratchet.mjs‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,10 @@ export const CEILINGS = new Map([
707707
// headroom, ceiling +49, pinned AT the measurement. Standing ruling (2026-10-27, tap A,
708708
// verbatim and untranslated): 「A:先削重复,余额按实测提高」; recognised path per ruling
709709
// record 6513306068.
710-
['skills/objectstack-data/rules/field-types.md', 3207],
710+
// 3207 -> 3199 (#30824): RE-LOCK at the landed count. The formula row now also says
711+
// a formula is no dataset or cube dimension (group key), paid by the Formula example's
712+
// remaining `// see the Calculated table` pointer; 12794 bytes -> 3199 tokens, zero headroom.
713+
['skills/objectstack-data/rules/field-types.md', 3199],
711714
['skills/objectstack-data/rules/indexing.md', 3241],
712715
['skills/objectstack-data/rules/lifecycle.md', 1590],
713716
['skills/objectstack-data/rules/naming.md', 773],
@@ -863,7 +866,12 @@ export const CEILINGS = new Map([
863866
// The file is 25047 bytes -> 6262 tokens, zero headroom, ceiling +41, pinned AT the
864867
// measurement. Standing ruling (2026-10-27, tap A, verbatim and untranslated):
865868
// 「A:先削重复,余额按实测提高」; recognised path per ruling record 6513306068.
866-
['skills/objectstack-ui/rules/dashboards.md', 6262],
869+
// 6262 -> 6232 (#30824): RE-LOCK at the landed count. The multi-value breakdown bullet
870+
// adds a `formula` as a refused dimension and `groupBy` (a dataset dimension over one is
871+
// `DATASET_INVALID` at compile); paid by the `compareTo` paragraph's restated "no second
872+
// widget-side vocabulary" sentence and the `DatasetSchema` source pointer that
873+
// `references/_index.md` already carries; 24926 bytes -> 6232 tokens, zero headroom.
874+
['skills/objectstack-ui/rules/dashboards.md', 6232],
867875
// 3011 -> 3141 (#19715, card item 4): a 5-line table of contents at the top,
868876
// anchors to the file's existing headings, nothing else changed. +132 tokens,
869877
// of which the row's 2 tokens of headroom absorb 2; the CEILING moves 130.

‎skills/objectstack-data/rules/field-types.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ Stored as JSON on the parent row — no separate table / FK:
128128

129129
| Type | When to Use | Config |
130130
|:-----|:------------|:-------|
131-
| `formula` | Computed from an expression referencing other fields — virtual: not aggregatable, a dataset measure needs a stored field (objectstack-ui → Dashboards → Dataset-Bound Widgets) | `expression` (CEL, `record.` prefixes), `returnType` (`'number' \| 'text' \| 'boolean' \| 'date' \| 'currency'`); `'currency'` carries its own `currencyConfig` |
131+
| `formula` | Computed from an expression referencing other fields — virtual: neither aggregatable nor a dataset or cube dimension (group key); both need a stored field (objectstack-ui → Dashboards → Dataset-Bound Widgets) | `expression` (CEL, `record.` prefixes), `returnType` (`'number' \| 'text' \| 'boolean' \| 'date' \| 'currency'`); `'currency'` carries its own `currencyConfig` |
132132
| `summary` | Roll-up aggregation from child records | `summaryOperations` ({ object, field? (omit for `count`), function, relationshipField?, filter? }) |
133133
| `autonumber` | Auto-incrementing display format ({0000} counter + optional date / {field} tokens, resets per scope) | `format` (shorthand) / `autonumberFormat` (canonical) — e.g., `"CASE-{0000}"`, `"AD{YYYYMMDD}{0000}"` |
134134

@@ -201,7 +201,7 @@ import { F } from '@objectstack/spec';
201201
const total = {
202202
type: 'formula',
203203
expression: F`record.amount * record.tax_rate`,
204-
returnType: 'number', // see the Calculated table
204+
returnType: 'number',
205205
};
206206
```
207207

‎skills/objectstack-ui/rules/dashboards.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ and select named `dimensions` + `values`**; the dataset owns the base object,
3838
allowed joins, intrinsic filter, dimensions, and certified measures. A widget
3939
**requires `dataset` + `values`**; the closed schema rejects `object` /
4040
`categoryField` / `valueField` / `aggregate` by name, and one lacking `dataset`
41-
fails `os validate`. The dataset shape is
42-
`DatasetSchema` — see `node_modules/@objectstack/spec/src/ui/dataset.zod.ts`.
41+
fails `os validate`.
4342

4443
A widget's presentation-scope `filter` flows into the query as the runtime
4544
filter; keep `filter` on the widget when binding a dataset.
@@ -102,8 +101,9 @@ Standardized answers to the recurring ambiguous cases:
102101
`beforeInsert`/`beforeUpdate` hook writes, then `{ name: 'days_sum', aggregate: 'sum', field: 'days_count' }`.
103102
- **Breakdown by a multi-value field.** `multiselect`/`checkboxes`/`tags` and
104103
any `multiple: true` field is refused as a dataset or cube dimension and as
105-
`groupBy` (`INVALID_FIELD`; no per-member buckets). One widget per member,
106-
filtered `{ tags: { $contains: 'urgent' } }`, or a single-value axis.
104+
`groupBy` (`INVALID_FIELD`; no per-member buckets); so is a `formula` (at a
105+
dataset's compile, `DATASET_INVALID`). One widget per member, filtered
106+
`{ tags: { $contains: 'urgent' } }`, or a single-value axis.
107107
- **Filter by a parent's attribute** → a **dimension** (guaranteed to join); a
108108
lookup-path *filter* is not a reliable analytics-path construct.
109109
- **A dashboard filter driving several charts** → **not** a dataset: `globalFilters`
@@ -228,7 +228,7 @@ no second `filter` is required.
228228

229229
`compareTo` is `{ kind, dimension? }` — the same shape the analytics executor
230230
reads (`DatasetSelection.compareTo`), so what a widget declares is exactly what
231-
runs. There is no second widget-side vocabulary.
231+
runs.
232232

233233
| Key | Value | Behaviour |
234234
|:--|:--|:--|

0 commit comments

Comments
 (0)