Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .changeset/29460-reference-name-visible-served.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@objectstack/objectql': patch
'@objectstack/spec': patch
---

feat(objectql): a `referenceNameVisible` lookup or user field names the record it points at on `$expand` when the reader may not read that record

Clause-②: yes (widening)
Metadata-protocol: none

`FieldSchema.referenceNameVisible` was declared ahead of its runtime. It is now served on the expanded read:

- **What a refused reader gets.** On `$expand` of a `lookup` or `user` field that declares `referenceNameVisible: true`, a target the reader is refused (the target object's read gate, its required permissions or its row scope) answers `{ id, name, readRefused: true }` in place of the bare id it answered before. `id` is the id the record holds; `name` is the target's display name as ADR-0079 resolves it (`nameField`, then `displayNameField`, then the derived title, with the floor `Record #` and the id). `readRefused: true` tells a renderer to show the name as text with no link. A `multiple: true` field is answered per element.
- **Nothing else of the target.** The name is read by the engine from exactly the target's id and its name field. The `$expand` request's projection, nested expand, filter and order are never applied to that read, and the field's `displayField` does not change the column it reads.
- **Field-level rules on the name field still hold.** When the reader's field-level security keeps the name field from it, the field declares a `maskingRule`, or the name is a formula reading through another reference, the name served is the `Record #` floor with the id.
- **Unchanged.** A target the reader can read is expanded exactly as before. An id no record has, a record of another organization, and every field without the key keep the bare id. An unexpanded read still answers the stored id for every reader. The same key covers `sys_user` names through a `user` field or a lookup to `sys_user`.

The key's `.describe()` drops its planned marker and the liveness ledger records it as live.
2 changes: 1 addition & 1 deletion content/docs/references/data/field.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const result = CurrencyConfigSchema.parse(data);
| **reference** | `string` | optional | Target object name (snake_case) for lookup/master_detail fields. Required for relationship types. Used by $expand to resolve foreign key IDs into full objects. Never a stored-metadata table (`sys_metadata`, `sys_metadata_history`): their rows are served only through the metadata API, so the engine never hydrates a reference into one, and `os validate`, `os build` and the object save door refuse it. On a `tree` field it is optional and, if given, must be the declaring object's own name — the object schema refuses any other target, and writes the own name when it is left out. |
| **referenceVia** | `string` | optional | Declares this text field as the id half of a polymorphic pointer pair (ADR-0052 §5 ActivityPointer): the value is a record id of the object named by the SIBLING FIELD this key names — e.g. `record_id` with `referenceVia: 'object_name'`. The sibling must be a declared field on the same object holding an object machine name. Text fields only; mutually exclusive with `reference` (a static and a per-record target contradict). Enforced today at seed load: the value resolves as a natural key against the object the sibling column names, and an unresolvable pointer is refused loudly instead of stored verbatim. Adds no referential integrity or $expand behavior. |
| **referenceDeletedWhen** | `{ field: string; value: string }` | optional | Marks the record a `referenceVia` pointer names as PLATFORM-DELETED on rows where a sibling `select` field holds one declared option — e.g. `record_id` with `{ field: 'cancel_reason', value: 'record_deleted' }`. A renderer then draws a tombstone from that option's own label, reads nothing and links nothing. Valid only beside `referenceVia`; `field` must be a single-value `select` on the same object declaring inline `options`, and `value` one of its option values — any other shape is refused at parse. |
| **referenceNameVisible** | `boolean` | optional | [PLANNED — not yet served] Declared name exposure (`lookup` and `user` fields only). TRUE declares that a principal who can read the record carrying this field may see the referenced record's display name (the target object's `nameField` value) and id even when that principal cannot read the referenced record. Nothing else of the target: `$expand` on this field returns the same name and id only, the field's `displayField` does not widen it, and the name renders as text with no link to the target. Absent or false keeps the restricted-reference posture for a target the reader cannot read. Declared on the referencing field only; there is no target-object switch. Refused on any other type. Declared ahead of its producer (ADR-0049): no runtime serves the name yet, so setting it changes no read today. |
| **referenceNameVisible** | `boolean` | optional | Declared name exposure (`lookup` and `user` fields only). TRUE declares that a principal who can read the record carrying this field may see the referenced record's display name (the target object's `nameField` value) and id even when that principal cannot read the referenced record. Served on `$expand`: a target the reader is refused (the object's read gate or its row scope) answers `{ id, name, readRefused: true }`, per element on a multi-value field; an unexpanded read answers the stored id, as for every reader. Nothing else of the target: `$expand` on this field returns the same name and id only, the field's `displayField` does not widen it, and the name renders as text with no link to the target. A field-level rule on the name field still holds: when the reader may not read that field, it is masked, or it is a formula reading through another reference, the name is the `Record #` floor with the id. A principal who can read the target is served exactly what it is served without the key. Absent or false keeps the restricted-reference posture for a target the reader cannot read. Declared on the referencing field only; there is no target-object switch. Refused on any other type. |
| **mailAssociation** | `boolean` | optional | Inbound mail association (`email` fields only): TRUE marks this field as identifying a correspondent. When a mailbox service ingests a message, it associates the message with every record of this object whose value in this field equals one of the message's participant addresses (From, Sender, Reply-To, To and Cc, plus those of a forwarded message it carries; ASCII case-insensitive; the receiving mailbox's own addresses excluded; only within the mailbox's organization). A message no record matches waits in the mailbox's unassociated pool. Refused on any other type. Takes effect only where a mailbox service is composed. |
| **deleteBehavior** | `Enum<'set_null' \| 'cascade' \| 'restrict'>` | optional (default: `"set_null"`) | What happens if referenced record is deleted |
| **inlineEdit** | `boolean \| Enum<'grid' \| 'form'>` | optional | Edit these child records inline within the parent's form (atomic master-detail). true = auto-pick grid/form by child shape; 'grid' = editable line-item grid; 'form' = list + per-row full form. |
Expand Down
4 changes: 2 additions & 2 deletions content/docs/references/data/object.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ const result = ApiMethod.parse(data);
| **reference** | `string` | optional | Target object name (snake_case) for lookup/master_detail fields. Required for relationship types. Used by $expand to resolve foreign key IDs into full objects. Never a stored-metadata table (`sys_metadata`, `sys_metadata_history`): their rows are served only through the metadata API, so the engine never hydrates a reference into one, and `os validate`, `os build` and the object save door refuse it. On a `tree` field it is optional and, if given, must be the declaring object's own name — the object schema refuses any other target, and writes the own name when it is left out. |
| **referenceVia** | `string` | optional | Declares this text field as the id half of a polymorphic pointer pair (ADR-0052 §5 ActivityPointer): the value is a record id of the object named by the SIBLING FIELD this key names — e.g. `record_id` with `referenceVia: 'object_name'`. The sibling must be a declared field on the same object holding an object machine name. Text fields only; mutually exclusive with `reference` (a static and a per-record target contradict). Enforced today at seed load: the value resolves as a natural key against the object the sibling column names, and an unresolvable pointer is refused loudly instead of stored verbatim. Adds no referential integrity or $expand behavior. |
| **referenceDeletedWhen** | `{ field: string; value: string }` | optional | Marks the record a `referenceVia` pointer names as PLATFORM-DELETED on rows where a sibling `select` field holds one declared option — e.g. `record_id` with `{ field: 'cancel_reason', value: 'record_deleted' }`. A renderer then draws a tombstone from that option's own label, reads nothing and links nothing. Valid only beside `referenceVia`; `field` must be a single-value `select` on the same object declaring inline `options`, and `value` one of its option values — any other shape is refused at parse. |
| **referenceNameVisible** | `boolean` | optional | [PLANNED — not yet served] Declared name exposure (`lookup` and `user` fields only). TRUE declares that a principal who can read the record carrying this field may see the referenced record's display name (the target object's `nameField` value) and id even when that principal cannot read the referenced record. Nothing else of the target: `$expand` on this field returns the same name and id only, the field's `displayField` does not widen it, and the name renders as text with no link to the target. Absent or false keeps the restricted-reference posture for a target the reader cannot read. Declared on the referencing field only; there is no target-object switch. Refused on any other type. Declared ahead of its producer (ADR-0049): no runtime serves the name yet, so setting it changes no read today. |
| **referenceNameVisible** | `boolean` | optional | Declared name exposure (`lookup` and `user` fields only). TRUE declares that a principal who can read the record carrying this field may see the referenced record's display name (the target object's `nameField` value) and id even when that principal cannot read the referenced record. Served on `$expand`: a target the reader is refused (the object's read gate or its row scope) answers `{ id, name, readRefused: true }`, per element on a multi-value field; an unexpanded read answers the stored id, as for every reader. Nothing else of the target: `$expand` on this field returns the same name and id only, the field's `displayField` does not widen it, and the name renders as text with no link to the target. A field-level rule on the name field still holds: when the reader may not read that field, it is masked, or it is a formula reading through another reference, the name is the `Record #` floor with the id. A principal who can read the target is served exactly what it is served without the key. Absent or false keeps the restricted-reference posture for a target the reader cannot read. Declared on the referencing field only; there is no target-object switch. Refused on any other type. |
| **mailAssociation** | `boolean` | optional | Inbound mail association (`email` fields only): TRUE marks this field as identifying a correspondent. When a mailbox service ingests a message, it associates the message with every record of this object whose value in this field equals one of the message's participant addresses (From, Sender, Reply-To, To and Cc, plus those of a forwarded message it carries; ASCII case-insensitive; the receiving mailbox's own addresses excluded; only within the mailbox's organization). A message no record matches waits in the mailbox's unassociated pool. Refused on any other type. Takes effect only where a mailbox service is composed. |
| **deleteBehavior** | `Enum<'set_null' \| 'cascade' \| 'restrict'>` | optional (default: `"set_null"`) | What happens if referenced record is deleted |
| **inlineEdit** | `boolean \| Enum<'grid' \| 'form'>` | optional | Edit these child records inline within the parent's form (atomic master-detail). true = auto-pick grid/form by child shape; 'grid' = editable line-item grid; 'form' = list + per-row full form. |
Expand Down Expand Up @@ -618,7 +618,7 @@ const result = ApiMethod.parse(data);
| **reference** | `string` | optional | Target object name (snake_case) for lookup/master_detail fields. Required for relationship types. Used by $expand to resolve foreign key IDs into full objects. Never a stored-metadata table (`sys_metadata`, `sys_metadata_history`): their rows are served only through the metadata API, so the engine never hydrates a reference into one, and `os validate`, `os build` and the object save door refuse it. On a `tree` field it is optional and, if given, must be the declaring object's own name — the object schema refuses any other target, and writes the own name when it is left out. |
| **referenceVia** | `string` | optional | Declares this text field as the id half of a polymorphic pointer pair (ADR-0052 §5 ActivityPointer): the value is a record id of the object named by the SIBLING FIELD this key names — e.g. `record_id` with `referenceVia: 'object_name'`. The sibling must be a declared field on the same object holding an object machine name. Text fields only; mutually exclusive with `reference` (a static and a per-record target contradict). Enforced today at seed load: the value resolves as a natural key against the object the sibling column names, and an unresolvable pointer is refused loudly instead of stored verbatim. Adds no referential integrity or $expand behavior. |
| **referenceDeletedWhen** | `{ field: string; value: string }` | optional | Marks the record a `referenceVia` pointer names as PLATFORM-DELETED on rows where a sibling `select` field holds one declared option — e.g. `record_id` with `{ field: 'cancel_reason', value: 'record_deleted' }`. A renderer then draws a tombstone from that option's own label, reads nothing and links nothing. Valid only beside `referenceVia`; `field` must be a single-value `select` on the same object declaring inline `options`, and `value` one of its option values — any other shape is refused at parse. |
| **referenceNameVisible** | `boolean` | optional | [PLANNED — not yet served] Declared name exposure (`lookup` and `user` fields only). TRUE declares that a principal who can read the record carrying this field may see the referenced record's display name (the target object's `nameField` value) and id even when that principal cannot read the referenced record. Nothing else of the target: `$expand` on this field returns the same name and id only, the field's `displayField` does not widen it, and the name renders as text with no link to the target. Absent or false keeps the restricted-reference posture for a target the reader cannot read. Declared on the referencing field only; there is no target-object switch. Refused on any other type. Declared ahead of its producer (ADR-0049): no runtime serves the name yet, so setting it changes no read today. |
| **referenceNameVisible** | `boolean` | optional | Declared name exposure (`lookup` and `user` fields only). TRUE declares that a principal who can read the record carrying this field may see the referenced record's display name (the target object's `nameField` value) and id even when that principal cannot read the referenced record. Served on `$expand`: a target the reader is refused (the object's read gate or its row scope) answers `{ id, name, readRefused: true }`, per element on a multi-value field; an unexpanded read answers the stored id, as for every reader. Nothing else of the target: `$expand` on this field returns the same name and id only, the field's `displayField` does not widen it, and the name renders as text with no link to the target. A field-level rule on the name field still holds: when the reader may not read that field, it is masked, or it is a formula reading through another reference, the name is the `Record #` floor with the id. A principal who can read the target is served exactly what it is served without the key. Absent or false keeps the restricted-reference posture for a target the reader cannot read. Declared on the referencing field only; there is no target-object switch. Refused on any other type. |
| **mailAssociation** | `boolean` | optional | Inbound mail association (`email` fields only): TRUE marks this field as identifying a correspondent. When a mailbox service ingests a message, it associates the message with every record of this object whose value in this field equals one of the message's participant addresses (From, Sender, Reply-To, To and Cc, plus those of a forwarded message it carries; ASCII case-insensitive; the receiving mailbox's own addresses excluded; only within the mailbox's organization). A message no record matches waits in the mailbox's unassociated pool. Refused on any other type. Takes effect only where a mailbox service is composed. |
| **deleteBehavior** | `Enum<'set_null' \| 'cascade' \| 'restrict'>` | optional (default: `"set_null"`) | What happens if referenced record is deleted |
| **inlineEdit** | `boolean \| Enum<'grid' \| 'form'>` | optional | Edit these child records inline within the parent's form (atomic master-detail). true = auto-pick grid/form by child shape; 'grid' = editable line-item grid; 'form' = list + per-row full form. |
Expand Down
Loading
Loading