Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

evalRowPredicate 的 fail-closed 报告丢掉引擎给的失败原因(且把每个面都称作 "list conditional predicate") #3792

Closed

Description

@yinlianghui

观察记录,实施 #3521 时发现,不是本单改动引入的;四个动作面共同的诊断质量问题,没有功能性影响,所以只作 finding 记录、不排期。

现象

packages/core/src/evaluator/listConditional.ts 的 evalRowPredicate 有两条诊断路径,信息量相反:

  • warnOnError 未开(快路):单次求值,由 evalFieldPredicate 自己 warn,消息里带原因 —— … Reason: [runtime] No such key: absent.
  • warnOnError: true(fail-closed 调用方走的路):evalCel 为了把 fault 和真 false 区分开跑两遍,两遍都传 { warn: false },于是原因被丢掉,只剩 warnEvalError 的一句:
[object-ui] A list conditional predicate failed to evaluate (<label>) and was
treated as its safe default: "…". Check the field names and CEL syntax.

也就是说:越是要 fail-closed(按钮直接消失)的场合,诊断越少。行 kebab、批量选择栏、记录页头(#3521 之后)都走 warnOnError: true,所以「按钮为什么不见了」在控制台里恰恰看不到原因 —— 而 No such key: owner_id / no such overload: string == null 这类原因基本一句话就能定位作者的笔误。

另一半:措辞

同一条消息把每个调用方都称作 "list conditional predicate"。这个函数早已不只服务列表面:kanban 卡片条件格式、批量选择栏,#3521 之后还有 page:header。页头上一个隐藏的按钮报出「a list conditional predicate」会让读者去列表视图里找错。

为什么现在没有人踩

结论方向完全正确(fault → fallback),warn-once 也在,受影响的只是排查耗时;而且 label 已经能指到具体动作/规则。所以这是观察级,不是缺陷级。

可能的修法(留给分诊,不预设)

evalCel 已经拿到两次 evalFieldPredicate 的结果,但拿不到 res.error。要带上原因,得让 evalFieldPredicate 有办法把失败原因回传而不是只 console.warn(例如可选的 onFault(reason) 回调,或返回富结果的姊妹函数),再由 warnEvalError 拼进消息;同时把 "list conditional predicate" 改成中性措辞(如 "conditional predicate")。两处都在 core,一处改动即覆盖四个面。

参考:#5149(fail-open 要响不要静默,原因文本就是那一单加的)、#3521(页头接入这条路径的那一单)。

Activity

  1. os-project-manager commented on Aug 9, 2026

    @os-project-manager
    Collaborator

    Triage (maintainer-authorized one-off pass, 2026-08-09, registered on objectstack#6015): promoted finding → pm:queue. Diagnostics-quality S-class, same standard as recent promotions: a fail-closed report that discards the engine's own failure reason and mislabels every surface as "list conditional predicate" makes the safest path the least debuggable one. Carry the reason through and name the calling surface; no behaviour change.


    Generated by Claude Code

  2. self-assigned this
    on Aug 10, 2026
  3. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    ContributorAuthor

    Claim: PM loop round 5 (objectui whole-repo seat, non-v17 queue wave) — packed with #3796 (same file: packages/core/src/evaluator/listConditional.ts; dispatching them separately would serialize anyway).
    Session: session_017Qqyix2QcnpUC9XeYVDzx3
    Branch: claude/issue-3792-3796-listconditional-diagnostics
    Worktree: objectui-issue-3792
    File surface: packages/core/src/evaluator/listConditional.ts + the expression-evaluation seam it needs for fault-reason passback + tests. Serial constraints: disjoint from the other four dispatches of this batch.
    Container & model: M, mode:subagent, model: opus

    Direction (both cards carry their own analysis; neither needs a maintainer ruling):


    Generated by Claude Code

  4. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    ContributorAuthor

    ACCEPT (objectui seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3, review of record) — PR #4104, closing this card and #3796 together.

    #3792 half: onFault(reason) on the existing exported diagnostic bag — the right seam of the two the card sketched (no signature change, no second function whose fault semantics could drift), with three details that show the edge was actually thought through: it fires per fault outside the once-per-predicate warning dedupe, it's invoked OUTSIDE the engine try/catch so a throwing callback surfaces as a caller bug instead of a fake engine fault, and the legacy dialect path — which had the identical reason-loss — was fixed alongside with its own [legacy] tag. Wording neutralized in both messages that carried the "list" misnomer. Fail-closed surfaces now say WHY the button vanished, which was the whole point.

    #3796 half: record pinned after the spread at the merge site — correctly chosen over stripping host scope, because it makes both dialect paths provably identical independent of either engine's precedence; the engine claim was re-measured on-branch before relying on it, and the reverse verification reproduces the card's exact asymmetry (CEL → SCOPE(decoy) / LEGACY → ROW) as the red rather than a generic failure. The rider that a row FIELD named record can no longer shadow the row root is a real hardening, noted.

    Consumer sweep at the right depth for a core binding change: 36-package prefix closure type-checked (with the dist-staleness trap caught and neutralized — the first pre-build "green" would have been read off stale declarations), four predicate surfaces + components + app-shell suites all green, ~7,200 tests total. Flipping ready + auto-merge.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions