You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 676d44d
Browse filesBrowse the repository at this point in the historyBrowse files
chore(spec): changeset + regenerated artifacts for the field-security pair
`gen:api-surface-declarations` and `gen:docs`, as `check:generated` proved
stale; `authorable-surface/ui.json` is the build's own regeneration. Six
additions in the authorable surface and nothing else — the two keys on the
three record blocks.
The changeset is `minor` and carries `Clause-②: yes (widening)`: the three
keys were refused at parse before this change, so declaring two of them
widens the accept set on a published authoring surface.
Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>
`record:details`, `record:highlights` and `record:related_list` accept `enforceFieldSecurity` and `redactFields` — the two field-security keys objectui's detail renderers have been honouring on documents this contract refused by name (#18159).
6
+
7
+
Clause-②: yes (widening)
8
+
9
+
All three blocks are `strictObject`s that declared neither key, while `@object-ui/plugin-detail` reads both off each of the three. An author who wrote either was refused at publish, and the same document was honoured on the raw-node path — a contract that could not be satisfied by writing it down. Both keys are declared here, optional, with no schema default, so an absent key stays absent rather than becoming "the author asked for off".
10
+
11
+
-**`enforceFieldSecurity`** (boolean) folds the block's field list — the detail body's fields and sections, the highlight chips, the related list's `columns` — through the caller's field-read permissions before rendering, so a field the permission set denies leaves no empty row behind.
12
+
-**`redactFields`** (string array) drops the names it lists outright. On `record:related_list` it also reaches the columns the list derives for itself when none are authored.
13
+
-**The claim is held to what the render path does.** Both are presentation filters, applied in the browser after the record is fetched: the values are in the page either way, so neither is a data-access control and neither is the object's `publicSharing.redactFields`, which removes them server-side. Each `describe()` says that in the text an author reads, rather than leaving the key names to imply it (Prime Directive #10). The gates that do keep a value from a caller are the field's own `requiredPermissions` / `maskingRule` (ADR-0066 D3) and the permission set.
14
+
-**⚠️ On `record:details`, `redactFields` neighbours the already-declared `hideFields`** and on a well-formed field list the two remove the same rows: `hideFields` is the dedupe channel the renderer also writes to (live `record:highlights` registrations, the page-title field), `redactFields` is the author's deliberate omission and the arm that participates in the renderer's fail-closed fold. Converging them is a contract question this change did not open.
15
+
-**⚠️ The third key the same three renderers read — `requiredPermissions` — is deliberately NOT declared**, and stays refused by name on all three. Its read is `perms.can(objectName, name)`, whose second parameter is this package's own closed `PermissionActionSchema` enum, not the ADR-0066 capability set that name means on `action`, `app`, `field` and `bulkAction`. Measured on both shipped permission providers: under the backend-backed one an unmapped name falls through to the object's `allowRead` bit, so a capability the caller does not hold passes for every reader; under the role-based one the same name is denied for everyone whenever the object carries a permission config. Declaring it would mint the ADR-0049 fail-open access gate retired from `app.areas[].requiredPermissions` in 17.0.0. The exit is a ruling, not an omission.
16
+
17
+
⚠️ **Not measured here**: the runtime behaviour of either declared key in a browser, and whether any authored document anywhere writes them. "The schema refused it" is not "nobody writes it"; only the first is measured.
Copy file name to clipboardExpand all lines: content/docs/references/ui/component.mdx
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1091,6 +1091,8 @@ View filter rule
1091
1091
|**hideFields**|`string[]`| optional | Field names to omit from the body — applied to `fields` and to every section's `fields` (used to dedupe fields already shown in `record:highlights` or as the page title) |
1092
1092
|**inlineEdit**|`boolean`| optional | Allow inline field editing in the detail body (renderer default: on, where the object itself is editable — set `false` to force it off). |
1093
1093
|**showHeader**|`boolean`| optional | Render the detail body's own heading (renderer default: off). |
1094
+
|**enforceFieldSecurity**|`boolean`| optional | Fold this block's field list through the caller's FIELD-read permissions before rendering, so a field the permission set denies leaves no empty row behind (renderer default: off). Presentation only: it re-applies the same field-read answer the server already enforced (ADR-0066 D3) and never widens access — with it off a denied field still arrives masked or stripped, and with it on the server still decides every value. |
1095
+
|**redactFields**|`string[]`| optional | Field names this block never renders, whatever the permission answer (renderer default: render everything authored). Presentation only, evaluated in the browser after the record is fetched — the values are still in the page, so this is NOT a data-access control and NOT the object's `publicSharing.redactFields`, which removes them server-side. To keep a value from the caller, gate the field itself (`requiredPermissions` / `maskingRule`, ADR-0066 D3) or the permission set. Neighbours `hideFields`, which is the dedupe channel the renderer also writes to. |
|**fields**|`(string \| { name: string; label?: string; type?: string; readonly?: boolean })[]`| ✅ | Key fields to highlight (1-7 fields max, typically displayed as prominent cards). Each item may be a bare field name or `{name, label?, type?, readonly?}` for inline overrides. |
|**enforceFieldSecurity**|`boolean`| optional | Fold this block's highlight chips through the caller's FIELD-read permissions before rendering, so a field the permission set denies leaves no empty chip behind (renderer default: off). Presentation only: it re-applies the same field-read answer the server already enforced (ADR-0066 D3) and never widens access — the record is fetched either way and the server still decides every value. |
1165
+
|**redactFields**|`string[]`| optional | Field names this block never renders as a chip, whatever the permission answer (renderer default: render every field authored). Presentation only, evaluated in the browser after the record is fetched — the values are still in the page, so this is NOT a data-access control and NOT the object's `publicSharing.redactFields`, which removes them server-side. To keep a value from the caller, gate the field itself (`requiredPermissions` / `maskingRule`, ADR-0066 D3) or the permission set. |
|**enforceFieldSecurity**|`boolean`| optional | Fold this list's `columns` through the caller's FIELD-read permissions on the RELATED object before rendering (renderer default: off). Presentation only: it re-applies the same field-read answer the server already enforced (ADR-0066 D3) and never widens access — the rows are fetched either way and the server still decides every value. |
1287
+
|**redactFields**|`string[]`| optional | Field names this list never renders, whatever the permission answer (renderer default: render every column authored or derived). Applies to the authored `columns` AND to the columns the list derives for itself when none are authored. Presentation only, evaluated in the browser after the rows are fetched — the values are still in the page, so this is NOT a data-access control and NOT the object's `publicSharing.redactFields`, which removes them server-side. To keep a value from the caller, gate the field itself (`requiredPermissions` / `maskingRule`, ADR-0066 D3) or the permission set. |
0 commit comments