Skip to content

Commit 253eab0

Browse files
docs(skills): the data skill's Field Groups section teaches a group's visibleWhen and requiresFeature (#30890)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoFLj7Er8ys8HFHE35FnUcar)_ domain:skills#2 · os-tesla · session_01L9qibxLYNwSMSVQkj55gA6 Fixes #30812 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:`skills/objectstack-data/references/examples-objects.md` 的 Field Groups 一节新增一条:整个字段分组(含标题)可以用 `visibleWhen`(对记录求值的 CEL,为 FALSE 或求值出错即隐藏)或 `requiresFeature`(取自 `PUBLIC_AUTH_FEATURES` 的部署特性名,未知名字解析即拒绝;解析时降成该分组的 `visibleWhen` 并去掉,与作者自写的 `visibleWhen` 以 AND 合成、作者谓词在前)隐藏,两者都只管界面、字段仍可经 API 读取;示例取真实的 `sys_organization`「集团结构」分组(`requiresFeature: 'multiOrgEnabled'`)。 - 为什么改:PR #30810 给字段分组加了 `requiresFeature`,技能文本仍只列 `icon`、`description`、`collapse`,读技能的 agent 写对象时不知道一个分组可以按谓词或按部署特性隐藏,会去改表单页面或者什么都不做。 - 风险与回滚:只改技能文本、一条 changeset 和 token 棘轮的一行(1756 → 1737,向下重锁到合并后的实测值:本 PR 削 5,已合并的 #30831 削 14 且未重锁);运行时零改动,行文逐词对照 `main` 上的 `object.zod.ts` 与 `public-auth-features.ts`。回滚即 revert。 - 先看:`skills/objectstack-data/references/examples-objects.md` 的 Field Groups 段(新增一条 + 四处删减);同文件 Quick-Start 的一行注释与 Metadata Protection 的一句删减;`scripts/check-skills-token-ratchet.mjs` 的 examples-objects 行。 - 两分钟验证:`node scripts/check-skills-token-ratchet.mjs | grep examples-objects`,预期 `is 1737 tokens (ceiling 1737; headroom 0)`;再对照 `packages/spec/src/data/object.zod.ts` 中 `requiresFeature` 的 describe 文本,技能句子与之一致。 - 需要你做的:本 PR 触 `skills/**`(Tier H),需要受管审批人在 GitHub 上 Approve 一次;之后席位落地。 ## What changes `skills/objectstack-data/references/examples-objects.md`, section "Field Groups (MVP)": one new bullet after the per-group options, stating what `main` does since PR #30810 (`ObjectFieldGroupSchema` in `packages/spec/src/data/object.zod.ts`, `lowerRequiresFeature` in `packages/spec/src/kernel/public-auth-features.ts`): - a whole group, header included, is hidden by `visibleWhen` (CEL over `record.*`; FALSE or a fault hides it — the slot is fail-closed) or by `requiresFeature` (a deployment feature flag from `PUBLIC_AUTH_FEATURES`; an unknown flag is refused at parse); - `requiresFeature` is lowered at parse into the group's `visibleWhen` and stripped from the output, AND-combined after an authored `visibleWhen` (the author's predicate first); - both are presentation only: the fields stay readable through the API; - the example is the real one: `sys_organization` hides Parent Organization and Sort Order on a single-organization deployment with `{ key: 'group_structure', label: 'Group Structure', requiresFeature: 'multiOrgEnabled' }` (`packages/platform-objects/src/identity/sys-organization.object.ts`). Not taught, on purpose: the gate's CEL spelling (`features.X != false` / `== true`), the four composition refusals (`visibleWhen: false`, a non-CEL or AST-only predicate, a blank `source`), the lint's accepted-trailing-gate rule, and the console's empty-`features` pre-fetch window. The skill names the two keys and the one behaviour an author must know; the rest is the schema's own docblock and refusal text. **Growth paid in the same file, by deletion (no re-wrap):** | Deleted | Why it was restatement | |:--|:--| | The section's intro sentence (`Organize fields into logical groups (e.g., "Contact Information", "Billing", "System") for forms, detail pages, and editors.`) | The heading says what the section is; the example right below shows those three labels; the `collapse` bullet already says groups render on forms, modals and detail pages. | | `; group labels are human-readable` | A truism beside the `label: 'Contact Information'` lines of the example. | | `— replaces the deprecated \`defaultExpanded\` flag, ADR-0085` | History of the `collapse` enum, not a rule; the enum's three values stay spelled out. | | The "Supported migrations at this layer" paragraph | Restates bullets 1 and 2 (edit the `fieldGroups` array; edit `Field.group`); its last sentence was a roadmap note. | | `— see above` in the Quick-Start `sharingModel` comment | A dangling pointer: the text it pointed at lives in `SKILL.md`, not in this file (the block was moved here verbatim). `// required in practice` stays. | | `The block is \`.strict()\`: \`reason\` is **required** (min 1 / max 500 chars) and unknown keys are rejected.` (Metadata Protection) | The schema block two lines above already says `REQUIRED — reason … (1–500 chars)`; strictness is taught once in `SKILL.md` (`ObjectSchema.create()` rejects unknown keys). | | `// src/objects/sys-user.object.ts` (Metadata Protection example) | A path comment the other two `os:check` blocks in this file do not carry. | `scripts/check-skills-token-ratchet.mjs`: the `examples-objects.md` row re-locks DOWN at the landed count, 1756 → 1737, zero headroom, with the usual comment: -5 from this PR's cut, -14 from the merged #30831 (its hook-example rename dropped the block's intro line and left the headroom unlocked). `.changeset/30812-field-group-visible-when-requires-feature.md`: `'@objectstack/skills': patch`, both declaration lines. No eval case: the `objectstack-data` evals convention (`skills/objectstack-data/evals/README.md`) lists the two fixtures' topic clusters and binds no taught key to a case; the card asked for one only if the convention asks. The seat may add one if it reads the convention wider. **Readings (os-dev rule for `skills/**`):** | Reading | Before (`origin/main` `8b9cc712`) | After (`62fd849b`) | |:--|:--|:--| | `skills/objectstack-data/references/examples-objects.md` lines | 195 | 191 | | `skills/objectstack-data/references/examples-objects.md` bytes / tokens (`check-skills-token-ratchet`, `ceil(bytes/4)`) | 6964 / 1741 (ceiling 1756, headroom 15) | 6945 / 1737 (ceiling 1737, headroom 0) | | Package, all 11 `skills/*/SKILL.md`, lines | 4525 | 4525 | | Package, all 11 `skills/*/SKILL.md`, tokens (gate formula) | 56670 | 56670 | ## Why #30812, the `Skills-docs: follow-up` PR #30810 declared (card #30692, ruling 6670372892 row 4 A). The key exists on `main` since that merge; until the skill names it, an agent reading the Field Groups section learns only `icon`, `description` and `collapse`, and either leaves a feature-bound section visible everywhere or reaches for a page layout to hide it. ## Tests No runtime code changes. Every word of the new bullet was read off `main` at `a2f523aa` (unchanged at `8b9cc712`): `ObjectFieldGroupSchema`'s `visibleWhen` / `requiresFeature` docblocks and `.describe()` texts, `lowerRequiresFeature` (`'visibleWhen'` slot: gate alone when no predicate is authored, `(AUTHORED) && GATE` otherwise), `z.enum(PUBLIC_AUTH_FEATURE_NAMES)` (unknown flag refused), and `sys_organization.object.ts` line 352 (the `group_structure` entry quoted verbatim). The pins behind them are `packages/spec/src/data/field-group-requires-feature.test.ts` and `packages/platform-objects/src/identity/sys-organization-group-structure-gate.test.ts`, unchanged here. `pnpm --filter @objectstack/spec check:skill-examples` (the three `os:check` blocks of this file among the 283 marked blocks) against freshly built `@objectstack/spec`, `@objectstack/client` and `@objectstack/client-react` declarations: `✅ 283 prose examples type-check across 3 surface(s)`, exit 0 — at `36152cd8` and again at the head `62fd849b`. ## Acceptance notes - Overlap: seat 1's PR #30831 (merged as `0e73de82` while this card was open) edits this file's Lifecycle Hooks block (hook name and its intro line); this diff stays inside the Field Groups section, the Quick-Start comment and Metadata Protection. `origin/main` `8b9cc712` was merged in (`909ea5ea`) with no conflict; the ratchet row was then re-locked at the merged count. `skills/objectstack-data/rules/field-types.md` (#30824) is untouched. - The title line `# Data — object authoring examples (moved verbatim from SKILL.md)` is left as is: the same convention heads four reference files, so changing one is a catalog decision, not this card's. - `check:skill-examples` refuses (exit 3, PREREQUISITE NOT MET) until `packages/client` and `packages/client-react` carry `dist/*.d.ts`; a spec-only build is not enough for it. ## Verification Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` against the three-file diff (no paths passed; the tool reads the change set off the merge base). Every line run with stdin closed, exit code landed to disk before any pipe (`COMMAND :: exit CODE`): - At `36152cd8` (before any `origin/main` merge; spec, client, client-react and lint built): 62 derived families, 62 run, 61 exit 0, 1 exit 3 (`pnpm check:dual-build-cjs-loads`, PREREQUISITE NOT MET: 31 packages had no `dist/`); `--ran`: 62 derived, 61 run, 1 NOT-MEASURED (derived from the recorded exit 3), 0 UNRUN. - At `c5cc868a` (after merging `origin/main` `18870c92` and a declarations build of every `./packages/**`): the same 62 families, 62 run, 62 exit 0; `--ran`: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN. - At `62fd849b` (the head: `origin/main` `8b9cc712` merged, the ratchet row re-locked at 1737): the same 62 families, 62 run, 62 exit 0; `--ran`: 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN. Ran lists: `skills-seat-2/out/30812-gates-ran-{36152cd,c5cc868a,62fd849b}.list` in the project files. <details><summary>Gate readings at 62fd849</summary> - `check-skills-token-ratchet`: `skills/objectstack-data/references/examples-objects.md is 1737 tokens (ceiling 1737; headroom 0)`, exit 0; `--self-test`: 97 cases pass, exit 0. - `check:skill-identifier-liveness`: `Leg 1: 486 citation(s) over 71 published file(s) … Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s)`, exit 0. - `check:dual-build-cjs-loads`: `116 published require entry point(s) across 71 package(s) load; 2456 emitted CommonJS file(s) parse`, exit 0 (declarations build of `./packages/**`; exit 3 without it). - `check-empty-changeset --base origin/main`: `1 declaring changeset(s) added`, no merge-base changeset modified, exit 0. `check-adr-0087-registration --base origin/main`: no declared-breaking changeset, exit 0. `check-changeset-no-major --base origin/main`: exit 0. - `check:doc-authoring`: `21232 customer-facing string(s) across 1711 spec sources clean`, exit 0. - `pnpm --filter @objectstack/spec run check:skill-refs`, `check:skill-compatibility`, `check:skill-frame-sync`, `check:pm-skill-id-lint`, `check:pm-governed-merges`, `check:lean-entry-closure`, `check:nul-bytes`, `check:role-word`, `check-comment-mask-corpus`, `check:pm-dispatch-gates` and the rest of the 62: exit 0. </details> Tier: `dispatch-gates --tier` on the three paths: `skills/**` clause ①, `claude-fable-5-1` mandated, no one-line exit; this thread runs on it. `check-governed-merges --test` on the three paths: Tier H (`skills/objectstack-data/references/examples-objects.md`). CI families (`Lint & Repo Gates`, `Test Core`, the rest) are the PR's check-runs, not re-run locally. Dogfood battery: n/a (no `!` in the title, no narrowing arm). ## 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` is CI's run (no source file in the diff). - [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. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01L9qibxLYNwSMSVQkj55gA6 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 1c34fd7 commit 253eab0

3 files changed

Lines changed: 32 additions & 18 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/skills': patch
3+
---
4+
5+
docs(skills): the data skill's Field Groups section teaches a group's `visibleWhen` and `requiresFeature`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`skills/objectstack-data/references/examples-objects.md` now names the two ways a whole field group (header included) is hidden: `visibleWhen`, a CEL predicate over the record that hides the group when FALSE or when it faults, and `requiresFeature`, a deployment feature flag from the `PUBLIC_AUTH_FEATURES` set, refused when unknown, lowered at parse into the group's `visibleWhen` and stripped, AND-combined after an authored `visibleWhen`; both are presentation only. The real example is `sys_organization`'s Group Structure section, hidden on a single-organization deployment with `requiresFeature: 'multiOrgEnabled'`. The text is paid for by deleting restatements in the same file (the intro sentence, the migrations paragraph, the deprecated-flag aside, the strictness sentence under Metadata Protection, a dangling "see above" and a path comment), so the file's token ceiling moves down, not up.

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1012,7 +1012,15 @@ export const CEILINGS = new Map([
10121012
// stale `{ type: 'lookup', reference: 'user' }` fields (an object that does
10131013
// not exist) became `type: 'user'`; -10 tokens, the row follows the
10141014
// measurement down, zero headroom.
1015-
['skills/objectstack-data/references/examples-objects.md', 1756],
1015+
// 1756 -> 1728: RE-LOCK at the landed count after two net cuts — the Field
1016+
// Groups section now names a group's `visibleWhen` and `requiresFeature`
1017+
// (lowered into `visibleWhen` at parse, unknown flag refused, AND-composed
1018+
// after an authored predicate) with the `sys_organization` example, paid by
1019+
// deleting restatement in the same file (-5), on top of the hook-example
1020+
// rename that dropped its intro line (-14) and left headroom, then the
1021+
// review's module pointer paid by the custom-Page aside (the ui skill's
1022+
// rule); the row follows the measurement down, zero headroom.
1023+
['skills/objectstack-data/references/examples-objects.md', 1728],
10161024
['skills/objectstack-data/references/lint-rules.md', 970],
10171025
// 1299 -> 1566 (#26297): the `columns` dataset key and `applyOn: 'create'`
10181026
// — a column a job or an admin owns after the first load is written on

‎skills/objectstack-data/references/examples-objects.md‎

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,24 @@
22

33
## Field Groups (MVP)
44

5-
Organize fields into logical groups (e.g., "Contact Information", "Billing",
6-
"System") for forms, detail pages, and editors.
7-
85
- Declare groups on `ObjectSchema.fieldGroups` — **array order is the display order**.
96
- Assign each field to a group via `Field.group`, which references an
107
`ObjectFieldGroup.key`. In-group display order equals the traversal order
118
of `fields`.
12-
- Group keys must be `snake_case`; group labels are human-readable.
9+
- Group keys must be `snake_case`.
1310
- Optional per-group: `icon`, `description`, and `collapse`
1411
(`'none'` always open · `'expanded'` collapsible, starts open ·
15-
`'collapsed'` collapsible, starts closed — replaces the deprecated
16-
`defaultExpanded` flag, ADR-0085). Groups render identically on forms,
17-
modals, and detail pages; for a bespoke single-page layout assign a
18-
custom Page instead.
12+
`'collapsed'` collapsible, starts closed). Groups render identically on forms,
13+
modals, and detail pages.
14+
- Hide a whole group, header included, with `visibleWhen` (CEL over
15+
`record.*`; FALSE or a fault hides it) or `requiresFeature` (a deployment
16+
feature flag from `PUBLIC_AUTH_FEATURES` in `@objectstack/spec/kernel`; an
17+
unknown flag is refused). At parse `requiresFeature` is lowered into the
18+
group's `visibleWhen` and stripped, AND-combined after an authored
19+
`visibleWhen`. Presentation only: the fields stay readable through the API.
20+
`sys_organization` hides Parent Organization and Sort Order on one
21+
organization:
22+
`{ key: 'group_structure', label: 'Group Structure', requiresFeature: 'multiOrgEnabled' }`.
1923

2024
<!-- os:check -->
2125
```typescript
@@ -44,10 +48,6 @@ export default ObjectSchema.create({
4448
});
4549
```
4650

47-
**Supported migrations at this layer:** add / rename / delete / reorder groups
48-
(edit the `fieldGroups` array), assign a field to a group (edit `Field.group`).
49-
Explicit per-field in-group ordering is deferred to a future iteration.
50-
5151
## Quick-Start Template
5252

5353
<!-- os:check -->
@@ -60,7 +60,7 @@ export default ObjectSchema.create({
6060
pluralLabel: 'Support Cases',
6161
description: 'A customer-reported issue tracked to resolution.',
6262
icon: 'life-buoy',
63-
sharingModel: 'private', // required in practice — see above
63+
sharingModel: 'private', // required in practice
6464
highlightFields: ['subject', 'status', 'priority'],
6565
enable: {
6666
trackHistory: true,
@@ -158,9 +158,6 @@ protection?: {
158158
}
159159
```
160160

161-
The block is `.strict()`: `reason` is **required** (min 1 / max 500 chars) and
162-
unknown keys are rejected.
163-
164161
| `lock` | Edit | Delete | Typical use |
165162
|:---|:---:|:---:|:---|
166163
| `none` (default) | ✅ | ✅ | Normal authored metadata |
@@ -172,7 +169,6 @@ unknown keys are rejected.
172169

173170
<!-- os:check -->
174171
```typescript
175-
// src/objects/sys-user.object.ts
176172
import { ObjectSchema } from '@objectstack/spec/data';
177173

178174
export const SysUserObject = ObjectSchema.create({

0 commit comments

Comments
 (0)