Skip to content

[Decision] What diagnostic budget should production carry for a faulting visibleWhen? Today a node-gate fault is entirely silent in a production bundle #6038

Description

@yinlianghui

Escalated by the domain:ui PM seat (session session_01CSoz9uGhaaSgiq3hshtN7L, R38, 2026-08-24). This is gap 3 of #5926, carved out because that card grades it itself and the grade is not a bug:

⚠️ It is also a deliberate performance short-circuit, so the fix is a design question — what diagnostic budget production should carry — not a bug report. Grading it as a bug would misread it.

#5926 was dispatched this round for gap 1 only (the emptyAction authored-node bypass). Gap 2 stays an observation on that card. Gap 3 is this card. ⛔ The PM seat does not rule it, and ⚠️ triage may re-grade the label — it is applied here to make the card visible in the inbox, not as a triage product.

一句话问题

evaluateVisibilityPredicate 里有一句 if (!__DEV__) 短路。所以在生产包里,一个作者写的 visibleWhen 谓词求值失败时,控制台一个字都不会打——门禁停止把关,页面照常渲染,没有任何信号。是要给生产留一条诊断通道,还是接受这份沉默换取性能?

为什么现在必须裁,而不是继续观察

这不是推测,是实测过的。 objectstack#11254 的普查里,一次 bare-string 断裂在真实页面上让门禁停止把关,且完全没有产生任何 console 行。(第一轮里那些 "has" is not a function 的日志来自另一个面——record-alert.tsxuseCondition 路径,那条无论如何都会打日志。)

⭐ 而且这条正是读懂另外两条的前提#5926 的原文说得直接:gap 3 是 gap 1 危险的原因。门禁本身结构上是健全的——visibleWhenpackages/react/src/SchemaRenderer.tsx 里被统一强制一次shouldHide 先测 visibleWhen(自 #5454 起排在被提升的 visible 之前),置 _hidden,然后 if (evaluatedSchema._hidden) return null 在注册表分发之前触发,所以块渲染器根本没机会忽略它。正因为门禁是健全的,一次静默失效才特别难被发现:没有人会去查一个"设计上不可能被绕过"的机制。

选项 × 真实代价

选项客户端效果我们付的代价
A 维持现状(生产静默)门禁失效时,作者与运维都拿不到任何信号;一个 class-1 缺陷可以长期在线上不被发现零。这是今天的行为,也是当初刻意选的
B 生产打一条限流的 warn失效可被发现;线上排障从"复现不出来"变成"看日志"每个失败谓词一次 console.warn(需去重/限流,否则一个列表里几百行会淹没控制台);极小的包体与运行时成本
C 走遥测通道而非 console失效可被聚合观测,不打扰终端用户的控制台需要接一条上报通道;与 #5522 的客户端错误上报许可门禁耦合,而那条本身还在等运维动作
D 让宿主自己决定(一个 opt-in 开关)需要的宿主打开它,其余不付代价新增一处公开配置面——本仓当前阶段倾向删特例而非加特例

四棱

① 平台长远合理性 — 一个"声明即强制"的门禁,其失效必须可被观测,否则"强制"只是在正常路径上成立的说法。B/C 修复这一点,A 把它永久留在信任而非证据上。

② 实测到的业务拉动 — 拉动是实测的而非假设的:#11254 已经踩到过一次,且只在专门做普查时才被发现。没有客户报障,因为门禁静默失效时没人知道本该被挡住的东西没被挡住——这类缺陷的报障率天然接近零,不能据此读作"没需求"。

③ 防 AI 写错元数据 — 这条最重。AI 生成的元数据最容易写出语法或绑定上站不住的谓词(本轮 #6010 就实测到一例:bare-string 里的 CEL in 操作符会被 legacy JS 求值器拒绝并 fail open)。生产静默意味着 AI 批量产出的坏谓词在线上全部 fail open 且无人知晓。A 是这条上最差的选项。

④ 创业阶段不扩散 — A 与 B 都不扩散(B 只是一行日志);D 明确是加特例;C 引入一条新通道且与一个尚未完成的运维依赖耦合。

推荐 B,四棱同向:成本落在一处,不新增公开面,把一个今天靠信任的性质变成靠证据的性质。回退 A(明确接受这份沉默,并把这个接受记录下来,这样下次普查发现同类问题时不必重新讨论)。⚠️C 不建议现在选——它依赖 #5522 的客户端错误上报许可,而那条正等着维护者做两件运维动作(轮换已提交的 Sentry DSN、给托管 console 授权)。

本分析看不见什么

① 没有读数说明生产里到底有多少谓词在 fail open —— 正因为它是静默的,这个数在选 B 之前不可测,这本身就是 B 的论据之一。② 没测限流后的实际日志量级:一个 200 行的列表里若每行都有一个坏谓词,去重键怎么取会决定 B 是可用还是刷屏。③ __DEV__ 短路当初的性能读数没有留档,所以"省了多少"只能重测,不能引用。

裁决格式:回「A」/「B」/「C」/「D」即可。裁后本席按结果路由回 domain:ui 队列执行。

Pointers

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions