fix(core): ActionRunner 的 condition 门改问「有没有声明」,condition: false 真的拦住动作 (#3872) - #3916
Merged
Merged
Conversation
#3872) `ActionRunner.execute` —— 引擎的公共执行入口,所有动作面共用 —— 用 `if (action.condition)` 把门开在**真值**上。真值回答不了门要问的那个问题 (「作者到底声明了条件吗」),而在这个键上它答错的方向是**过度放行**: `condition: false` 落在 `if (false)`,整段被跳过,`evaluateCondition` 根本没被 问过,动作照常执行。基线实测(`origin/main` @ 2937bcf,handler 自计数): condition: false | handler ran: true | {"success":true} condition: {cel,'false'} | handler ran: false | {"error":"Action condition not met"} 同一句话的两种拼法,结论相反 —— 布尔字面量执行了,同义 envelope 被拦。`false` 是元数据能写出的最明确的一句「永远不要执行这个」(把动作关掉的模板产出的正是 它),所以方向要紧:动作真的跑了,可能落库。这是 #3492 家族的过度放行那一半, 下面一行的 `disabled` 门(#3848 / PR #3873)病在相反方向。 门现在先问「有没有声明 condition 门」再求值。「没有可求值的条件」取自 core 唯一 的谓词归一器 `toPredicateInput`(把 `''`、`null`、空 `source` envelope、非谓词值 一律映射为 `undefined`),外加它包裹而非折叠的纯空白字符串 —— 这就是 #3850 对 「空谓词」范围的裁决,与 `disabled` 门已经在用的同一份。门问对了问题,verdict 就 不需要自己的布尔分支:`evaluateCondition` 对布尔实参原样返回。 ## 行为变更面:单向,且只有一行 只有一种形状换了结论 —— 已声明的布尔 `false`,从执行改为拒绝(`{ success: false, error: 'Action condition not met' }`,这个键本来就用的文案)。其余逐字节不变: `condition: true`、缺省、真表达式/真 envelope 照样执行;假表达式、假 CEL envelope、假 `${…}` 模板照样被拦;三种空谓词(`''`、纯空白、空 `source` envelope) 照样执行,只是理由从「`if ('')` 恰好为假」换成了「什么都没声明」;非谓词垃圾 (`0`、`{}`)照样执行 —— 不是谓词的值不该决定动作的命运,这正是本模块在 `disabled` 上已经确立的 fail-open 姿态。因此本改动只可能开始拒绝执行,绝不可能 开始放行,是 #3848 修法的镜像。 `ActionDef.condition` 随之放宽为 `string | boolean`,与门现在兑现的面一致(也与 旁边的 `disabled` 一致)。这不是消费端的宽容别名:布尔本来就通过接口的索引签名 在运行时被接受,只是被忽略了。 交给求值器的值刻意保持**原样**而非先归一,理由与 #3848 相同、符号相反: `toPredicateInput` 无条件包裹,已经是模板的 `'${x}'` 会变成 `'${${x}}'`,解析失败 后原样返回并被强制为恒 `true` —— 在 `disabled` 上这拦住一切,在 `condition` 上它 会**放行**一切。该归一器缺陷是 #3871;新钉子旁的 tripwire 会在它被修好那天转红。 ## 反向验证(方向先写后跑) 把门还原成 `if (action.condition)`、求值不动:预判恰好三个测试转红,且都点名 `false` —— `condition: false` 那一行、布尔/envelope 等价性、以及「已声明的布尔确实 到达求值器」(真值门下它永远到不了)。实跑一致:`Tests 3 failed | 38 passed`, `disabled` 门的既有钉子全绿,纯表格断言与两条 tripwire 全绿。 Co-authored-by: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3872
机理
ActionRunner.execute—— 引擎的公共执行入口,所有动作面共用 —— 把 condition 门开在真值上:真值回答不了门要问的那个问题(「作者到底声明了条件吗」),而在这个键上它答错的方向是过度放行:
condition: false落在if (false),整段被跳过,evaluateCondition根本没被问过,动作照常执行。基线复现(worktree @
origin/main=2937bcf7db0da9d27cd4f330a7033d2b380d2335,handler 自计数,一次性探针未提交)issue 的前提逐行成立:
false与语义完全相同的{ dialect: 'cel', source: 'false' }结论相反 —— 布尔字面量执行了,同义 envelope 被拦。false是元数据能写出的最明确的一句「永远不要执行这个」(把动作关掉的模板产出的正是它),所以方向要紧:动作真的跑了,可能落库。修法
门改问「有没有声明 condition 门」再求值。「没有可求值的条件」取自 core 唯一的谓词归一器
toPredicateInput(把''、null、空sourceenvelope、非谓词值一律映射为undefined),外加它包裹而非折叠的纯空白字符串 —— 即 #3850 对「空谓词」范围的裁决,与disabled门已经在用的同一份。门问对了问题,verdict 就不需要自己的布尔分支:evaluateCondition对布尔实参原样返回(if (typeof condition === 'boolean') return condition),false于是自然短路成「拦」。交给求值器的值刻意保持原样而非先归一,理由与 #3848 / PR #3873 相同、符号相反:
toPredicateInput无条件包裹,已经是模板的'${x}'变成'${${x}}',解析失败后原样返回并被强制为恒true—— 在disabled上这拦住一切,在condition上它会放行一切。该归一器缺陷是 #3871;新钉子旁的 tripwire 会在它被修好那天转红。ActionDef.condition随之放宽为string | boolean,与门现在兑现的面一致(也与旁边的disabled一致)。这不是消费端的宽容别名:布尔本来就通过接口的索引签名在运行时被接受,只是被忽略了。逐形状新旧对照(变化行只有一行,方向 = 收紧)
falseAction condition not met)trueuser.role == "admin"user.role == "guest"{ dialect: 'cel', source: 'true' }{ dialect: 'cel', source: 'false' }${…}模板真${…}模板假''' '纯空白{ dialect: 'cel', source: '' }0{}0这一行与派单预判不同,如实报出:派单预判变化行在false/0/ 空形状,实测只有false变。0在toPredicateInput下归一为undefined(不是谓词)→ 无门 → 放行,与本文件在disabled上已落地的catch { isDisabled = false }fail-open 姿态一致,也与 #3850 的「已声明 = 归一后仍有可求值条件」一致。这同时是本 PR 刻意不照抄ActionEngine.getActionsForLocation模板的一处:那边非谓词分支保留了历史Boolean(raw)强制,visible: 0于是隐藏(对垃圾 fail-CLOSED)。差异已用一条DOCUMENTED DIVERGENCE测试钉住,免得下一个读者以为是漏看。反向验证(方向先写后跑)
预判写在钉子文件头:把门还原成
if (action.condition)、求值不动,应当恰好三个测试转红,且都点名false—— (1)condition: false那一行(handler 又跑了);(2) 布尔/envelope 等价性(false执行而{cel,'false'}被拦,正是本单报的分歧);(3)「已声明的布尔确实到达求值器」(真值门下它永远到不了,spy 收不到false)。纯表格断言(变化行集合、真值 vs 已声明对照)与两条 tripwire 按构造全绿,其余执行行(含0、{}、三种空形状)全绿。实跑一致:
ActionRunner.disabledGate.test.ts在还原态下全绿 —— 本 PR 对disabled门的语义与文案零改动,唯一共享编辑是那个 module-private 判门 helper 改成了中性名(两道门现在问同一个问题,同文件里不该有第二份同问的定义);等 #3850 / #3862 的定义下沉落地后再一并改读 core 的共享定义。验证
pnpm --filter '@object-ui/core^...' build绿。pnpm exec vitest run packages/core/src/actions packages/core/src/evaluator --maxWorkers=2→Test Files 26 passed (26) / Tests 617 passed (617)。Tests 21 passed (21)。pnpm exec turbo run type-check --concurrency=2→78 successful, 78 total。eslint变更两文件:0 error(仅既有no-explicit-anywarning)。condition写成布尔/0("condition": false|true|0零命中),core 之外无消费者读ActionDef.condition,故类型放宽与本变更均无外溢。grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]'三个变更文件零命中。Generated by Claude Code