diff --git a/skills/objectstack-data/evals/README.md b/skills/objectstack-data/evals/README.md index 520a5e2be7f..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`), 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/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..1b06e63d635 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,14 @@ 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 (a `multiple` one only when the removal would empty it), refuse the delete +with `409 DELETE_RESTRICTED`. + ## Roll-up Summaries A parent `summary` field aggregates a child collection. The engine recomputes it