Repository navigation
Commit b4a9d04
docs(skills): a user field's declared deleteBehavior is taught in the data skill's field-types and relationships rules (#29781)
domain:skills#1 · huangyiirene · session_01J8VVAnMUWA9rufXzFaEh4J
<!-- claude-projects-attribution: {"github_login":"huangyiirene"} -->
_Requested by **Irene** · [project
thread](https://claude.ai/code/project/chan_01TJXtQ1tVD26ecLDZxFS67x?thread=cmsg_01TJXtQ1tVD26ecLDZxFS67xRY2TwWjfFKsjhqoNR7z9DE)_
Fixes #29400
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:objectstack-data 技能的两份规则文件现在教「`user` 字段上作者写明的 `deleteBehavior`
在删除该用户时真的生效」:field-types 的 `user` 行列出 `deleteBehavior`(无默认;写了就按 lookup
的方式 cascade / set_null / restrict,没写则保留被删用户的 id);relationships 的 Delete
Behaviors 节点名删除联动读 `lookup`、`tree`,以及只在声明了值时才读的 `user` 字段。加一条 eval 用例练它。
- 为什么改:PR #29368 已把该行为合入引擎(card #29253),其 `Skills-docs: follow-up`
行欠下本卡;技能文本此前只说 user 字段「存储同 lookup」,没说删除用户时会发生什么,AI 写元数据时会把
`deleteBehavior` 当摆设或漏写。
- 风险与回滚:纯技能文本与 eval 夹具,无代码、无契约改动。两个 token 天花板都就地付账(删掉在本包别处已有完整版本的
Summary 示例块与 When-to-Use 列表),上限行未动,无 re-wrap。回滚即 revert。
- 先看:`skills/objectstack-data/rules/relationships.md` 新段(Delete
Behaviors 节末)与被删的 When to Use
节;`skills/objectstack-data/rules/field-types.md` 的 `user` 行与被删的 Summary
(Roll-up)
块;`skills/objectstack-data/evals/objects-fields-relationships.json` 的用例
6。
- 两分钟验证:`node scripts/check-skills-token-ratchet.mjs`,预期 field-types.md
3183/3207、relationships.md 3844/3873,全绿。
- 需要你做的:Tier H 受管面(`skills/**`),需要 `GOVERNED_APPROVERS` 的批准;PR 保持 draft。
## What changes
Facts are taken from current `main`, not the card text alone:
`content/docs/data-modeling/fields.mdx` as corrected by PR #29712,
`statesDeleteSemantics` / `cascadeRelationBehavior` in
`packages/objectql/src/engine.ts`, and the pins in
`packages/objectql/src/engine-cascade-delete-user-field.test.ts`.
1. **`skills/objectstack-data/rules/field-types.md`, the `user` row.**
The Key Config cell adds `deleteBehavior` (no default — declared,
deleting the user acts as on `lookup`: `cascade` deletes the holders,
`set_null` clears the field or drops that member, `restrict` refuses
with `409 DELETE_RESTRICTED`; undeclared, the field keeps the deleted
user's id). Everything else in the row is byte-identical.
2. **`skills/objectstack-data/rules/relationships.md`, Delete
Behaviors.** One new paragraph after the `master_detail` code block: the
walk also reads `lookup` and `tree` (`set_null` by default — the field
is cleared; a `multiple` field loses only that member — or an authored
`cascade` / `restrict`), and a `user` field only when it declares a
value: deleting the user then cascades, clears or refuses exactly as a
`lookup` does; undeclared, the field keeps the deleted user's id.
`restrict`, and `set_null` on a `required` field (a `multiple` one only
when the removal would empty it), refuse the delete with `409
DELETE_RESTRICTED` — the multi-valued narrowing is the engine's
(`cascadeDeleteRelations` escalates unconditionally only for a
single-valued field and otherwise refuses only the rows the removal
would empty; pinned in `engine-cascade-delete.test.ts`). The existing
`master_detail` text above it is byte-identical.
3.
**`skills/objectstack-data/evals/objects-fields-relationships.json`.**
Case 6: a required `owner` user field that must block deleting a held
user (`restrict`) and a multi-valued `watchers` field whose deleted
member should simply drop out (`set_null`). Must-contain pins the two
`deleteBehavior` values, `type: 'user'`, `multiple: true` and `required:
true`; must-not-contain refuses `reference: 'user'` and `deleteBehavior:
'cascade'`. The eval README's index line names the `user` case
("`deleteBehavior`, on a `user` field too"). Eval fixtures are measured,
not ratcheted.
### Token ratchet, paid in place (no ceiling row moved, no re-wrap)
| file | tokens before / after / ceiling | lines before / after |
|:--|:--|:--|
| `rules/field-types.md` | 3207 / 3183 / 3207 (headroom 0 → 24) | 264 /
250 |
| `rules/relationships.md` | 3869 / 3844 / 3873 (headroom 4 → 29) | 420
/ 414 |
| `evals/objects-fields-relationships.json` (not ratcheted) | 1436 /
1749 | 101 / 120 |
| `evals/README.md` (not ratcheted) | 142 / 148 | 12 / 12 |
| package `skills/objectstack-data` (17 files) | 44130 / 44399 | 3971 /
3970 |
The two payments, each a deletion of text restated elsewhere, with its
home:
- `field-types.md` → the **Summary (Roll-up)** block under Common Field
Configurations (14 lines, 361 bytes). Home: `rules/relationships.md` →
Roll-up Summaries carries the same `summaryOperations` block with the
same keys and comments plus the `filter` / `relationshipField` teaching;
the Calculated table row in field-types.md keeps the key shape (`{
object, field?, function, relationshipField?, filter? }`). The deleted
block's own last comment pointed at `rules/relationships.md`.
- `relationships.md` → the **When to Use lookup vs master_detail**
section (14 lines) and its link in the table of contents (613 bytes
together). Home: the Relationship Types table directly above it
(Lifecycle, Required, Use Case columns), the Incorrect vs Correct
section (lookup vs master_detail on invoice line items), and the
Patterns examples. Its one line not restated there — `set_null` is the
`lookup` default — moves into the new Delete Behaviors paragraph. Its
`task.assigned_to → user` example taught a `lookup` to a `user` object,
which this package's own eval 2 forbids (`reference: 'user'` is
must-not-contain; a `user` object does not exist), so that line was also
stale.
The package grows by about 270 tokens: the eval fixture and its index
+319 (not loaded into a customer session), the two loaded rule files −24
and −25. Their ceiling rows could be lowered to 3183 and 3844 in a
follow-up; this card's file surface does not include
`scripts/check-skills-token-ratchet.mjs`.
## Why
PR #29368 (card #29253) made a `user` field's declared `deleteBehavior`
take effect when the user is deleted, and its `Skills-docs: follow-up`
line owed this card. Before this change the data skill said only that a
`user` field is "stored identically to `lookup`", and its Delete
Behaviors section spoke of `master_detail` alone, so an AI authoring
metadata from the skill had no way to know that `deleteBehavior` on a
`user` field is now enforced, that it has no default, or that a
`required` field's `set_null` refuses the delete.
## Tests
- `node scripts/check-skills-token-ratchet.mjs`: exit 0 — `57 authored
bundle file(s) within their ceilings`; `rules/field-types.md is 3183
tokens (ceiling 3207; headroom 24)`, `rules/relationships.md is 3844
tokens (ceiling 3873; headroom 29)`.
- The eval fixture re-serializes byte-identically for cases 1–5 (checked
before appending case 6), and every `must_contain` string of case 6
appears in its `expected_output`.
- No code changed, so no package test, typecheck, ablation or dogfood
battery is owed (`Clause-②: no`, no `!` in the title).
## Acceptance notes
- The edit is made on the branch the platform fixes for this thread
(`claude/project-thread-5yjezo`) in this container's own checkout rather
than in a sibling worktree: the branch can be checked out in one place
only and the container is this card's alone. Noted as a deviation from
the worktree-first spelling, not from its purpose.
- `master_detail` text in both files is left exactly as it was; the card
asked for the `user` teaching only.
- The `lookup` row of field-types.md still lists `deleteBehavior`
without its default; relationships.md now states the default once. Not
expanded further (small feature, small text).
- Round 2 (commit 5039eb2) answers the seat's contract review of
dceca37 (comment 6595584700): M1, the required + `set_null` sentence
now carries the multi-valued qualifier; S1, the eval README index line
reads as a sentence. Nothing else moved.
`content/docs/data-modeling/fields.mdx` carries the same unqualified
sentence for a `user` field and is the docs lane's follow-up, not this
PR's.
## Verification
Head `5039eb23` (two commits: dceca37 the change, 5039eb2 the review's
M1 + S1). No code changed, so no package test or typecheck is owed; the
derived gate set is the verification.
<details><summary>Local gates on 5039eb2: 32 derived, 31 run with exit
0, 1 NOT MEASURED (`dispatch-gates --ran`); the same 32 on dceca37 read
the same</summary>
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 32 commands for the four changed
files, the same list on both heads. All 32 ran on each head in a tree
with `pnpm install --frozen-lockfile` done and `@objectstack/formula`,
`@objectstack/lint`, `@objectstack/spec` and `@objectstack/objectql`
built, each with a 900 s timeout, each exit code captured before any
pipe.
- 31 exited 0 on 5039eb2, among them `check-skills-token-ratchet.mjs`
(and its `--self-test`), `check-comment-mask-corpus.mjs`,
`check:doc-authoring`, `check:pm-skill-id-lint`,
`check:skill-compatibility`, `check:skill-frame-sync`,
`check:skill-identifier-liveness`, `check:role-word`, `check:nul-bytes`,
`check:pm-governed-merges`, `check:dts-closure` and
`check:lean-entry-closure` (on dceca37 the last exited 3 before the
`@objectstack/objectql` build and 0 after it).
- `pnpm check:dual-build-cjs-loads` exited 3: `PREREQUISITE NOT MET —
this gate reads built output, and some package has no dist/` (91
packages; it needs a full `pnpm build`). Not a finding: this diff
touches no `packages/**` file, and the gate's own text says nothing was
measured. CI's `Lint & Repo Gates` run on the PR is its reading.
- `--ran` printed: `32 derived famil(ies) accounted for — 31 run, 1
NOT-MEASURED (1 DERIVED from a recorded exit 3)`.
- `node scripts/check-commit-card-trailers.mjs --range
origin/main..HEAD`: exit 0 on both heads (two commits, no card relation,
model-free trailer pair).
- CI on dceca37: every check-run `success` or skipped by path filter or
opt-in, including the required Lint & Repo Gates, TypeScript Type Check,
Test Core, Build Core, Governed Surface Queue Guard, Test UI and Live
E2E UI. CI on 5039eb2: in progress at the time of writing.
- Writes: `git push` ×3 (the bare branch pointer, dceca37, 5039eb2);
one relay `pr_create` carrying `skip-changeset`; no GitHub MCP write.
</details>
## 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.
- [x] 3. No formatter ran; `pnpm lint` is CI's repo-wide run.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. Nothing publishes from a released package: `skip-changeset`
rides the create; both declaration lines are above.
- [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 (the 维护者速读 block is the
repo's own Chinese reading aid).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01J8VVAkZCePk1V8qh465qcT
---
_Generated by [Claude
Code](https://claude.ai/code/session_01J8VVAkZCePk1V8qh465qcT)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 6f35b74 commit b4a9d04
4 files changed
Lines changed: 30 additions & 31 deletions
File tree
- skills/objectstack-data
- evals
- rules
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
| 8 | + | |
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
99 | 118 | | |
100 | 119 | | |
101 | 120 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
96 | | - | |
| 96 | + | |
97 | 97 | | |
98 | 98 | | |
99 | 99 | | |
| |||
203 | 203 | | |
204 | 204 | | |
205 | 205 | | |
206 | | - | |
207 | | - | |
208 | | - | |
209 | | - | |
210 | | - | |
211 | | - | |
212 | | - | |
213 | | - | |
214 | | - | |
215 | | - | |
216 | | - | |
217 | | - | |
218 | | - | |
219 | | - | |
220 | 206 | | |
221 | 207 | | |
222 | 208 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
33 | 19 | | |
34 | 20 | | |
35 | 21 | | |
| |||
186 | 172 | | |
187 | 173 | | |
188 | 174 | | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
189 | 183 | | |
190 | 184 | | |
191 | 185 | | |
| |||
0 commit comments