Skip to content

feat(spec): action param 的 options[] 声明逐选项 visibleWhen (#5016) - #6235

Merged
qq9340100 merged 2 commits into
mainfrom
claude/issue-5016-action-param-options-vocab
Aug 7, 2026
Merged

feat(spec): action param 的 options[] 声明逐选项 visibleWhen (#5016)#6235
qq9340100 merged 2 commits into
mainfrom
claude/issue-5016-action-param-options-vocab

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes #5016

执行 2026-08-06 维护者裁 B(2026-08-07 窗口改判进 v17)。前置的逐键 liveness 审计改变了落点:裁决把审计设为前置条件,审计跑完的结论是「不是整套复用 SelectOptionSchema,而是只开 visibleWhen 一个键」。下面第 1 节是审计证据,第 2 节是与硬约束有关的一处前提证伪,两处都请复核。


1. 逐键 liveness 审计(objectui 只读,未改一行 objectui 代码)

问的不是「这个键在 objectui 存不存在」,而是「一个 action param 的选项列表能不能走到它的读者」。对话框只拿选项列表建一个输入控件,提交完就把列表丢掉 —— 它没有「已存值展示」这个 surface。

声明在 从 action param 选项可达的读者 证据 处置
visibleWhen SelectOptionSchema SelectField / MultiSelectField / RadioField / CheckboxesField 四个控件均调 useCascadingOptionscore/src/evaluator/optionRules.ts:101 o?.visibleWhenFieldRulePredicate = string | { dialect?, source }fieldRules.ts:33)正好收 ExpressionInputSchema 的信封 开放
color SelectOptionSchema 读者只有已存值的展示渲染器:fields/src/index.tsx:1118,1133SelectCellRenderer(网格/详情徽章)、metadata-viewer.tsx:152 的状态机视图。四个输入控件无一读它 —— MultiSelectField.tsx:9 在本地 Option 上声明了 color? 却从不渲染 继续拒绝
default SelectOptionSchema ,且是层级写错 唯一读者 metadata-viewer.tsx:153 options.find((o) = o.default),读的是对象字段的选项。对话框参数的默认值走参数自己的 defaultValue,高一层 继续拒绝
icon 仓里任何 spec 形状都没有 objectui 全仓 icon 命中只有 WidgetRegistry.ts:173manifest.iconschema-builder.ts:348,均无关 不升级 C
disabled 同上 四个选项控件里每一个 disabled 都是字段级 props.disabledSelectField.tsx:140MultiSelectField.tsx:125RadioField.tsx:92CheckboxesField.tsx:126),没有逐选项的 不升级 C

所以 icon / disabled 按裁决「证据不足则不升」→ 未升级 C;default 按派单「实测无消费方则不进 extend 面」→ 未纳入。color 的审计结论与派单的预设不同:派单据 issue 正文认为它活("packages/fields 的 option?.color"),但那个读者是对象字段已存值的展示渲染器,action param 的选项列表到不了它。按同一条「不声明惰性键」的理由一并排除 —— 这是本 PR 唯一超出派单明文授权的判断,请复核(见文末 open question)。

实现上没有用 .extend()

裁决写的是 SelectOptionSchema.extend()。实测这条路会丢掉整套错误面strictObject 的 error map 闭包在基座{ surface, history, aliases, guidance } 上,knownKeys 也从基座的 shape 读(shared/strict-object.ts),而 .extend() 产生的 clone 不带新的 declaration(该文件 docblock 自述「a marker on the instance does not survive the clone .extend() make」)。照搬会把批 14 那套 icon / disabled / default 的指路文案、以及 optionValue / optionLabel / displayName 三条 alias 全部替换成 SelectOptionSchema 的表,surface 名也会变成 "this select option"。因此保留本 surface 自己的 strictObject,只把 visibleWhen定义(同一个 ExpressionInputSchema)接进来 —— 一套词汇的目标达成,错误面不倒退。

顺带两处必须同步:

  • 删掉 visibleWhenguidance 条目 —— guidance 只从 unrecognized_keys 这条路被查,声明键永远到不了,留着就是 shared/alias-integrity.test.ts 判定的死条目。
  • 接过 visible / showWhen 两条 alias —— 目标键现在这个 shape 接受了,符合 finding 12「never suggest a key the schema cannot accept」。

2. 前提证伪:硬约束的「同一个 PR」在这条路上不成立

裁决的硬约束是「必须与 resolveActionParamsnormaliseOptions 停止重建条目同一个 PR落地,否则会造出『声明了、门过了、渲染器收不到』的键」。实测两件事:

(a) normaliseOptions 不在本仓。 它只存在于 objectui packages/app-shell/src/utils/resolveActionParams.ts:228;本仓 packages/spec/src/ui/action.zod.ts 的两处命中是注释引用。一个 PR 跨不了两个仓库。

(b) 更重要的是,本次开放的这条路根本不经过它。 resolveActionParamnormaliseOptions 只作用在 param.options ?? normaliseOptions(field.options, …)半 —— 即从字段继承的列表。作者显式写在 param 上的 options内联分支options: param.options 逐字下沉,随后 ActionParamDialog.tsx:207 逐条 { ...o, label: pickLocalized(...) }(spread,保留额外键)、paramToField.ts options: param.options(原样)。所以 spec 一放开,作者写的 visibleWhen 今天就到得了渲染器,零 objectui 改动

硬约束想防的危害在本次落点上不会发生。normaliseOptions 的丢弃是真的,但它丢的是字段自己早已声明的逐选项词汇(今天 main 上每一个 field-backed select 参数都在丢),既早于本次改动也不受其影响 —— 那是一条独立的 objectui 缺陷,已另行立单。本 PR 的拒绝文案因此仍然刻意开「把参数改成 field-backed 去继承」这张药方。


3. 验证

  • 反向验证(方向事先声明为「双通道红」,实测吻合):把 visibleWhen 从 shape 上摘掉后 —— (i) 三条端到端用例转红,报 unrecognized_keys: ["visibleWhen"],因为 shape 是 strict,是「响亮拒绝」而非「静默剥离」);(ii) shared/alias-integrity.test.ts 的 "every alias target is a key the schema really accepts" 同时转红,2 条 —— 新接的两条 alias 指向了 shape 不接受的键(finding 12 通道)。恢复后全绿。
  • 端到端断言走真实入口:7 条新用例全部经 getMetadataTypeSchema('action')MetadataManager.validate / GET /api/v1/meta / Studio 表单用的那道门)与 ObjectSchema.actions[],且断言键在 parse 输出里活着到达{ dialect: 'cel', source: … }),不是只断言 success —— 只断言 success 在批 14 之前那个静默剥离的世界里同样会绿。
  • pnpm --filter @objectstack/spec test330 files / 8426 tests 全绿(含新增 7 条)。
  • pnpm --filter @objectstack/runtime test105 files / 1506 tests 全绿typecheck 绿(先 --filter '@objectstack/runtime^...' build 起依赖)。
  • check:generated 10/10 绿content/docs/references/ui/action.mdxgen:docs 重生成,未手改;authorable-surface / json-schema 零变化 —— 该产物记的是顶层 authorable 属性,不含逐选项键)。
  • 「不代跑」6 源审计整组绿:check:liveness / check:empty-state / check:skill-examples / check:variant-docs / check:exported-any / check:dual-source-exports
  • pnpm lint 绿;check:nul-bytes(5921 tracked,另做 grep -naP 自扫)/ check:doc-authoring / check:adr-anchors / check:spec-parsed-alias / check:docs-audit-scope / check:release-notes 全绿。

4. 边界

⛔ 未动 objectui 任何代码(只读审计)。⛔ bulk-action.zod.ts.passthrough() 原样保留 —— #4909 的两条理由在本条路上都不成立,正文已辨析。⛔ 未碰 content/docs/releases/。changeset 定 @objectstack/spec: major(随 v17 列车),FROM→TO 写明了行为激活面:16.x 里作者写的 visibleWhen 被静默剥掉、选项永远可选,17.0.0 起键保留并生效、选项集会变窄

5. 与在飞单的关系

Open question(需维护者一句话确认)

color 是否要一并开放?裁决说的是「复用 SelectOptionSchema」,审计测出它在这条路上无读者,我按「不声明惰性键」排除了。方向不对称:现在补开是加性的、一行的;先开了再收窄是破坏性的。所以本 PR 取可回退的那一侧,等一句确认。


Generated by Claude Code

批 14 把 ActionParamSchema.options[] 关成 { label, value } 并把能力问题
留给 #5016。逐键量过消费面后,只开 visibleWhen 一个键 —— 它是唯一一个
在 action param 这条路上真有读者的:内联参数的 options 逐字下沉
(resolveActionParam 内联分支 → ActionParamDialog → paramToField),四个
选项控件都经 useCascadingOptions → resolveCascadingOptions 按它过滤,
且 evalFieldPredicate 接受 ExpressionInputSchema 产出的 { dialect, source }
信封。此前挡在作者和这个能工作的门控之间的,只有 spec 这道门。

color / default 继续拒绝:前者只被"已存值"的展示渲染器读(网格单元格 /
详情徽章),对话框只拿列表建输入控件;后者是层级写错,参数的默认值走
高一层的 defaultValue。icon / disabled 也未升级进 SelectOptionSchema
(#5016 的 C 选项)—— 重测确认 objectui 无任何读者,四个控件里的
disabled 全是字段级 props.disabled。四个键各自保留指路的 guidance。

visibleWhen 的 guidance 条目必须移除(声明键到不了 unrecognized_keys
这条路,留着就是 alias-integrity 判定的死条目),并把 SelectOptionSchema
的两个拼法 visible / showWhen 作为 alias 接过来。

新测试全部走真实的门(getMetadataTypeSchema('action') 与
ObjectSchema.actions[]),并断言键在 parse 输出里"活着到达",而不只是
parse 成功 —— 只断言 success 在批 14 之前那个静默剥离的世界里同样会绿。

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 7, 2026 11:57am

Request Review

@github-actions github-actions Bot added the size/m label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/dogfood, @objectstack/spec.

113 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via packages/qa/dogfood, @objectstack/spec)
  • content/docs/permissions/delegated-administration.mdx (via packages/qa/dogfood)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…5016)

expression-conformance 的 ratchet 会重新扫描 packages/spec/src 下每个
ExpressionInputSchema 声明,新增的 ui/action.zod.ts:visibleWhen 没有归类,
CI 的 Dogfood Regression Gate (3/3) 因此报 "UNCLASSIFIED surface"。本仓
scoped 的 spec / runtime 测试看不到这一条 —— 它只在 dogfood 分片里跑。

单开一行而不是并进 cel-ui:cel-ui 那批是 SchemaRenderer 藏元素,这一条是
在字段控件内部收窄一个选项列表,evaluator 不同。tier 取 fail-soft-log,
与 cel-field-rule 一致 —— evalFieldPredicate 的 fallback 是 true,谓词坏掉
时选项保持可选,而不是无声删掉一个作者没打算拿走的选择。

Co-Authored-By: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

第二个提交(5dce674):CI 抓到一条本地 scoped 测试看不见的门。

首轮 CI 的 Dogfood Regression Gate (3/3) 红,是我的:

+ "UNCLASSIFIED surface — add a ledger row (ADR-0060): ui/action.zod.ts:visibleWhen"
❯ test/expression-conformance.test.ts:56:43

packages/qa/dogfood/test/expression-conformance.test.ts重新扫描 packages/spec/src 下每一个 ExpressionInputSchema 声明(正则 ^\s*(\w+)\s*:\s*ExpressionInputSchema),要求每条都被 ADR-0058 D7 账本恰好一行 covers。新增一个表达式面而不归类,就是它存在的理由(#1887 那类「声明了却没接线的谓词」)。这条门只在 dogfood 分片里跑 —— --filter @objectstack/spec test / --filter @objectstack/runtime test 都看不到它,本地全绿并不代表它绿。

修法是给 expression-conformance.ledger.ts 加一行 cel-action-param-option-visible

  • 单开一行,不并进 cel-ui —— cel-ui 那批是 SchemaRenderer 把一个元素藏掉,这一条是在字段控件内部收窄一个选项列表,evaluator 不是同一个(resolveCascadingOptions)。账本的每行都要求 enforcement 写清求值链路,混在一起就写不准。
  • tier 取 fail-soft-log,与 cel-field-rule 一致 —— evalFieldPredicate 在这条路上的 fallbacktrue,谓词坏掉时选项保持可选,而不是无声删掉一个作者没打算拿走的选择。取 fail-closed 会把账本写成一句假话。
  • 行里同时记下了这句边界:enforceActionParams(ADR-0104 D2)只按声明的选项校验提交,不求值这个谓词,所以访问控制必须由 action body / 权限再拒一次。

复推后 24 个 check 全部完成:Console Pin Gate skipped,其余全绿 —— 含 ESLint(家族门都在这个 job 里)、TypeScript Type CheckDogfood Regression Gate 三个分片、Test Core 三个分片、Check ChangesetSpec property liveness


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

action param 的 options[] 该不该讲字段级的逐选项词汇(color / visibleWhen)? —— 三处形状,三种拼法

1 participant