Skip to content

skills(objectstack-ui, objectstack-data): teach the aggregate × field-type rule and the multi-value dimension rule - #24439

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-24425-aggregate-field-type-rule
Oct 19, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/issue-24425-aggregate-field-type-rule

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #24425
Clause-②: no

维护者速读(草稿)

改了什么 — 对外发布的两个 skill 各补一条规则,规则只在 objectstack-ui 陈述一次,objectstack-data 只做交叉引用。① 数据集度量的 sum/avg/min/max 必须落在已存储的数值或布尔字段上;formula 字段是虚拟列(不落库),不管它声明的 returnType 是什么都会被拒 —— 要合计一个计算值,就把数存下来(用户录入、summary 汇总、或由 hook 维护的存储字段),或者把部件如实标成 count。② 多值字段(multiselect / checkboxes / tags,以及任何 multiple: true 的字段)不能当分组维度,数据集维度、cube 维度、引擎 groupBy 一视同仁 —— 改用按成员的 $contains 过滤(一个成员一个部件)或换单值轴。各配一个几行的例子(请假天数合计、按标签分布)。rules/field-types.md 的 formula 行加一句指向该规则。评测夹具加两条用例,must_not_contain 钉住错误答案必然写出的拼法。补丁轮(裁决 A 之后):scripts/check-skills-token-ratchet.mjs 里该评测夹具的上限按实测值从 1102 移到 1229,行旁按文件自身惯例写入裁决原话与算术;契约复核的两处小修 —— 请假天数例子里存储字段另起名(days_count,公式留在表单上)并对它求和,评测 7 的 must_not_contain 补上 cube 维度拼法 sql: 'tags'。

为什么改 — 维护者在 cloud 卡 2861 裁决 B 后的原话:「2861 这个应该是 skills 也要优化吧」。平台早已拒绝这两种写法(spec 的 aggregate × field-type 表、分析维度门与引擎 groupBy 门、以及 os validate 的 lint 腿),发布的 skill 却没教,AI 会先写出被拒的形状、再从拒绝里学。本 PR 把规则放到作者决定部件时已在读的位置(Dataset-Bound Widgets 的标准答案列表)。上限上移的依据是维护者 2026-10-19 对决策批次 369 第 1 项(字母 A)的裁决「其他同意」(卡上裁决评论 6259315766),按棘轮自身惯例「先付再抬」。

风险与代价(含回滚) — 四个文件:三个 skills/** 文本文件加棘轮脚本一行上限及其记录块;无平台代码,无 changeset(skip-changeset,scripts/ 不发布)。token 棘轮在最终 head 全绿:rules/dashboards.md 6251/6252(余量 1),rules/field-types.md 3116/3158,evals/analytics-inline-vs-dataset.json 1229/1229(按实测钉死,余量 0);新增 token 全部由同文件内的无损折叠或删除他处已有陈述支付(下表逐条列出存活位置),评测夹具付到底后的余额由裁决抬上限补齐(+127)。回滚 = revert 本 PR 的两个 commit,无数据与运行时影响。

席位意见 —

你要做的 — 批准并手合(Tier H,skills/**):本 PR 保持 draft,由你手工合并。

Ceiling raise — letter A, paid down first, raised for the remainder

Maintainer ruling 2026-10-19, decision batch 369 item 1, letter A (the card was presented as 「推荐:A」), verbatim and untranslated: 「其他同意」 — ruling comment 6259315766 on the card; the director's delta 6259193569 agreed. The row skills/objectstack-ui/evals/analytics-inline-vs-dataset.json in scripts/check-skills-token-ratchet.mjs moves from 1102 to 1229, pinned AT the measurement taken on the final head (zero headroom). Arithmetic, in bytes because that is what the convention divides: on origin/main (e22defb13) the file was 4388 bytes → 1097 tokens (headroom 5). The existing text was folded losslessly to 3780 bytes → 945 tokens first (the description, the five existing expected_output strings, eval 2's note and prompts 1 / 3 / 4 tightened, every fact and every assertion kept — the round-1 estimate said "about 1020"; measured, it is 945); the two cases then add +1135 bytes, so the landed file is 4915 bytes → 1229 tokens; the 5 tokens of existing headroom are absorbed and the ceiling moves 1102 → 1229 (+127). Reproducible without the script: wc -c on the fixture answers 4915, and ceil(4915 / 4) is 1229. The record block beside the row mirrors the file's 「17310 允许增加」 convention; the shrink-only testimony above CEILINGS is untouched (pnpm check:ratchet-remedy-authority exit 0; node scripts/check-skills-token-ratchet.mjs --self-test exit 0, 79 cases). No other ceiling moves, no new fixture, no new gate.

What this teaches, measured against main at e22defb

Rule 1 — a dataset measure's sum / avg / min / max needs a STORED field of an accepted type; a formula is virtual and refused whatever its returnType says. Sources read: packages/spec/src/data/aggregate-field-type-compatibility.ts (the one table: numeric class plus booleans, percent not under sum, min/max also the temporal class; formula named as VIRTUAL in SQL storage); packages/services/service-analytics/src/dataset-compiler.ts (DATASET_INVALID / 400 before any query is built); packages/lint/src/validate-dataset-measure-aggregates.ts (the authoring-time leg behind os validate); packages/spec/src/migrations/entries/semantic/18.dataset-measure-aggregate-field-type-refused.ts (the remedy: store the value as a number and aggregate that). Worked example: leave days — sum over the formula days_requested (end - start) is refused; a stored number days_count, written by a beforeInsert / beforeUpdate hook (the defineHook event names of objectstack-data's data-hooks reference), is summed instead, so the example and eval 6's prompt (the formula stays on the form) agree.

Rule 2 — a multi-value field cannot be a group-by dimension: dataset dimensions, a cube dimension and the engine's groupBy alike. Sources read: packages/services/service-analytics/src/structured-json-dimension-door.ts and packages/objectql/src/group-by-structured-json-door.ts (INVALID_FIELD / 400 at both doors; the class is isMultiValueField in packages/spec/src/data/field-value.zod.ts: multiselect / checkboxes / tags, or multiple: true on select / radio / lookup / user / file / image; no bucket-per-member grouping exists). The route the rule names, one widget per member filtered { tags: { $contains: 'urgent' } }, is a membership test on a dataset-bound widget too: packages/services/service-analytics/src/contains-membership-sql.ts lowers $contains on a multi-value or JSON-stored field as membership in the analytics where clause (whereContainsMembershipSql in strategies/native-sql-strategy.ts) and in the read scope; the engine path lowers it the same way.

Both rules live in skills/objectstack-ui/rules/dashboards.md, Dataset-Bound Widgets, as two entries of the standardized-answers list (right after the computed-column entry). skills/objectstack-data/rules/field-types.md cross-references from the formula row. skills/objectstack-ui/SKILL.md is untouched: its Detailed Rules line already points at the section. No new file, no platform code, no changeset.

Token ratchet — every added token paid in the same file; the fixture's remainder raised by ruling

file tokens before tokens after ceiling headroom after
skills/objectstack-ui/rules/dashboards.md 6243 6251 6252 1
skills/objectstack-ui/SKILL.md (untouched) 3843 3843 3851 8
skills/objectstack-ui/evals/analytics-inline-vs-dataset.json 1097 1229 1229 (was 1102; letter A) 0
skills/objectstack-data/rules/field-types.md 3157 3116 3158 42

Deletions and folds, each with the site where the fact survives:

# file deleted / folded survives at
1 dashboards.md ObjectChart jsx fence under the result-row-keying bullet (7 lines) the bullet's own prose directly above it (rows keyed by the raw field names; bind xAxis.field / yAxis[].field / series[].name to those names; os validate checks both halves); references/react-blocks.md, ObjectChart section (aggregate prop row states the same keying); eval 5's expected output carries a worked block; the format: '$0,0' axis key survives in the next bullet (axis presentation rides on the axis)
2 dashboards.md bullet "Dataset-bound widgets need at least one values entry … os validate fails on an unresolved name (an empty chart otherwise)" the section's opening paragraph (a widget requires dataset + values; one lacking dataset fails os validate) and SKILL.md → Verify your work → Dashboard widget binding (an unresolved dataset / dimensions / values renders an empty chart)
3 dashboards.md child-count entry: dropped the quoted restatement "on project, count related tasks" and "to-many rollup: not dataset-expressible — use a" the Level A table's right column (a to-many rollup onto the parent → escalate) and the iron rule (escalate to a stored field / a summary rollup)
4 dashboards.md computed-column entry: dropped the pseudo-example margin = difference(revenue, cost); "Formatting/currency → a measure's" folded to "Format/currency → the measure's"; patch round: "arithmetic over declared measures" → "arithmetic over measures" the Level A table (derived measures — ratio/sum/difference/product of other measures)
5 dashboards.md dashboard-filter entry: dropped "(date/region)" and "a dashboard variable + per-chart filterBindings broadcast into each chart's WHERE"; now points at globalFilters + filterBindings (below); patch round: "reshapes the query (grain/window/join)" → "reshapes grain/window/join" the Dashboard filters section of the same file (broadcast into every widget's query; the filterBindings table)
6 dashboards.md to-one-enrichment and filter-by-parent entries re-phrased ("is fine and does not change grain" → "keeps grain"; "model it as a dimension" → "a dimension") same entries, same facts
7 field-types.md the blockquote "multiple: true lookup ≠ junction object …" after the Relational table objectstack-data SKILL.md → Relationship Patterns: the same blockquote, a superset (it adds the positional {record.tags.0} reading), plus the Many-to-Many (simple) table row
8 evals json description and the five existing expected_output strings, eval 2's note, and prompts 1 / 3 / 4 shortened (4388 → 3780 bytes) every fact and every assertion-bearing clause kept; the caveat in eval 2's note kept (no other home)

Line budget

file lines before lines after
skills/objectstack-ui/rules/dashboards.md 468 465
skills/objectstack-ui/SKILL.md 308 308
skills/objectstack-ui/evals/analytics-inline-vs-dataset.json 57 77
skills/objectstack-data/rules/field-types.md 327 322
scripts/check-skills-token-ratchet.mjs (the ceiling row + its record block; unpublished) 1456 1477

Net +12 lines across the four skills/** files (budget ≤ +30); +21 in the ratchet script (the record block, in the file's own convention). git diff --numstat e22defb13...HEAD: 4 files, 77 insertions / 44 deletions; 52 of the insertions are new lines (two entries 11, two eval cases 20, the record block 21), the other 25 are in-place folds or edits of existing lines (the token payment, the two nits, the ceiling row) — the gross-additions line (≤ 45) is exceeded by those folds and the record block and is reported as a deviation. Bundle readings: sum of all SKILL.md lines 4394 → 4394 (unchanged); whole skills/ tree 13338 → 13350 lines over 65 files; ratcheted authored tokens 144061 → 144160 of 155015 (the ceiling sum moved by the +127; shipped tree 155247 → 155346).

Evals

No in-repo eval runner exists: git grep -l must_not_contain origin/main -- scripts packages/spec/scripts packages/lint answers 0 files, no package.json script is named eval, and skills/objectstack-ui/evals/README.md describes the fixtures as scored by an assistant harness. The card's acceptance line "the eval cases pass with the skill's eval runner" is therefore unmet by construction; the fixture's validity is JSON validity (parsed with the ids 1–7 read back), the token ratchet and check:skill-docs.

  • Case 6 (leave-days total): the prompt keeps the formula on the form, so a right answer stores the number under another name (the rule's example now does the same: days_count); must_not_contain field: 'days_requested' (a dataset measure or an inline aggregate over the formula) and sql: 'days_requested' (a cube measure over it) are what the wrong answer must spell; must_contain number.
  • Case 7 (by-tag breakdown): must_not_contain field: 'tags' (a dataset dimension declared over the multiselect), groupBy: 'tags' (an inline aggregate or an engine query) and, since the patch round, sql: 'tags' (a cube dimension, analytics.zod.ts) are what the wrong answer must spell; a right answer filters { tags: { $contains: TAG } } and never declares a dimension on tags; must_contain $contains.

Verification — head 6acc9e915

  • Derivation at 6acc9e915: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; change set from the merge base e22defb13, 4 files) names 50 families (the ratchet script adds 19 to round 1's 31). --ran reconciliation: 50 derived, 46 run, 4 NOT MEASURED, 0 UNRUN. The 4 NOT MEASURED answered exit 3 (PREREQUISITE NOT MET: the tree is not built, and the diff touches no package): check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure, check:sourcemap-no-sources-content — CI's Lint & Repo Gates carries them (success on this head).
  • Exit codes captured before any pipe, all 0 unless named: node scripts/check-skills-token-ratchet.mjs 0 — GREEN at the final head ("54 authored bundle file(s) within their ceilings"; the eval file "is 1229 tokens (ceiling 1229; headroom 0)", dashboards.md "6251 tokens (ceiling 6252; headroom 1)"); its --self-test 0 (79 cases); pnpm check:ratchet-remedy-authority 0 (275 scripts swept); pnpm check:pm-dispatch-gates 0 (the battery: 1913 fast-tier cases, 280 s, run via nohup and tail --pid); check:corpus-claim-drift, check:skill-identifier-liveness, check:doc-authoring, check:pm-skill-id-lint, check:nul-bytes, check:skill-frame-sync, check:skill-compatibility, check:skill-docs, check:doc-formula-expressions (after building @objectstack/formula + @objectstack/lint under the verify lock), the 19 added families (check-declaration-mirrors, check-position-name-fold-loaders, check-scripts-symbol-anchors, check-self-test-wired, check-self-test-workflow-commands, check-whole-set-label-write and their self-tests, bare-root-worklist --self-test, check:bash32-floor, check:cli-command-ids, check:entry-guard, check:parse-guard, check:pnpm-filter-targets, check:pnpm-flag-spellings) and every other derived family: 0.
  • No package is touched, so no build closure or package test is owed; test:repo is declared to CI. No ablation: no tested behaviour changed (published skill text, an eval fixture and a ceiling row).
  • The published commit 1b0c5012b is untouched (no rebase, no amend, no force-push); the patch round is one commit on top, 6acc9e915.

Acceptance notes

  • PM assumption falsified: the card's validate-dataset-measure-aggregates.ts does exist on main — git cat-file -e e22defb13:packages/lint/src/validate-dataset-measure-aggregates.ts succeeds. It is the authoring-time leg of the aggregate × field-type contract and also carries the dimension leg (dimension-json-stored-field-refused, a dataset dimension over a JSON-stored or multi-value field, and a cube dimension over such a column), so os validate refuses both shapes at authoring time. Cited above by its real path.
  • Observation, not filed (a stale code comment, no runtime effect; carrier: none): the module comment of structured-json-dimension-door.ts ("The multi-value route is a RECORD query's membership filter … It is not this service's own where") predates contains-membership-sql.ts, which now lowers $contains as membership in NativeSQLStrategy.buildFilterClause as well.
  • The harness's commit-attribution reminder names a model; the repository's pre-push hook refused that trailer on the first (unpublished) push of round 1, and the commit was amended to the model-free pair before anything reached the remote — no published history was rewritten.
  • Labels other than skip-changeset on this PR (documentation, size/m) are the labeler's, not set by the dev, left alone.

Generated by Claude Code

…-type rule and the multi-value dimension rule

Two standardized answers in rules/dashboards.md (Dataset-Bound Widgets):
a dataset measure's sum/avg/min/max needs a STORED field of an accepted
type (a formula is virtual, refused whatever its returnType says) and a
multi-value field cannot be a dataset, cube or groupBy dimension (per-member
$contains filter instead). One worked example each. The formula row of
objectstack-data's rules/field-types.md cross-references the rule. Two eval
cases in analytics-inline-vs-dataset.json pin the refused spellings.

Every added token is paid in the same file by lossless folds or by deleting
text stated elsewhere in the bundle; the eval fixture still exceeds its
shrink-only ceiling by the measured remainder, reported for the maintainer's
word.

Claude-Session: https://claude.ai/code/session_01V1upgJbYCZ5xed9pEQF4Tz
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 19, 2026
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Oct 19, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 1b0c5012bab37d1356b0157490b1f72d8082c879
Local-runs: none
Scope: skills/objectstack-ui/rules/dashboards.md, skills/objectstack-ui/evals/analytics-inline-vs-dataset.json, skills/objectstack-data/rules/field-types.md + packages/spec/src/data/aggregate-field-type-compatibility.ts (the table, L35–L41, L80–L105), packages/services/service-analytics/src/structured-json-dimension-door.ts, packages/objectql/src/group-by-structured-json-door.ts, packages/services/service-analytics/src/contains-membership-sql.ts (L103) + strategies/native-sql-strategy.ts (L2050), packages/spec/src/ui/dataset.zod.ts (L181, L254), packages/spec/src/data/analytics.zod.ts (L478), packages/lint/src/validate-dataset-measure-aggregates.ts (header), skills/objectstack-data/SKILL.md L325–L334, skills/objectstack-ui/SKILL.md L294–L295, skills/objectstack-ui/references/react-blocks.md L87, skills/objectstack-ui/rules/dashboards.md L207 — read on origin/main e22defb13 (the merge base)

Face: governed rules text (skills/**, Tier H); rendered in seat — get_session last_served_model equals the value of CONTRACT_REVIEW_TIER. Inputs: the card (body and three comments), the PR diff read through REST (+55/−43 over 3 files, merge base e22defb13), the PR body, the head's 43 check-runs. ⛔ Not the dispatch order's conclusions; the dev's report was read for its machine fields only.

① Derived judgments

  • Rule 1 (rules/dashboards.md, new entry 「Total over a computed value」): sum/avg/min/max need a STORED numeric or boolean field, percent not under sum, min/max also the temporal class (the entry says 「dates」; date/datetime/time all qualify — loose, not wrong); a formula has no column and is refused whatever returnType says. Matches AGGREGATE_FIELD_TYPE_COMPATIBILITY (numeric class number, currency, percent, rating, slider, progress, summary; boolean class; formula/autonumber named VIRTUAL). The remedies (store the number — user-entered, a summary rollup, or hook-kept — or title the widget count) match semantic migration 18's replacement text. Correct. Nit, non-blocking: the worked example keeps the NAME days_requested for the stored number (the formula replaced), while eval 6's prompt keeps the formula on the form and pins field: 'days_requested' as the wrong spelling — an agent that follows the example's naming under that prompt is flagged. Patch round: name the stored field apart from the formula (a few tokens).
  • Rule 2 (「Breakdown by a multi-value field」): multiselect/checkboxes/tags and any multiple: true field refused as a dataset or cube dimension and as groupBy, INVALID_FIELD, no per-member buckets; a per-member $contains filter or a single-value axis. Matches both doors and isMultiValueField; the widget-filter route is real on a dataset-bound widget: whereContainsMembershipSql (contains-membership-sql.ts L103) is called from native-sql-strategy.ts L2050. Correct.
  • field-types.md, the formula row: 「— virtual: not aggregatable, a dataset measure needs a stored field (objectstack-ui → Dashboards → Dataset-Bound Widgets)」 — one cross-reference clause, not a second statement. Correct.
  • Evals 6 and 7: the fixture's shape (id, prompt, expected_output, files, assertions); the file parses, ids 1–7. Dataset measure and dimension declarations carry field: (dataset.zod.ts L254 and L181), so field: 'days_requested' / field: 'tags' catch the dataset spellings, groupBy: 'tags' the inline and engine spelling, sql: 'days_requested' the cube measure. Gap, non-blocking: eval 7 lacks the cube-dimension spelling sql: 'tags' (analytics.zod.ts L478) — add in the patch round (about 4 tokens). must_contain: ["number"] on eval 6 is weak but harmless.
  • Payments, the PR body's eight ledger rows, each verified against the diff and origin/main e22defb13: Add metamodel interfaces for ObjectQL/ObjectUI contract #1 the JSX fence — the prose above it states the keying rule; react-blocks.md L87 (the aggregate prop row) and eval 5 carry a worked form. ✨ Set up Copilot instructions #2 the values/unresolved-name bullet — the section's opening paragraph (a widget requires dataset + values; one lacking dataset fails os validate) and objectstack-ui SKILL.md L294–L295 (an unresolved binding renders an empty chart). Implement ObjectStack protocol specification with Zod schemas and TypeScript interfaces #3/Add Changesets and GitHub Actions automation #4/Migrate from npm to pnpm #6 in-entry folds — every fact kept; the dropped margin = difference(revenue, cost) illustration is covered by the Level A derived-measures row. [WIP] Fix error in step four of the action run #5 the dashboard-filter entry now points at globalFilters + filterBindings 「(below)」 — that section exists at L207 of the same file. Remove explicit pnpm version from workflows to fix version conflict #7 the junction blockquote — objectstack-data SKILL.md L330–L334 carries the same blockquote as a superset, L325 the Many-to-Many row. Convert to monorepo with scoped packages #8 the eval strings — every assertion unchanged, every fact kept, eval 2's note kept. No re-wrap used as currency. Lossless to the seat's reading.
  • Token arithmetic recomputed from blob bytes (ceil(bytes/4)): dashboards.md 24969→25006 bytes = 6243→6252 (ceiling 6252, headroom 0); field-types.md 12627→12462 = 3157→3116 (ceiling 3158); the eval file 4388→4900 = 1097→1225 (ceiling 1102, over by 123); SKILL.md untouched. Matches the PR body and the report.
  • No issue or PR number in the skill text; English only; no platform code; no new file; the 3 files all under skills/**; Clause-②: no is right — no published accept set or public surface moves.

② Semver level

No package is touched (skills/** publishes on its own cadence); skip-changeset is the correct declaration, read back on the PR's labels. No semver level applies.

③ Boundary flags

  • open_questions (1): the eval fixture cannot hold both cases under its shrink-only ceiling (1225 vs 1102 after every lossless fold). A ceiling raise is gate weakening on the human floor (抬上限) — ⛔ not the seat's to decide: escalated to the maintainer on the card with the four-facet block and the arithmetic (A — raise that one row 1102→1225 / B — keep one case). The PR stays draft, red on that one gate until the word; neither option changes the rules text.
  • Correction the seat carries into its 速读终稿: the dev's draft says the ceiling row 「由席位代改」 — the seat never writes code; under A the dev carries the row edit in this PR's patch round, the ruling quoted in the PR body (the ratchet's convention: paid down first, raised for the remainder).
  • Deviations accepted: gross additions 55 > 45 (24 are in-place folds, the payment; net +12 ≤ +30 holds); 4 build-closure families NOT MEASURED locally (exit 3 on an unbuilt tree) — green on the head's Lint & Repo Gates and the three green type-check lanes; the first, unpublished commit's model-named trailer refused by the pre-push hook and amended before any publish (no published history moved); SKILL.md untouched (allowed).
  • PM assumption falsified and conceded: validate-dataset-measure-aggregates.ts exists at packages/lint/src/ (the seat's git cat-file -e looked under packages/spec/src/data/); the PR cites it by its real path. Acceptance-note observation (a stale module comment in structured-json-dimension-door.ts): no class, carrier: none — carried on the seat post, not filed.

Implemented-by: claude/issue-24425-aggregate-field-type-rule
Reviewed-by: session_01V1upgJbYCZ5xed9pEQF4Tz

VERDICT: PASS

The PASS covers the rules text and the payments at this head. Landing is gated separately: the maintainer's ceiling word on the card, the patch round carrying it and the two nits above, every check green, check-governed-merges.mjs --pr 24439, the two approvers' review, the maintainer's hand merge.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)

改了什么:对外发布的两个 skill 各补一条规则,只在 objectstack-ui 的 rules/dashboards.md(Dataset-Bound Widgets 的标准答案列表)写一次,objectstack-data 的 formula 行留一句引用。① 数据集度量的 sum/avg/min/max 必须落在已存储的数值或布尔字段上;formula 是虚拟列,不管 returnType 写什么都被拒——要合计一个计算值,就把数存下来(用户录入、summary 汇总、或由 hook 维护的字段),或把部件如实标成 count。② 多值字段(multiselect/checkboxes/tags,以及任何 multiple: true 的字段)不能当分组维度,数据集维度、cube 维度、引擎 groupBy 一视同仁——改为按成员 $contains 过滤、一个成员一个部件,或换单值轴。各配一个几行的例子;评测夹具加两条用例钉住错误写法。

为什么改:您在 cloud 卡 2861 裁 B 后的原话「2861 这个应该是 skills 也要优化吧」。平台三道门早已拒绝这两种写法(spec 的 aggregate × field-type 表、分析维度门与引擎 groupBy 门、os validate 的 lint 腿),发布的 skill 却没教,AI 会先写出被拒的形状、再从拒绝里学;cloud 侧的作者步骤已按裁 B 改好,这是另一半。

风险与代价(含回滚):只动 skills/** 三个文件,无平台代码、无 changeset。token 棘轮(只减不增):rules/dashboards.md 落在 6252/6252,rules/field-types.md 3116/3158;新增全部由同文件内的无损折叠或删除他处已有陈述支付,席位逐条核过每处删除的存活位置(PR 正文那八行账)。一处红:评测夹具 evals/analytics-inline-vs-dataset.json 折到底仍超上限 123 token(1225/1102)——现有文本从 1097 压到约 1020,两条用例约 205;上限只能凭您的字抬。CI 上唯一的红就是这道棘轮(Type Check · source gates 里的那一步,汇总进 TypeScript Type Check),main 上同两项是绿的。回滚 = revert 一个 commit,无数据与运行时影响。

席位意见:规则文本席内达档复核 PASS(上一条评论)。两处小改留给补丁轮:例子里的存储字段换个名字(现在沿用 days_requested,与评测 6「保留公式」的前提撞名);评测 7 补 cube 维度拼法 sql: 'tags'(约 4 token)。棘轮抬上限是人工地板项,席位不裁。推荐 A:按棘轮自身惯例「先付再抬」,把该夹具那一行上限抬到实测值(1225,连同两处小改约 1230,+约 130 token);理由:一条规则一条用例是卡的验收,多值维度这条正是 AI 在两道门都会撞的那条,且仓内没有 eval runner,用例是这段规则文本唯一的回归面。B 是回退项:只留 formula 那条,夹具落回 1097/1102 绿,多值那条另立卡等有余量的夹具——而姊妹夹具 views-apps-actions-pages.json 余量也是 0(1505/1505,席位复核)。dev 草稿里「那一行由席位代改」不成立:PM 不写代码;选 A 则由 dev 在本 PR 的补丁轮改那一行,并在正文引您的原话与算术。

你要做的:一个字——A 或 B,回在卡 #24425 的决策评论下即可。之后本席续派 dev 打补丁、CI 全绿后请 os-zhuang / hotlong 审;Tier H,仍由您手合。

席位核对

ESCALATE (content reviewed; one human-floor question) — PR #24439, head 1b0c5012b, reviewed by session_01V1upgJbYCZ5xed9pEQF4Tz (os-steve, domain:skills#1) against GitHub and origin/main e22defb13 (the merge base; the base check-run reading below is on the origin/main tip 538f923cb), not against the report.

  1. 形态: draft, base main, same-repo head; body line 1 Fixes #24425, line 2 Clause-②: no; the only closing keyword in the stored body is that Fixes; files = 3, all skills/** (+55/−43); labels skip-changeset (correct: the published skills ship on their own cadence), documentation (the labeler's), size/s; assignee os-steve; no reviewer requested yet (held until the patch round turns the PR green); body carries ## 维护者速读(草稿) with 席位意见 empty and ends with one session-URL footer; one commit 1b0c5012b on e22defb13 with the model-free trailer pair; mergeable true.
  2. 前提实测: the seat's re-checks at e22defb13 — formula 0 hits in rules/dashboards.md (control 2 in SKILL.md); no measure × field-type rule and no multi-value-dimension rule in the two skills (git grep -i -E 'aggregate|measure' over both); the platform sources named in the order unchanged; the dev's readings agree; the dev falsified the seat's path claim (packages/lint/src/validate-dataset-measure-aggregates.ts exists), conceded in the contract review. premise_still_valid: true holds. The card's acceptance line 「the eval cases pass with the skill's eval runner」 has no runner in the repo (0 files match must_not_contain under scripts, packages/spec/scripts, packages/lint; no package.json script named eval) — a card-premise reading, recorded on the card.
  3. 门禁 check-runs on 1b0c5012b (read 2026-10-19T03:26Z): 43 runs — 30 success, 10 skipped, 1 in progress (the Test Core aggregate; its six shards green), 2 failure: Type Check · source gates (the step 「Published skills token ratchet ([finding] the published skills bundle has no length budget — a maintainer ruling now establishes the principle, but nothing enforces it #10473) + its self-test」, outcome failure under continue-on-error; lane verdict 30 passed / 1 red) and its aggregate TypeScript Type Check — the eval file over its ceiling by 123, this PR's own and red by design until the maintainer's word; on origin/main 538f923cb both are success. Lint & Repo Gates success. Dev side: 29 derived / 25 run / 4 NOT-MEASURED (exit 3 prerequisite, build-closure families) at the final head, exit codes captured before any pipe (listed in the PR body); the seat re-ran no gate locally.
  4. 接受的偏差: gross additions 55 > 45 (24 are in-place folds, the payment; net +12 ≤ +30); the four NOT-MEASURED families read green on the head's check-runs; the first, unpublished commit's model-named trailer refused by the pre-push hook and amended before any publish; SKILL.md untouched; check:doc-formula-expressions first exit 3 then 0 after the closure build under the verify lock (held 117 s, waited 0 s).
  5. 范围外去向: one observation (a stale module comment in structured-json-dimension-door.ts), no class, carrier: none — carried on the seat post, not filed. Two nits of the seat's own (the example's field name; eval 7's cube spelling) ride the patch round, not a card.
  6. 落地条款: Tier H (skills/**: all 3 paths governed; 98 changed lines, far under 3000); contract review of record PASS on this head (the previous comment). The landing waits on ① the maintainer's A / B on skills(objectstack-ui, objectstack-data): teach the aggregate × field-type rule — a formula cannot be a sum / avg / min / max measure (store a number), a multi-value field cannot be a group-by axis (cloud#2861 ruling B; Tier H) #24425; ② the patch round (A: the ceiling row 1102 → the measured count, with the two nits, the ruling quoted in the PR body; B: eval 7 removed and a follow-up card filed for it); ③ every check green; ④ check-governed-merges.mjs --pr 24439; ⑤ review requests to os-zhuang and hotlong through the relay (pr_request_reviewers); ⑥ the maintainer's hand merge. Card skills(objectstack-ui, objectstack-data): teach the aggregate × field-type rule — a formula cannot be a sum / avg / min / max measure (store a number), a multi-value field cannot be a group-by axis (cloud#2861 ruling B; Tier H) #24425 moves to needs-user-decision this act (the raise question), assignee kept.

mcp_calls 0 · api_writes 2 relay strokes (pr_create with assignee and label, the report comment) · line_budget per file as the PR body's two tables, recomputed by the seat from blob bytes (6252 / 1225 / 3116) — machine fields read from the report and checked against the PR object, its labels and the branch.


Generated by Claude Code

…the measured value; the two review nits

Maintainer ruling 2026-10-19, batch 369 item 1, letter A (verbatim, untranslated:
「其他同意」): scripts/check-skills-token-ratchet.mjs moves the ceiling of
skills/objectstack-ui/evals/analytics-inline-vs-dataset.json from 1102 to the
measured 1229 (4915 bytes), paid down first (4388 -> 3780 bytes of existing
text, lossless) and raised for the remainder; the record block beside the row
carries the words and the arithmetic in the file's own convention.

Review nits: the leave-days example in rules/dashboards.md names the stored
number apart from the formula (days_count) and sums that field, paid by two
lossless tightens in the same file (6251/6252); eval 7's must_not_contain gains
the cube-dimension spelling sql: 'tags'.

Claude-Session: https://claude.ai/code/session_01V1upgJbYCZ5xed9pEQF4Tz
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m and removed size/s labels Oct 19, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 6acc9e9152a1e898b460b4abf5c9383b4c8237cd
Local-runs: none
Scope: scripts/check-skills-token-ratchet.mjs (L573–L597: the moved row and its record block), skills/objectstack-ui/rules/dashboards.md (L90–L108), skills/objectstack-ui/evals/analytics-inline-vs-dataset.json (eval 7), skills/objectstack-data/rules/field-types.md (unchanged since the round-1 record) + scripts/check-skills-token-ratchet.mjs L292–L330 (the raise convention) and L470–L486 (the 「17310 允许增加」 precedent block); the round-1 record 6258981869 carries the unchanged hunks — delta 1b0c5012b..6acc9e915 read through git; merge base e22defb13, origin/main tip fa26308bb at the read

Face: governed rules text (skills/**, Tier H) plus an unpublished repo gate (scripts/, not on the governed register); rendered in seat — get_session last_served_model equals the value of CONTRACT_REVIEW_TIER. Inputs: the card (body and eight comments, the ruling 6259315766 among them), the delta diff, the PR body, the head's 46 check-runs. ⛔ Not the patch order's conclusions; the dev's report was read for its machine fields only.

① Derived judgments

  • The ceiling row ['skills/objectstack-ui/evals/analytics-inline-vs-dataset.json', 1229], pinned at the measurement: the blob at 6acc9e915 is 4915 bytes, ceil(4915 / 4) = 1229 (recomputed by the seat from the blob, not from the report); 1102 → 1229 is +127 with the 5 tokens of headroom on main absorbed. The record block beside the row follows the 「17310 允许增加」 convention at L470–L486: the maintainer's words verbatim and untranslated 「其他同意」, decision batch Comprehensive architecture evaluation and 12-month transformation roadmap #369 item 1, letter A, the ruling comment id, the arithmetic in bytes (4388 → 3780 paid down first → +1135 for the two cases → 4915) and the reproducible one-liner. The shrink-only testimony block above the CEILINGS doc comment is untouched (the delta begins at L573); check:ratchet-remedy-authority and the ratchet's --self-test run green inside the head's Type Check · source gates lane. Matches ruling 6259315766 items 1–3: both cases stay, no other ceiling, no new fixture, no new gate. Correct.
  • Nit (a) resolved (rules/dashboards.md): the example now reads 「a stored number days_count a beforeInsert/beforeUpdate hook writes, then { name: 'days_sum', aggregate: 'sum', field: 'days_count' }」 — the stored number is named apart from the formula days_requested, which eval 6's prompt keeps on the form; example and eval agree. Paid in the same file by two lossless tightens: 「arithmetic over declared measures」 → 「arithmetic over measures」 (the Level A row already says a derived measure is arithmetic over other measures) and 「reshapes the query (grain/window/join)」 → 「reshapes grain/window/join」 (punctuation). 25001 bytes → 6251 of 6252, headroom 1. Correct.
  • Nit (b) resolved: eval 7's must_not_contain is ["field: 'tags'", "groupBy: 'tags'", "sql: 'tags'"] — the dataset dimension, the inline / engine groupBy and the cube dimension spellings; the fixture parses, ids 1–7. Correct.
  • field-types.md is unchanged since the round-1 record (12462 bytes → 3116 of 3158). Every other hunk is the round-1 text, reviewed PASS at 6258981869 and carried.
  • The diff is 4 files, +77/−44. check-governed-merges.mjs --pr 24439: 3 of 4 paths governed (skills/**, Tier H), the ratchet script not on the register, 121 changed lines ≤ 3000. Clause-②: no still right — no published accept set or public surface moves.

② Semver level

No package is touched (scripts/ and skills/** are both outside the published packages); skip-changeset is the correct declaration, read back on the PR's labels. No semver level applies.

③ Boundary flags

  • No open_questions. The round-1 estimate 「about 1020」 is corrected by measurement to 945 (3780 bytes); the record carries the measured numbers in bytes, as the convention asks, and the landed +127 is the 「about 1230」 the ruling anticipated with the two nits. Deviations accepted: gross additions 77 (25 in-place folds or edits, 21 the record block the ruling asks for; net +12 on the four skills/** files, inside the ≤ +30 budget); 4 build-closure families NOT MEASURED locally (exit 3 on an unbuilt tree), green on the head's Lint & Repo Gates.
  • The out-of-scope observation (a stale module comment in structured-json-dimension-door.ts) is unchanged from round 1 — carried on the seat post, not filed.

Implemented-by: claude/issue-24425-aggregate-field-type-rule
Reviewed-by: session_01V1upgJbYCZ5xed9pEQF4Tz

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读(终稿)

改了什么:对外发布的两个 skill 各补一条规则,只在 objectstack-ui 的 rules/dashboards.md(Dataset-Bound Widgets 的标准答案列表)写一次,objectstack-data 的 formula 行留一句引用。① 数据集度量的 sum/avg/min/max 必须落在已存储的数值或布尔字段上;formula 是虚拟列,不管 returnType 写什么都被拒——要合计一个计算值,就把数存下来(用户录入、summary 汇总、或由 hook 维护的字段),或把部件如实标成 count。② 多值字段(multiselect/checkboxes/tags,以及任何 multiple: true 的字段)不能当分组维度,数据集维度、cube 维度、引擎 groupBy 一视同仁——改为按成员 $contains 过滤、一个成员一个部件,或换单值轴。各配一个几行的例子;评测夹具加两条用例钉住错误写法。按您的裁决 A,评测夹具在棘轮里的上限从 1102 抬到实测的 1229(+127),棘轮脚本里留了按惯例写的记录块(您的原话、算术、可复现命令)。

为什么改:您在 cloud 卡 2861 裁 B 后的原话「2861 这个应该是 skills 也要优化吧」。平台三道门早已拒绝这两种写法(spec 的 aggregate × field-type 表、分析维度门与引擎 groupBy 门、os validate 的 lint 腿),发布的 skill 却没教,AI 会先写出被拒的形状、再从拒绝里学;cloud 侧的作者步骤已按裁 B 改好,这是另一半。抬上限是第 369 批第 1 项您的「其他同意」(裁决记录 6259315766)。

风险与代价(含回滚):四个文件——三个 skills/** 文本文件,加棘轮脚本一行上限及其记录块;无平台代码、无 changeset。token 棘轮在最终 head 全绿:rules/dashboards.md 6251/6252,rules/field-types.md 3116/3158,评测夹具 1229/1229(余量 0,下一个 token 仍要在本文件内付账)。客户侧代价是每次加载该夹具多约 130 token(整包约 15.5 万的千分之一)。CI 46 项全部绿或预期 skip,三个必查 job(Lint & Repo Gates、TypeScript Type Check、Type Check · source gates)全绿。回滚 = revert 本 PR 的两个 commit,无数据与运行时影响。

席位意见:ACCEPT。席内达档复核两次 PASS(round 1 的规则文本 6258981869;补丁 head 的增量 6259960374):规则与 spec 表、两道门、$contains 下沉路径一致;八处付账的存活位置逐个在 main 上找到;token 算术按字节重算一致;两处小改已落。无需您再裁的事。

你要做的:批准并手工合并本 PR(Tier H,skills/**);已向 os-zhuang / hotlong 请审。合并后本席按您「当前任务处理完,合并后就下班」收班。

席位核对

ACCEPT — PR #24439, head 6acc9e915, reviewed by session_01V1upgJbYCZ5xed9pEQF4Tz (os-steve, domain:skills#1) against GitHub and origin/main fa26308bb (merge base e22defb13), not against the report.

  1. 形态: draft, base main, same-repo head; body line 1 Fixes #24425, line 2 Clause-②: no; the only closing keyword in the stored body is that Fixes; files = 4 (three skills/**, the unpublished scripts/check-skills-token-ratchet.mjs), +77/−44; labels skip-changeset (correct), documentation and size/m (the labeler's); assignee os-steve; body carries ## 维护者速读(草稿) with 席位意见 empty, the 「Ceiling raise」 paragraph with the maintainer's words and the per-file arithmetic, and ends with one session-URL footer; two commits 1b0c5012b and 6acc9e915 on e22defb13, model-free trailer pairs, no rebase or force-push; mergeable true, mergeable_state clean.
  2. 前提实测: the round-1 readings stand (formula 0 hits in rules/dashboards.md on main; no measure × field-type and no multi-value-dimension rule in the two skills; the platform sources unchanged); ruling 6259315766 (letter A) applied as written — the row at the measured value, both cases kept, nothing else moved; the fixture's blob at 6acc9e915 is 4915 bytes = 1229 tokens, recomputed by the seat. premise_still_valid: true holds; the eval-runner premise (none in the repo) recorded on the card and in the PR body.
  3. 门禁 check-runs on 6acc9e915 (read 2026-10-19T04:55Z): 46 runs — 36 success, 10 skipped, 0 in progress, 0 failed; required: Lint & Repo Gates success, TypeScript Type Check success, Type Check · source gates success (the ratchet step green); check-expected-skips.mjs --pr 24439: every one of the 10 skips in the roster (exit 0). Dev side: 50 derived / 46 run / 4 NOT-MEASURED (exit 3 prerequisite, build-closure families) at the final head, exit codes captured before any pipe; the seat re-ran no gate locally.
  4. 接受的偏差: gross additions 77 > 45 (25 in-place folds or edits, 21 the record block the ruling asks for; net +12 ≤ +30 on the four skills/** files); the four NOT-MEASURED families green on the head's check-runs; the round-1 「about 1020」 estimate corrected to the measured 945 (3780 bytes) in the record; SKILL.md untouched; the dev's first issue_patch attempt stopped by its own angle-bracket check before dispatch (respelled, nothing sent).
  5. 范围外去向: one observation (a stale module comment in structured-json-dimension-door.ts), no class, carrier: none — carried on the seat post, not filed.
  6. 落地条款: Tier H (check-governed-merges.mjs --pr 24439: 3 of 4 paths governed under skills/**; 121 changed lines ≤ 3000); contract review of record PASS on this head (6259960374) and on the rules text (6258981869); every check green or an expected skip; mergeable_state clean. The four-piece set: this record (速读 pinned above, 席位核对 below); the card goes needs-user-decision with the pointer line this act; review requested from os-zhuang and hotlong through the relay (pr_request_reviewers); the round report lists this PR under awaiting a human merge. ⛔ The seat flips nothing ready and arms nothing: the maintainer's hand merge (or an authorized APPROVED review, after which the seat lands it) ends the window. Seat follows it to MERGED, then signs off per the maintainer's close-of-shift word.

mcp_calls 0 · api_writes 3 this round (git push, the relay issue_patch on the PR body, the report comment) · line_budget per file as the PR body's tables, recomputed by the seat from blob bytes (6251 / 1229 / 3116) — machine fields read from the report and checked against the PR object, its labels and the branch.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review October 19, 2026 05:33
@os-zhuang
os-zhuang enabled auto-merge October 19, 2026 05:33
@os-zhuang
os-zhuang added this pull request to the merge queue Oct 19, 2026
Merged via the queue into main with commit 31ce45c Oct 19, 2026
47 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-24425-aggregate-field-type-rule branch October 19, 2026 05:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants