From c987fa028990288e20d426103b40c1b870fc5832 Mon Sep 17 00:00:00 2001 From: os-zhuang Date: Mon, 13 Jul 2026 11:29:03 +0800 Subject: [PATCH] feat(spec,platform-objects): action param 'visible' predicate; gate create-user phone on features.phoneNumber MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ActionParamSchema gains an optional 'visible' (ExpressionInputSchema / CEL), evaluated against the same scope as action 'visible'. The sys_user create_user action's phoneNumber param is gated on features.phoneNumber == true, so the form only offers a phone field when the opt-in phoneNumber auth plugin is loaded — otherwise admin/create-user rejects the phone. Pairs with objectui's ActionParamDialog honoring param.visible (objectstack-ai/objectui#). Co-Authored-By: Claude Opus 4.8 --- .changeset/action-param-visible.md | 8 ++++++++ .../platform-objects/src/identity/sys-user.object.ts | 5 +++++ packages/spec/src/ui/action.zod.ts | 9 +++++++++ 3 files changed, 22 insertions(+) create mode 100644 .changeset/action-param-visible.md diff --git a/.changeset/action-param-visible.md b/.changeset/action-param-visible.md new file mode 100644 index 0000000000..b93f00fbce --- /dev/null +++ b/.changeset/action-param-visible.md @@ -0,0 +1,8 @@ +--- +'@objectstack/spec': patch +'@objectstack/platform-objects': patch +--- + +**Action params gain a `visible` predicate; the create-user `phoneNumber` param is gated on `features.phoneNumber`.** + +`ActionParamSchema` gains an optional `visible` (CEL, `ExpressionInputSchema`) evaluated against the same scope as action `visible` (`current_user`/`app`/`data`/`features`); a UI that honors it omits the param when it's false. The `sys_user` `create_user` action's `phoneNumber` param now carries `visible: 'features.phoneNumber == true'`, so the form no longer offers a Phone Number field when the opt-in `phoneNumber` auth plugin is off — otherwise the endpoint rejects it with "Phone numbers require the phoneNumber auth plugin". Pairs with the objectui `ActionParamDialog` change that evaluates `param.visible`. diff --git a/packages/platform-objects/src/identity/sys-user.object.ts b/packages/platform-objects/src/identity/sys-user.object.ts index 51c13c1874..1cd6da47b2 100644 --- a/packages/platform-objects/src/identity/sys-user.object.ts +++ b/packages/platform-objects/src/identity/sys-user.object.ts @@ -153,6 +153,11 @@ export const SysUser = ObjectSchema.create({ type: 'text', required: false, helpText: 'Sign-in phone number (E.164, e.g. +8613800000000). Required when no email is given.', + // Only offer phone when the opt-in phoneNumber auth plugin is loaded — + // otherwise the create-user endpoint rejects a phone with + // "Phone numbers require the phoneNumber auth plugin". `features.phoneNumber` + // is served in /api/v1/auth/config (getPublicConfig). + visible: 'features.phoneNumber == true', }, { field: 'name', required: false }, { diff --git a/packages/spec/src/ui/action.zod.ts b/packages/spec/src/ui/action.zod.ts index feed011722..611750264a 100644 --- a/packages/spec/src/ui/action.zod.ts +++ b/packages/spec/src/ui/action.zod.ts @@ -66,6 +66,15 @@ export const ActionParamSchema = lazySchema(() => z.object({ * context. Useful for edit dialogs that pre-fill from the selected row. */ defaultFromRow: z.boolean().optional(), + /** + * Visibility predicate (CEL) — same scope as the action-level `visible` + * (`current_user` / `app` / `data` / `features`). When it evaluates false the + * dialog omits this param entirely. Use it to hide a param that the backend + * only accepts under an opt-in capability, e.g. the create-user `phoneNumber` + * param gated on `features.phoneNumber` so the form never offers a field the + * default backend rejects. Absent = always visible. + */ + visible: ExpressionInputSchema.optional().describe('Param visibility predicate (CEL); omits the param when false.'), }).refine( (p) => Boolean(p.name) || Boolean(p.field), { message: 'ActionParam requires either "name" or "field"' },