Skip to content

「空谓词」在三处有三种范围:disabled: { dialect: 'cel', source: '' } 仍被判成已声明的门 → 永久置灰(#3842 修完后的残留,需先裁) #3850

Description

@yinlianghui

来源

发现自 #3842 实施时对残留形状的探针核对(#3842 的修法只覆盖 '' 字面量)。修法要动的是 visible 一族共用的那处定义,属于要先裁的契约变更,故独立成单,未认领

机理

「这是个空谓词吗」在仓里被回答了三次,范围各不相同:

定义 位置 判为「空」的范围
hasDeclaredVisibilityGate packages/components/src/renderers/action/visibility-gate.ts:43 null / undefined / ''
toPredicateInput packages/core/src/evaluator/predicateInput.ts:87 null / undefined / '' / source 为空的信封
evaluateCondition packages/core/src/evaluator/ExpressionEvaluator.ts:237,246 上面全部 + 纯空白串(trim() 后为空)

disabled 侧的置灰判定是「门已声明 求值为真」。两个定义范围不等,夹缝里的形状就是「门说已声明、求值说没有条件(→ true)」→ 永久置灰

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

shape                        | 已声明? | toPredicateInput | evaluateCondition | disabled 侧
''                           | false   | undefined        | true              | 可点   ← #3842 已修
'   '(纯空白)               | true    | "${   }"         | false             | 可点   ← 良性,见下
{dialect:'cel', source:''}   | true    | undefined        | true              | 置灰   ← 残留
{source:''}(无 dialect)     | true    | undefined        | true              | 置灰   ← 残留
true                         | true    | true             | true              | 置灰(正确)
false                        | true    | false            | false            | 可点(正确)
undefined                    | false   | undefined        | true              | 可点(正确)

两个结论都是量出来的,不是推的:

为什么信封形状值得管

{ dialect, source } 正是 @objectstack/specExpressionInputSchema任何已授权谓词(含裸串)归一后的产物,也是 objectstack build 编译出的形状。「作者留空 → 编译出 source: '' 的信封」是这条链上最自然的产生路径,比手写 disabled: '' 更可能出现在真实元数据里。'' 字面量反而更像是手写 JSON / 进程内构造才有的拼法。

同一形状在 visible 侧良性(判成已声明 → 求值 true → 显示,与「没有门」同一结果),所以只有 disabled 方向亮 —— 与 #3842 完全同一个不对称。

需要先裁(两条轴)

修法只有一处合理落点(hasDeclaredVisibilityGate 那唯一定义),但它被 visible 一族六处以上落点、三个包共用,改的是契约范围而不是一个调用点,所以不该由实施者顺手定。

选项 A —— 把「空」的范围在定义处对齐(推荐):hasDeclaredVisibilityGate 也把「source 为空的信封」判成「没有门」,即与 toPredicateInput 的范围一致。

选项 B —— 在 producer 侧拒收:让 spec / 发布期校验直接拒绝空 source 的谓词信封,渲染侧不动。

  • 长期健康度:更契约优先 —— 一个求不出值的谓词本就不该被发布,与「declared = enforced」一致。
  • 让 AI 写的元数据难写错:最强 —— 发布期大声拒绝,而不是运行期靠消费者宽容。
  • 代价:落在 @objectstack/spec(framework 仓),跨仓;且对已在库里的历史元数据不追溯,渲染侧仍需要一个兜底姿态,所以大概是 A + B 而不是 B 单独成立。

⛔ 不推荐:在两处 disabled 调用点加一个「顺便判一下空 source」的本地判定 —— 那就是把第四种「空」的拼法写进消费者,#3842 刚刚合掉的正是这类分身。

我的建议:A 先落(收敛范围、visible 侧补等价钉子),B 作为 framework 侧独立单跟进。

影响

命中条件:元数据里出现 source 为空的谓词信封。表现与 #3842 一致 —— 按钮永久置灰,界面上无法与「元数据本意」区分。今天必然可见的回归尚未观察到,严重度请分诊裁。

Related: #3842(同族,'' 字面量半边已修,PR 分支 claude/issue-3842-disabled-declared-gate)、#3848(执行入口的同族半边,那侧纯空白串也被拦)、#3849(其余五处渲染落点)、#3492(不变量出处)、#3074 / #4115 upstream(PredicateInputEvaluatorPredicateInput 的概念分界)。未认领。

Metadata

Metadata

Assignees

No one assigned

    Labels

    pm:queuetarget:v17v17 发布窗口工作集(GA 前排查 2026-08-04)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions