Repository navigation
skills(objectstack-ui, objectstack-data): teach the aggregate × field-type rule and the multi-value dimension rule - #24439
Conversation
…-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>
Contract reviewServed-tier: Face: governed rules text ( ① Derived judgments
② Semver levelNo package is touched ( ③ Boundary flags
Implemented-by: 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, Generated by Claude Code |
维护者速读(终稿)改了什么:对外发布的两个 skill 各补一条规则,只在 objectstack-ui 的 为什么改:您在 cloud 卡 2861 裁 B 后的原话「2861 这个应该是 skills 也要优化吧」。平台三道门早已拒绝这两种写法(spec 的 aggregate × field-type 表、分析维度门与引擎 groupBy 门、 风险与代价(含回滚):只动 席位意见:规则文本席内达档复核 PASS(上一条评论)。两处小改留给补丁轮:例子里的存储字段换个名字(现在沿用 你要做的:一个字——A 或 B,回在卡 #24425 的决策评论下即可。之后本席续派 dev 打补丁、CI 全绿后请 os-zhuang / hotlong 审;Tier H,仍由您手合。 席位核对ESCALATE (content reviewed; one human-floor question) — PR #24439, head
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>
Contract reviewServed-tier: Face: governed rules text ( ① Derived judgments
② Semver levelNo package is touched ( ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
维护者速读(终稿)改了什么:对外发布的两个 skill 各补一条规则,只在 objectstack-ui 的 为什么改:您在 cloud 卡 2861 裁 B 后的原话「2861 这个应该是 skills 也要优化吧」。平台三道门早已拒绝这两种写法(spec 的 aggregate × field-type 表、分析维度门与引擎 groupBy 门、 风险与代价(含回滚):四个文件——三个 席位意见:ACCEPT。席内达档复核两次 PASS(round 1 的规则文本 6258981869;补丁 head 的增量 6259960374):规则与 spec 表、两道门、 你要做的:批准并手工合并本 PR(Tier H, 席位核对ACCEPT — PR #24439, head
Generated by Claude Code |
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.md6251/6252(余量 1),rules/field-types.md3116/3158,evals/analytics-inline-vs-dataset.json1229/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.jsoninscripts/check-skills-token-ratchet.mjsmoves 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: onorigin/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 existingexpected_outputstrings, 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 -con the fixture answers 4915, andceil(4915 / 4)is 1229. The record block beside the row mirrors the file's 「17310 允许增加」 convention; the shrink-only testimony aboveCEILINGSis untouched (pnpm check:ratchet-remedy-authorityexit 0;node scripts/check-skills-token-ratchet.mjs --self-testexit 0, 79 cases). No other ceiling moves, no new fixture, no new gate.What this teaches, measured against
mainat e22defbRule 1 — a dataset measure's
sum/avg/min/maxneeds a STORED field of an accepted type; aformulais virtual and refused whatever itsreturnTypesays. Sources read:packages/spec/src/data/aggregate-field-type-compatibility.ts(the one table: numeric class plus booleans,percentnot undersum,min/maxalso the temporal class;formulanamed 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 behindos 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 —sumover theformuladays_requested(end - start) is refused; a storednumberdays_count, written by abeforeInsert/beforeUpdatehook (thedefineHookevent 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'sgroupByalike. Sources read:packages/services/service-analytics/src/structured-json-dimension-door.tsandpackages/objectql/src/group-by-structured-json-door.ts(INVALID_FIELD/ 400 at both doors; the class isisMultiValueFieldinpackages/spec/src/data/field-value.zod.ts:multiselect/checkboxes/tags, ormultiple: trueonselect/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.tslowers$containson a multi-value or JSON-stored field as membership in the analytics where clause (whereContainsMembershipSqlinstrategies/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.mdcross-references from theformularow.skills/objectstack-ui/SKILL.mdis 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
skills/objectstack-ui/rules/dashboards.mdskills/objectstack-ui/SKILL.md(untouched)skills/objectstack-ui/evals/analytics-inline-vs-dataset.jsonskills/objectstack-data/rules/field-types.mdDeletions and folds, each with the site where the fact survives:
xAxis.field/yAxis[].field/series[].nameto those names;os validatechecks both halves);references/react-blocks.md, ObjectChart section (aggregateprop row states the same keying); eval 5's expected output carries a worked block; theformat: '$0,0'axis key survives in the next bullet (axis presentation rides on the axis)valuesentry …os validatefails on an unresolved name (an empty chart otherwise)"dataset+values; one lackingdatasetfailsos validate) andSKILL.md→ Verify your work → Dashboard widget binding (an unresolveddataset/dimensions/valuesrenders an empty chart)project, count related tasks" and "to-many rollup: not dataset-expressible — use a"summaryrollup)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"ratio/sum/difference/productof other measures)filterBindingsbroadcast into each chart's WHERE"; now points atglobalFilters+filterBindings(below); patch round: "reshapes the query (grain/window/join)" → "reshapes grain/window/join"filterBindingstable)multiple: truelookup ≠ junction object …" after the Relational tableSKILL.md→ Relationship Patterns: the same blockquote, a superset (it adds the positional{record.tags.0}reading), plus the Many-to-Many (simple) table rowdescriptionand the five existingexpected_outputstrings, eval 2'snote, and prompts 1 / 3 / 4 shortened (4388 → 3780 bytes)Line budget
skills/objectstack-ui/rules/dashboards.mdskills/objectstack-ui/SKILL.mdskills/objectstack-ui/evals/analytics-inline-vs-dataset.jsonskills/objectstack-data/rules/field-types.mdscripts/check-skills-token-ratchet.mjs(the ceiling row + its record block; unpublished)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 allSKILL.mdlines 4394 → 4394 (unchanged); wholeskills/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/lintanswers 0 files, nopackage.jsonscript is namedeval, andskills/objectstack-ui/evals/README.mddescribes 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 andcheck:skill-docs.formulaon the form, so a right answer stores the number under another name (the rule's example now does the same:days_count);must_not_containfield: 'days_requested'(a dataset measure or an inline aggregate over the formula) andsql: 'days_requested'(a cube measure over it) are what the wrong answer must spell;must_containnumber.must_not_containfield: '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 ontags;must_contain$contains.Verification — head
6acc9e9156acc9e915:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths; change set from the merge basee22defb13, 4 files) names 50 families (the ratchet script adds 19 to round 1's 31).--ranreconciliation: 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'sLint & Repo Gatescarries them (success on this head).node scripts/check-skills-token-ratchet.mjs0 — 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-test0 (79 cases);pnpm check:ratchet-remedy-authority0 (275 scripts swept);pnpm check:pm-dispatch-gates0 (the battery: 1913 fast-tier cases, 280 s, run via nohup andtail --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/lintunder 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-writeand 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.test:repois declared to CI. No ablation: no tested behaviour changed (published skill text, an eval fixture and a ceiling row).1b0c5012bis untouched (no rebase, no amend, no force-push); the patch round is one commit on top,6acc9e915.Acceptance notes
validate-dataset-measure-aggregates.tsdoes exist onmain—git cat-file -e e22defb13:packages/lint/src/validate-dataset-measure-aggregates.tssucceeds. 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), soos validaterefuses both shapes at authoring time. Cited above by its real path.structured-json-dimension-door.ts("The multi-value route is a RECORD query's membership filter … It is not this service's ownwhere") predatescontains-membership-sql.ts, which now lowers$containsas membership inNativeSQLStrategy.buildFilterClauseas well.skip-changeseton this PR (documentation,size/m) are the labeler's, not set by the dev, left alone.Generated by Claude Code