Skip to content

ActionRunner.execute 的 disabled 门把「空谓词」当已禁用,拦掉执行(实测 handler 不跑),且与渲染器判定不一致 #3848

Description

@yinlianghui

来源

发现自 #3842 实施(渲染器侧 disabled 的「已声明」判定补 !== '')的同族扫面。不在 #3842 的修法面(PM 已裁其范围为 DeclaredActionsBar + action:button 两处渲染落点),故独立成单,未认领

机理

packages/core/src/actions/ActionRunner.ts:666 的执行门:

if (action.disabled != null && action.disabled !== false) {
  let isDisabled = false;
  try { isDisabled = this.evaluator.evaluateCondition(action.disabled as never); } catch { isDisabled = false; }
  if (isDisabled) return { success: false, error: 'Action is disabled' };
}

两点都与渲染器侧不同:

  1. 入门判定是 != null && !== false,没有 !== '' —— 空谓词进门;
  2. 原始值交给 evaluateCondition,而不是先过 toPredicateInputevaluateCondition 对空/纯空白/空 source 的语义是「没有条件 → true」,而这里 true 意味着禁用

于是任何「空谓词」拼法都被判成「已禁用」并拦掉执行。

实测(worktree @ origin/main = aca561a77fd0dc793ea76a7b46cd037e1731eb93,一次性探针,未提交)

new ActionRunner({}) + registerHandler('probe'),只改 disabled 一个键:

disabled ''                       | handler ran: false | result: {"success":false,"error":"Action is disabled"}
disabled '   '                    | handler ran: false | result: {"success":false,"error":"Action is disabled"}
disabled {dialect:'cel',source:''}| handler ran: false | result: {"success":false,"error":"Action is disabled"}
disabled absent                   | handler ran: true  | result: {"success":true}
disabled false                    | handler ran: true  | result: {"success":true}

handler 一次没跑 —— 不是「点了没反应」,是执行被门拦下并回了一个 Action is disabled 错误。

与渲染器侧的分歧(#3314 同一类)

同一个 disabled 值,两条路给出相反答案(渲染器侧数据取自同一批探针,PR 落地后):

disabled 渲染器(修完 #3842 后) ActionRunner.execute
'' 可点
' '(纯空白) 可点('${ }' 求值为 false)
{dialect:'cel', source:''} 置灰(#3842 残留,另单)
false / 未声明 可点 放行

所以 #3842 修完之后的用户可见行为是:disabled: '' 的服务端声明动作按钮能点了,点下去收到 Action is disabled 报错。比原来「永久置灰、无任何解释」是改善(至少有了消息),但这半边不修,该动作仍然执行不了。渲染器与执行入口对同一谓词不同意,正是 #3314 已经付过一次代价的形状。

影响

修法建议(未裁)

契约优先:「是否声明了 disabled 门」这个问题在仓里已有唯一定义(hasDeclaredVisibilityGate,packages/components/src/renderers/action/visibility-gate.ts,!= null && !== ''),而「谓词如何归一」也已有唯一定义(toPredicateInput,packages/core/src/evaluator/predicateInput.ts)。这处执行门两个都没用。

方向:入门判定改问「已声明」,并把值过 toPredicateInput 后再求值,使执行侧与渲染侧逐一同意。注意 hasDeclaredVisibilityGate 目前住在 components,而这里是 core(依赖方向相反)—— 定义大概要下沉到 core(与 toPredicateInput 同层)才谈得上复用,那是跨包动作,请先裁。⚠️ 现有 catch { isDisabled = false }(求值失败不误拦)的姿态应保留。

Related: #3842(渲染器侧同族,PR: claude/issue-3842-disabled-declared-gate)、#3492(不变量出处)、#3314(渲染路径与引擎路径判定分歧的先例)、#1885(disabled 首次接线)。未认领。

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions