Skip to content

Commit a193351

Browse files
committed
docs(protocol): document requiresFeature capability gates on actions/params (#2874)
Also lists the param-level visible predicate (added in #2871) in the ActionParam field summary, which had drifted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HGofzwNXvtahMGk8TQqXdx
1 parent e51cdb7 commit a193351

1 file changed

Lines changed: 23 additions & 1 deletion

File tree

content/docs/protocol/objectui/actions.mdx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ interface Action {
189189
190190
// Access
191191
visible?: ExpressionInput; // Visibility predicate (CEL)
192+
requiresFeature?: string; // Public auth feature flag gate — lowered into `visible` at parse time
192193
disabled?: boolean | ExpressionInput; // Disabled when TRUE (CEL)
193194

194195
// Bulk
@@ -291,6 +292,27 @@ Prefer the `record.<field>` form: it reads unambiguously and resolves identicall
291292

292293
This is a narrower scope than [record-alert](/docs/protocol/objectui/record-alert) conditions (which also expose `os.user` / `os.org` / `os.env`) — action predicates see the record only.
293294

295+
#### Capability gates: `requiresFeature`
296+
297+
When an action (or param) only works with an opt-in auth capability behind it — the better-auth `admin` plugin, `phoneNumber`, `twoFactor`, … — gate it with **`requiresFeature: '<flag>'`** instead of a hand-written `features.*` predicate. The flag names the public feature flag served at `/api/v1/auth/config` (see `PUBLIC_AUTH_FEATURES` in `@objectstack/spec/kernel`); at parse time the schema lowers it into the canonical `visible` predicate — `features.X == true` for opt-in flags, `features.X != false` for default-on flags — AND-composing with any explicit `visible` you also declare, then strips itself from the output. An unknown flag name fails the parse.
298+
299+
```yaml
300+
# Hidden entirely when the admin plugin is off (instead of a button that 404s)
301+
name: ban_user
302+
label: Ban User
303+
type: api
304+
target: /api/v1/auth/admin/ban-user
305+
requiresFeature: admin
306+
307+
# Composes with a residual row predicate:
308+
# (record.role != 'owner') && features.organization != false
309+
name: transfer_ownership
310+
visible: "record.role != 'owner'"
311+
requiresFeature: organization
312+
```
313+
314+
This is how the platform keeps "form follows plugin" (#2874): the UI never advertises an input the default backend would reject.
315+
294316
## Confirmation & Feedback
295317

296318
Actions express confirmation and feedback through dedicated string fields — there is no nested confirm-dialog object with `requireTyping`/`confirmLabel`.
@@ -375,7 +397,7 @@ params:
375397
- { label: High, value: high }
376398
```
377399

378-
`ActionParam` fields: `name`, `field`, `objectOverride`, `label`, `type`, `required` (default `false`), `options`, `placeholder`, `helpText`, `defaultValue`, `defaultFromRow`.
400+
`ActionParam` fields: `name`, `field`, `objectOverride`, `label`, `type`, `required` (default `false`), `options`, `placeholder`, `helpText`, `defaultValue`, `defaultFromRow`, `visible` (CEL predicate — the dialog omits the param when false), `requiresFeature` (capability gate, lowered into `visible` — see [Capability gates](#capability-gates-requiresfeature)).
379401

380402
## AI Tool Exposure (ADR-0011)
381403

0 commit comments

Comments
 (0)