From dceca3722feb53f101c545343928a1f6b1828faf Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 04:09:23 +0000 Subject: [PATCH 1/2] docs(skills): the data skill teaches that a user field's declared deleteBehavior takes effect when the user is deleted The `user` row of the field-types rule lists `deleteBehavior` with the walk's behaviour (no default; declared, it acts as on a lookup; undeclared, the field keeps the deleted user's id), and the delete-behaviours section of the relationships rule names `lookup`, `tree` and a declaring `user` field among the reference types the walk reads. One eval case exercises it. Each ceiling is paid in place: the field-types Summary (Roll-up) block (restated by the relationships rule's Roll-up Summaries) and the relationships "When to Use lookup vs master_detail" bullets (restated by the Relationship Types table and the Incorrect vs Correct section; its lookup-to-user example contradicted the package's own eval) are deleted. Claude-Session: https://claude.ai/code/session_01J8VVAkZCePk1V8qh465qcT Co-authored-by: Claude --- skills/objectstack-data/evals/README.md | 2 +- .../evals/objects-fields-relationships.json | 19 +++++++++++++++ skills/objectstack-data/rules/field-types.md | 16 +------------ .../objectstack-data/rules/relationships.md | 23 +++++++------------ 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/skills/objectstack-data/evals/README.md b/skills/objectstack-data/evals/README.md index 520a5e2be7f..9b114c24cb8 100644 --- a/skills/objectstack-data/evals/README.md +++ b/skills/objectstack-data/evals/README.md @@ -5,7 +5,7 @@ JSON fixtures in the objectui shape (`skill_name`, `evals[]`: `prompt`, - `objects-fields-relationships.json` — naming, field types (`secret` vs `password`), relationships (junction vs multi-value lookup, - `deleteBehavior`), validation (`state_machine`, unique **index**), + `deleteBehavior`, a `user` field's included), validation (`state_machine`, unique **index**), conditional field rules. - `hooks-security-seeds-datasources.json` — search mirrors via hooks, permission-set scopes, external datasources, seeds (`externalId`, diff --git a/skills/objectstack-data/evals/objects-fields-relationships.json b/skills/objectstack-data/evals/objects-fields-relationships.json index 9b5fd4b95d0..8fc43804276 100644 --- a/skills/objectstack-data/evals/objects-fields-relationships.json +++ b/skills/objectstack-data/evals/objects-fields-relationships.json @@ -96,6 +96,25 @@ "name: 'crm_account'" ] } + }, + { + "id": 6, + "prompt": "On `ticket`, `owner` is the agent who holds it and must always be set; deleting that agent's user account must be blocked while they still hold tickets. `watchers` is several users, and a deleted user should just drop out of the list.", + "expected_output": "Both are `type: 'user'` fields, each with an authored `deleteBehavior` — a `user` field joins the referential delete walk only when it declares one. `owner`: `required: true` with `deleteBehavior: 'restrict'` (deleting a held user is refused with `409 DELETE_RESTRICTED`; `set_null` on a required field would refuse too). `watchers`: `multiple: true` with `deleteBehavior: 'set_null'` (only the deleted member is removed; an emptied set is `[]`). Neither is a `lookup` to a `user` object, which does not exist, and a `user` field with no `deleteBehavior` keeps the deleted user's id.", + "files": [], + "assertions": { + "must_contain": [ + "type: 'user'", + "deleteBehavior: 'restrict'", + "deleteBehavior: 'set_null'", + "multiple: true", + "required: true" + ], + "must_not_contain": [ + "reference: 'user'", + "deleteBehavior: 'cascade'" + ] + } } ] } diff --git a/skills/objectstack-data/rules/field-types.md b/skills/objectstack-data/rules/field-types.md index edcf01d3aaa..ea11da3edbb 100644 --- a/skills/objectstack-data/rules/field-types.md +++ b/skills/objectstack-data/rules/field-types.md @@ -93,7 +93,7 @@ options: [ | `lookup` | Reference another object (independent) | `reference`, `lookupFilters`, `multiple`, `deleteBehavior`, `lookupAutofill` | | `master_detail` | Parent–child with lifecycle control | `reference`, `deleteBehavior` (`cascade`/`restrict` — `set_null` is refused), `lookupAutofill` | | `tree` | Hierarchical self-reference | `reference` — optional; when written, this object's own name (any other object is refused at parse) | -| `user` | Person picker — a lookup specialized to `sys_user` (assignee, watchers). Stored identically to `lookup` | `multiple` (collaborators), `defaultValue: 'current_user'` | +| `user` | Person picker — a lookup specialized to `sys_user` (assignee, watchers). Stored identically to `lookup` | `multiple` (collaborators), `defaultValue: 'current_user'`, `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) | **`lookupAutofill: [{ target, source, overwrite? }]`** (single-value `lookup` / `master_detail`): a pick copies the picked record's `source` into this object's @@ -203,20 +203,6 @@ const total = { }; ``` -### Summary (Roll-up) - -```typescript -{ - type: 'summary', - summaryOperations: { - object: 'invoice_line_item', // child object to aggregate - field: 'amount', // child field - function: 'sum', // 'count' | 'sum' | 'min' | 'max' | 'avg' - // relationshipField / filter — optional (see rules/relationships.md) - }, -} -``` - ### Autonumber ```typescript diff --git a/skills/objectstack-data/rules/relationships.md b/skills/objectstack-data/rules/relationships.md index aa0277878fa..9f0e5244169 100644 --- a/skills/objectstack-data/rules/relationships.md +++ b/skills/objectstack-data/rules/relationships.md @@ -1,6 +1,6 @@ # Relationship Patterns -- [Relationship Types](#relationship-types) · [When to Use lookup vs master_detail](#when-to-use-lookup-vs-master_detail) +- [Relationship Types](#relationship-types) - [Patterns](#patterns) — [lookup](#one-to-many-lookup) · [master_detail](#one-to-many-master_detail) · [Junction Object](#many-to-many-junction-object) · [tree](#hierarchical-tree-self-reference) - [Delete Behaviors](#delete-behaviors) · [Roll-up Summaries](#roll-up-summaries) - [Inline Editing (Master-Detail Entry)](#inline-editing-master-detail-entry) · [Detail-page related lists](#detail-page-related-lists-the-read-side-mirror) @@ -16,20 +16,6 @@ Guide for modeling relationships between objects using `lookup`, `master_detail` | `master_detail` | Coupled (cascade delete) | Forced only under `controlled_by_parent`; else lint-warned | Inherits parent; owner opt-in `ownerMayEdit` (below) | Supported via `summary` | "Owned by" | | `tree` | Self-reference | Optional — and so is `reference`, which when written must name this object itself; any other object is refused at parse, not lint-warned | N/A | Not available | Hierarchical | -## When to Use lookup vs master_detail - -### Use `lookup` When: -- Child record can exist independently -- Parent deletion should only clear the FK (`deleteBehavior: 'set_null'`, the default) -- Relationship is optional -- **Example:** `task.assigned_to → user` (task can exist without assignment) - -### Use `master_detail` When: -- Child record is meaningless without parent -- Parent deletion should cascade to children -- Relationship is mandatory -- **Example:** `invoice_line_item.invoice_id → invoice` (line items belong to invoice) - ## Patterns ### One-to-Many: lookup @@ -186,6 +172,13 @@ an authored `set_null` is refused at publish (it would orphan the detail row). } ``` +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, refuse the delete with `409 DELETE_RESTRICTED`. + ## Roll-up Summaries A parent `summary` field aggregates a child collection. The engine recomputes it From 5039eb23986ee206b6513fa99042d7675e5d41c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 04:39:06 +0000 Subject: [PATCH 2/2] docs(skills): the required set_null refusal names the multi-valued case, and the eval index line reads as a sentence A required `multiple: true` field declaring `set_null` refuses the delete only when removing the member would empty the set; otherwise the member is removed and the delete goes through. The relationships rule's delete-behaviours paragraph now says so, since it binds a `user` field to the lookup behaviour. Claude-Session: https://claude.ai/code/session_01J8VVAkZCePk1V8qh465qcT Co-authored-by: Claude --- skills/objectstack-data/evals/README.md | 2 +- skills/objectstack-data/rules/relationships.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/skills/objectstack-data/evals/README.md b/skills/objectstack-data/evals/README.md index 9b114c24cb8..5baff576d2e 100644 --- a/skills/objectstack-data/evals/README.md +++ b/skills/objectstack-data/evals/README.md @@ -5,7 +5,7 @@ JSON fixtures in the objectui shape (`skill_name`, `evals[]`: `prompt`, - `objects-fields-relationships.json` — naming, field types (`secret` vs `password`), relationships (junction vs multi-value lookup, - `deleteBehavior`, a `user` field's included), validation (`state_machine`, unique **index**), + `deleteBehavior`, on a `user` field too), validation (`state_machine`, unique **index**), conditional field rules. - `hooks-security-seeds-datasources.json` — search mirrors via hooks, permission-set scopes, external datasources, seeds (`externalId`, diff --git a/skills/objectstack-data/rules/relationships.md b/skills/objectstack-data/rules/relationships.md index 9f0e5244169..1b06e63d635 100644 --- a/skills/objectstack-data/rules/relationships.md +++ b/skills/objectstack-data/rules/relationships.md @@ -177,7 +177,8 @@ 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, refuse the delete with `409 DELETE_RESTRICTED`. +field (a `multiple` one only when the removal would empty it), refuse the delete +with `409 DELETE_RESTRICTED`. ## Roll-up Summaries