Repository navigation
ActionRunner.execute 的 disabled 门把「空谓词」当已禁用,拦掉执行(实测 handler 不跑),且与渲染器判定不一致 #3848
Description
Activity
Triage:
pm:queue(in-repo card — sibling repos take nodomain:*).Classification basis: concrete defect with a scripted probe (handler provably never runs; execution returns
Action is disabledfor every empty-predicate shape) at a named location. Premise re-verified onorigin/main@993336f— which is exactly #3842's merge (PR #3851): the gate atpackages/core/src/actions/ActionRunner.ts:666still readsaction.disabled != null && action.disabled !== falsewith no empty-string half and passes the raw value toevaluateCondition, so the renderer/executor disagreement the card describes is now live user-visible behavior (button clickable per #3842, execution still blocked).Direction note for dispatch: the behavioral invariant is already ruled (#3492 declared-gate +
toPredicateInputas the single normalizer, #3842 precedent) — align the execution gate with the renderer verdict. The open sub-question (whether the declared-gate helper moves fromcomponentstocore, dependency direction) is an implementation-placement choice for the implementer/reviewer, not a product ruling; routing the value throughtoPredicateInputalso covers the empty-envelope shape, which keeps this card independent of #3850's definition-scope decision. Preserve the existingcatch { isDisabled = false }fail-open posture.Dedup: repo-scoped search finds only the family itself (#3842 merged two renderer sites; #3849 five remaining renderer sites; #3850 shared-definition scope) — file faces are disjoint (this card is
packages/coreonly). No open PR covers this gate.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
🔒 认领(objectui shard PM,session
session_01GTRjn8xBqp75dk7kFupVRt)- 分支:
claude/issue-3848-actionrunner-disabled-gate - 文件面:仅
packages/core(src/actions/ActionRunner.ts:666执行门 + 钉子测试);渲染器五处已由 disabled 的「已声明」判定还有五处停在 != null(action-icon / action-group ×2 / action-menu / record-quick-actions),#3842 只修了两处 #3849/PR3861 收敛(在闸),本单不碰 components/plugin-detail。 - 方向按分诊:行为不变量已裁(fix(plugin-grid): 批量操作按钮忽略 requiredPermissions,且布尔 visible 被判为故障导致按钮全员隐藏 #3492 declared-gate +
toPredicateInput唯一归一器),执行门与渲染器 verdict 对齐;优先形状 = 先过toPredicateInput归一再判再求值(空串/纯空白/空 envelope 归一后即出局,无需从 components 跨包借hasDeclaredVisibilityGate,本单因此独立于 「空谓词」在三处有三种范围:disabled: { dialect: 'cel', source: '' } 仍被判成已声明的门 → 永久置灰(#3842 修完后的残留,需先裁) #3850 的定义作用域裁决)。若实测该形状复现不了家族语义,停手报告,不做跨包搬迁。 ⚠️ 保留现有catch { isDisabled = false }失败放行姿态。- 与在飞 258 个 t() 调用点引用的 key 在任何语言包里都不存在(#3530 守卫首跑实测),其中 8 处直接把 raw key 渲染给用户 #3546 切片六(i18n)、docs(skills): guide 里反引号包裹的仓内路径无任何门禁校验 —— #3713/#3730 两轮 13+ 处死路径都是靠人肉发现的 #3735(scripts)、pnpm --filter @object-ui/runner test 在干净 main 上 4 红(包级 vitest 落到 runner 的 vite.config.ts、无 jsdom 环境),而仓根同一批测试 13 全绿 #3746(packages/runner)、packages/react-runtime 的 peerDependencies.react 是无上界的
>=18,允许尚不存在的 React 20+ #3741(react-runtime)文件面零相交。
Generated by Claude Code
- 分支:
- added 3 commits that reference this issue
on Aug 10, 2026
来源
发现自 #3842 实施(渲染器侧
disabled的「已声明」判定补!== '')的同族扫面。不在 #3842 的修法面(PM 已裁其范围为DeclaredActionsBar+action:button两处渲染落点),故独立成单,未认领。机理
packages/core/src/actions/ActionRunner.ts:666的执行门:两点都与渲染器侧不同:
!= null && !== false,没有!== ''—— 空谓词进门;evaluateCondition,而不是先过toPredicateInput。evaluateCondition对空/纯空白/空source的语义是「没有条件 →true」,而这里true意味着禁用。于是任何「空谓词」拼法都被判成「已禁用」并拦掉执行。
实测(worktree @
origin/main=aca561a77fd0dc793ea76a7b46cd037e1731eb93,一次性探针,未提交)new ActionRunner({})+registerHandler('probe'),只改disabled一个键:handler 一次没跑 —— 不是「点了没反应」,是执行被门拦下并回了一个
Action is disabled错误。与渲染器侧的分歧(#3314 同一类)
同一个
disabled值,两条路给出相反答案(渲染器侧数据取自同一批探针,PR 落地后):disabled''' '(纯空白)'${ }'求值为 false){dialect:'cel', source:''}false/ 未声明所以 #3842 修完之后的用户可见行为是:
disabled: ''的服务端声明动作按钮能点了,点下去收到Action is disabled报错。比原来「永久置灰、无任何解释」是改善(至少有了消息),但这半边不修,该动作仍然执行不了。渲染器与执行入口对同一谓词不同意,正是 #3314 已经付过一次代价的形状。影响
source)。ActionRunner.execute的动作,不限于某个渲染面 —— 这是 core 的公共执行入口。disabled的「已声明」判定用!= null,disabled: ''把按钮永久置灰(#3492 同族的另一半 predicate,探针实证) #3842 相反的方向也存在:' '在渲染器侧可点、在执行侧被拦,任何用「按钮能点」推断「动作能跑」的人都会被误导。修法建议(未裁)
契约优先:「是否声明了
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首次接线)。未认领。