Skip to content

Commit 8b60601

Browse files
committed
docs(spec): write the stored-metadata reference convention into the reference describe and the field-types page
Claude-Session: https://claude.ai/code/session_019oFY6Hi35J7atvgVFnmoaK Co-authored-by: Claude <noreply@anthropic.com>
1 parent 17e4d12 commit 8b60601

2 files changed

Lines changed: 19 additions & 2 deletions

File tree

‎content/docs/data-modeling/field-types.mdx‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ Reference to a record in another object (foreign key).
307307

308308
| Property | Type | Default | Description |
309309
|:---|:---|:---|:---|
310-
| `reference` | `string` | **required** | Target object name (snake_case) |
310+
| `reference` | `string` | **required** | Target object name (snake_case). A stored-metadata table (`sys_metadata`, `sys_metadata_history`) is refused — see [below](#a-relationship-never-targets-a-stored-metadata-table) |
311311
| `referenceFilters` | `string[]` | — | **Removed** (#2377, ADR-0049) — no longer a recognized field property. `FieldSchema` is a strict object, so an unknown key is **rejected with guidance**, not silently stripped (ADR-0078): the error echoes the offending key and prescribes the replacement. Use structured `lookupFilters` + `dependsOn` instead; see [Relationships](/docs/data-modeling/relationships) |
312312
| `deleteBehavior` | `'restrict' \| 'cascade' \| 'set_null'` | `'set_null'` | Behavior when referenced record is deleted. On a *required* lookup `set_null` is escalated to `restrict`, since a NOT NULL foreign key cannot be cleared — **whether the `set_null` was defaulted or written out explicitly**. On a `multiple: true` required lookup the escalation is judged per referencing row: only a row the member removal would leave EMPTY is refused. `cascade` and `restrict` are the values honored as written. Where `set_null` does run, a `multiple: true` lookup loses only the deleted **member** — the other members are kept, and a set emptied that way is stored as `[]`, never `null` |
313313

@@ -329,7 +329,7 @@ Parent-child relationship (cascading delete by default).
329329

330330
| Property | Type | Default | Description |
331331
|:---|:---|:---|:---|
332-
| `reference` | `string` | **required** | Target (master) object name |
332+
| `reference` | `string` | **required** | Target (master) object name. A stored-metadata table is refused — see [below](#a-relationship-never-targets-a-stored-metadata-table) |
333333
| `referenceFilters` | `string[]` | — | **Removed** (#2377, ADR-0049) — no longer a recognized field property. `FieldSchema` is a strict object, so an unknown key is **rejected with guidance**, not silently stripped (ADR-0078): the error echoes the offending key and prescribes the replacement. Use structured `lookupFilters` + `dependsOn` instead; see [Relationships](/docs/data-modeling/relationships) |
334334
| `deleteBehavior` | `'restrict' \| 'cascade'` | `'cascade'` | Behavior when parent is deleted. `restrict` refuses the parent delete while children exist; `cascade` (and omitting the key) deletes the children with the parent. An explicit `set_null` is a **parse-time rejection** on this type (#9689) — a detail row cannot outlive its master; use a `lookup` if children must survive the parent |
335335
| `inlineEdit` | `boolean \| 'grid' \| 'form'` | — | Edit child records inline on the parent create/edit form (`true` = auto-pick, `'grid'`, or `'form'`) |
@@ -347,6 +347,10 @@ Parent-child relationship (cascading delete by default).
347347
}
348348
```
349349

350+
#### A relationship never targets a stored-metadata table
351+
352+
A `lookup` or `master_detail` whose `reference` is `sys_metadata` or `sys_metadata_history` is refused by `os validate`, `os build` and the object save door alike. Those rows carry serialized metadata bodies, which are served only through the metadata API (`GET /api/v1/meta/:type/:name`), where the body is projected; the engine never hydrates a reference into them, so `$expand` would keep the stored id and never nest the row, and a master-detail child's `parent` root would stay unbound, refusing every write a `parent` rule judges. To point a record at a metadata item, store its type and name in `text` fields and read the item through the metadata API.
353+
350354
### `tree`
351355
Self-referential hierarchy (e.g., categories, org chart).
352356

‎packages/spec/src/data/field.zod.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1472,10 +1472,23 @@ export const FieldSchema = lazySchema(() => {
14721472
* engine's object registry for a definition that reaches it unparsed, so every
14731473
* reader of the target — the delete walk that honours `deleteBehavior`,
14741474
* `$expand`, the write-time reference check — sees an ordinary self-reference.
1475+
*
1476+
* [#24677] A `lookup` / `master_detail` never targets a stored-metadata-body
1477+
* table (`STORED_METADATA_BODY_OBJECTS`: `sys_metadata`,
1478+
* `sys_metadata_history`). Its rows are served only through the metadata
1479+
* protocol's own door, so the engine never hydrates a reference into one
1480+
* (`$expand` keeps the stored id; a master-detail `parent` root stays
1481+
* unbound). The refusal is the authoring rules', beside the refusal of a
1482+
* formula or validation rule reading through such a field
1483+
* (`@objectstack/lint`'s expression rule), so `os validate`, `os build` and
1484+
* the object save door refuse it alike.
14751485
*/
14761486
reference: z.string().optional().describe(
14771487
'Target object name (snake_case) for lookup/master_detail fields. '
14781488
+ 'Required for relationship types. Used by $expand to resolve foreign key IDs into full objects. '
1489+
+ 'Never a stored-metadata table (`sys_metadata`, `sys_metadata_history`): their rows are served only '
1490+
+ 'through the metadata API, so the engine never hydrates a reference into one, and `os validate`, '
1491+
+ '`os build` and the object save door refuse it. '
14791492
+ 'On a `tree` field it is optional and, if given, must be the declaring object\'s own name — '
14801493
+ 'the object schema refuses any other target, and writes the own name when it is left out.'
14811494
),

0 commit comments

Comments
 (0)