Skip to content

Commit 1c34fd7

Browse files
docs(skills): an objectstack-data eval relabels a vendor field with fieldPatches, never by redeclaring it in fields (#30941)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6Jao3wye4ZiYLUP17qLNb6899Z)_ domain:skills#2 · os-tesla · session_01L9qibyYUF3Rw1uw6uU1yUy Fixes #28804 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:`skills/objectstack-data/evals/objects-fields-relationships.json` 新增第 7 例:装了 HotCRM 后,作者想让 `crm_account.industry` 字段显示为 "Sector"。标准答案是在配对包 `hotcrm.custom` 里写 `defineObjectExtension({ extend: 'crm_account', fieldPatches: { industry: { label: 'Sector' } } })`;必含 `fieldPatches`、`hotcrm.custom`、`extend: 'crm_account'`、`label: 'Sector'`、`defineObjectExtension`,禁含把 `industry` 在 `fields` 里重新声明(`industry: Field.select(`)、`overrides:`、`name: 'crm_account'`。另加一条 changeset。技能正文零改动。 - 为什么改:技能的〈Object Extension Model〉教了"改厂商字段的显示用 `fieldPatches`,不要在 `fields` 里重声明",但没有任何评测覆盖它;以后为付 token 棘轮而删掉这段,什么都不会红。这一例让这条教学有了守门。 - 风险与回滚:只加一个评测夹具用例与一条 changeset,不改技能文本、不改运行时;评测夹具不计入 token 棘轮(裁决 6513306068 B)。回滚即 revert。 - 先看:`skills/objectstack-data/evals/objects-fields-relationships.json` 第 7 例(`expected_output` 的每句都对照 `main` 上 `SKILL.md` 〈Object Extension Model〉与 `packages/objectql/src/registry.ts` 的 `patchedField` 写成);`.changeset/28804-data-eval-field-patches.md`。 - 两分钟验证:`node scripts/check-skills-token-ratchet.mjs`,预期 `skills/objectstack-data/evals/objects-fields-relationships.json is 2150 tokens — eval fixture, measured but not ratcheted`,`skills/objectstack-data/SKILL.md is 6087 tokens (ceiling 6123)` 不变;再 `grep -c "label: 'Sector'" skills/objectstack-data/SKILL.md` 得 1,证明每个必含 token 都在技能里教过。 - 需要你做的:本 PR 触 `skills/**`(Tier H),需要受管审批人在 GitHub 上 Approve 一次;之后席位落地。 ## What changes `skills/objectstack-data/evals/objects-fields-relationships.json`: case 7, appended after case 5's `crm_account` extension (the fixture whose topic cluster holds object extensions; `hooks-security-seeds-datasources.json` has no extension case). - **Prompt:** HotCRM installed; its `crm_account` object's `industry` field should read "Sector" instead of "Industry", without touching HotCRM's source. - **Expected output:** `defineObjectExtension({ extend: 'crm_account', fieldPatches: { industry: { label: 'Sector' } } })` authored in `hotcrm.custom` and registered in `objectExtensions`; a patch carries display keys only (`label`, `description`, `inlineHelpText`, `placeholder`), the field's type, values and rules stay HotCRM's, a tenant-edited key keeps the tenant's text; never redeclare `industry` in `fields` (takes the field over, cuts it off from upgrades, and an unsuffixed field in a `.custom` extension is refused anyway); an option's label rides the same patch keyed by the option's stored `value` only for a field with its own `options`, a `picklist`-bound field's options are never patchable, a patch naming a field or option value the target lacks is refused at install and by `os validate`. - **`must_contain`:** `hotcrm.custom`, `defineObjectExtension`, `extend: 'crm_account'`, `fieldPatches`, `label: 'Sector'`. Each appears as a whole token in `skills/objectstack-data/SKILL.md` on `main` (1, 2, 1, 4, 1 hits; checked with the `IDENT_CHAR` boundary rule of `ui/scripts/check-skill-eval-tokens.mjs`, whose own population is `skills/objectui` only, so the check for this bundle is by hand). - **`must_not_contain`:** `industry: Field.select(` (the take-over spelling: HotCRM's field is `Field.select`), `overrides:` (replacing the object whole), `name: 'crm_account'` (redeclaring the object). None names a mechanism a right contrasting answer must spell. `.changeset/28804-data-eval-field-patches.md`: `'@objectstack/skills': patch`, both declaration lines. Every sentence of the expected output states what `main` does: `skills/objectstack-data/SKILL.md` 〈Object Extension Model〉 (the whitelist, the take-over rule, the `__c` refusal, the picklist rule), `packages/spec/src/data/object.zod.ts` (`FieldDisplayPatchSchema`, `refuseFieldPatchOfReplacedField`, `fieldPatchesDescription`), `packages/objectql/src/registry.ts` `patchedField` (the picklist-bound `options` skip), `packages/metadata-core/src/extension-field-patch-targets.ts` (the `field` / `option` / `picklist-option` misses the install door and `os validate` refuse by). **Readings (os-dev rule for `skills/**`):** | Reading | Before (`origin/main` `4d56914a`) | After | |:--|:--|:--| | `skills/objectstack-data/evals/objects-fields-relationships.json` lines | 120 | 140 | | same file, tokens (`check-skills-token-ratchet`, eval fixture: measured, not ratcheted) | 1749 | 2150 | | `skills/objectstack-data/SKILL.md` lines / tokens | 465 / 6087 (ceiling 6123) | unchanged | | Package, all 11 `skills/*/SKILL.md`, lines | 4525 | unchanged | No ceiling row moves; no skill text is added or deleted. ## Why #28804: an out-of-scope finding of PR #28795 (card #28730, which added the `fieldPatches` paragraph and is closed completed). With no eval, a later trim made to pay the token ratchet could drop the paragraph and nothing would fail. Evals sit outside the ratchet (ruling record 6513306068 B), so the case costs no skill text. ## Tests No runtime code changes. The fixture parses as JSON (7 cases, ids 1–7); the case's own `expected_output` contains every `must_contain` token and none of the `must_not_contain` tokens (self-check script in the thread). ## Acceptance notes - `skills/objectstack-data/evals/` has no runner in this repo; the fixture is parsed as JSON and measured by `check-skills-token-ratchet` (population 1b). The CI eval-token gate (`ui/scripts/check-skill-eval-tokens.mjs`) walks `skills/objectui` only, so the whole-token teaching check for this bundle was done by hand and is recorded above. - Out of scope, for the seat to judge (class c, noted, not filed): the skill's own example `fieldPatches: { industry: { label: 'Sector', options: { technology: { label: 'Tech' } } } }` (`SKILL.md` line 364, and the same example in `fieldPatchesDescription()` of `packages/spec/src/data/object.zod.ts`) patches an option of `crm_account.industry`, which in HotCRM is `Field.select({ picklist: 'industry' })` (`hotcrm` `src/sales/objects/account.object.ts`). A `picklist`-bound field's options are never patchable, so an author who copies the example against the real HotCRM is refused by `os validate` and the install door with the `picklist-option` miss (`extension-field-patch-targets.ts`). The eval's expected output therefore keeps the `options` form conditional ("only for a field that declares its own `options`"). Reach read off the judge's code path, not measured live. - Overlap: seat 2's open PRs #30890 (`references/examples-objects.md`) and #30921 (`rules/field-types.md`) touch other files; seat 1's #30805 declares `skills/**` wide. `origin/main` (`4d56914a`) had not moved at push time, so there was nothing to merge; a later move is merged on the next look. ## Verification Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` against the two-file diff at `73639a7c` (branch base `origin/main` `4d56914a`, unchanged at push time, so no merge was needed). Every line run with stdin closed, exit codes landed on disk first: **40 derived families, 40 run, 40 exit 0**; `dispatch-gates --ran` reconciliation: `40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN`. Ran list: `skills-seat-2/out/28804-gates-ran-73639a7c.list` in the project files. <details><summary>Gate readings at 73639a7</summary> - `check-skills-token-ratchet`: `skills/objectstack-data/evals/objects-fields-relationships.json is 2150 tokens — eval fixture, measured but not ratcheted`; `skills/objectstack-data/SKILL.md is 6087 tokens (ceiling 6123; headroom 36)`, unchanged; `57 authored bundle file(s) within their ceilings`, exit 0. - `check:skill-identifier-liveness`, `check:skill-compatibility`, `check:skill-frame-sync`, `check-empty-changeset --base origin/main`, `check-changeset-no-major --base origin/main`, `check-adr-0087-registration --base origin/main`, `check:pm-governed-merges`, `check:doc-authoring`, `check:nul-bytes`, `check:role-word`, `check-closing-keyword-parity`, `check-comment-mask-corpus`, the rest of the 34 no-build families: exit 0. - Six dist-reading families after a declarations build of `./packages/**` under the verify lock (`VERDICT command-exit 0`, 7m27s): `check:dual-build-cjs-loads` (`116 published require entry point(s) across 71 package(s) load; 2456 emitted CommonJS file(s) parse`), `check:lean-entry-closure`, `check:dts-closure` (`241/241 declared declaration file(s) present`), `check:published-files`, `check:sourcemap-no-sources-content`, `@objectstack/lint check:doc-formula-expressions`: exit 0. - `ui/scripts/check-skill-eval-tokens.mjs`: exit 0 (`Every must_contain token is taught by its own skill bundle`; population `skills/objectui` only, see Acceptance notes). - Tier: `dispatch-gates --tier` on the two paths: `skills/**` clause ①, `claude-fable-5-1` mandated, no one-line exit; this thread runs on it. `check-governed-merges --test` on the two paths: Tier H. </details> CI families (`Lint & Repo Gates`, `Test Core`, the rest) are the PR's check-runs, not re-run locally. ## 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_01L9qibyYUF3Rw1uw6uU1yUy --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7c1eeb1 commit 1c34fd7

2 files changed

Lines changed: 30 additions & 0 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): an objectstack-data eval checks that a vendor field is relabelled with `fieldPatches`, never redeclared in `fields`
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
`skills/objectstack-data/evals/objects-fields-relationships.json` gains case 7: an author wants HotCRM's `crm_account.industry` to read "Sector" instead of "Industry". The accept set requires `fieldPatches` on a `defineObjectExtension` authored in `hotcrm.custom` (an option's label keyed by its stored value where the field declares its own options); it refuses `industry` redeclared in `fields`, which takes the field over and cuts it off from HotCRM's upgrades. No skill text changes.

‎skills/objectstack-data/evals/objects-fields-relationships.json‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,26 @@
115115
"deleteBehavior: 'cascade'"
116116
]
117117
}
118+
},
119+
{
120+
"id": 7,
121+
"prompt": "We installed HotCRM (`hotcrm`, namespace `crm`). Its `crm_account` object's `industry` field should read \"Sector\" instead of \"Industry\" for our users, without touching HotCRM's source.",
122+
"expected_output": "An object extension in HotCRM's paired customization package `hotcrm.custom`, registered in that stack's `objectExtensions`: `defineObjectExtension({ extend: 'crm_account', fieldPatches: { industry: { label: 'Sector' } } })`. A patch carries display keys only (`label`, `description`, `inlineHelpText`, `placeholder`); the field's type, values and rules stay HotCRM's, so its upgrades still reach it, and a key the tenant edited keeps the tenant's text. Never redeclare `industry` in `fields`: that takes the field over whole and cuts it off from the owner's upgrades, and an unsuffixed field in a `.custom` extension is refused anyway. An option's label rides the same patch keyed by the option's stored `value` only for a field that declares its own `options`; HotCRM's `industry` takes its options from the `industry` picklist, and a `picklist`-bound field's options are never patchable, so no `options` entry belongs on this patch. A patch naming a field or option value the target lacks is refused at install and by `os validate`.",
123+
"files": [],
124+
"assertions": {
125+
"must_contain": [
126+
"hotcrm.custom",
127+
"defineObjectExtension",
128+
"extend: 'crm_account'",
129+
"fieldPatches",
130+
"label: 'Sector'"
131+
],
132+
"must_not_contain": [
133+
"industry: Field.",
134+
"overrides:",
135+
"name: 'crm_account'"
136+
]
137+
}
118138
}
119139
]
120140
}

0 commit comments

Comments
 (0)