You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
…${root} is unbound here, so the predicate faults and falls back to VISIBLE, leaving the field the test was meant to hide showing for everyone (#6146).
readonlyWhen: the UNBOUND-ROOT case is fail-CLOSED (#4889)
… A readonlyWhen predicate that faults because it names a scope ROOT this operation did not bind … That single case now resolves to LOCKED. Every OTHER readonlyWhen fault (undeclared key, null overload, parse error) keeps the fail-open policy … and requiredWhen / option visibleWhen are untouched.
logger?.warn?.(`readonlyWhen for '${name}' reads '${unbound}', which is not bound for this operation — `+`treating the field as LOCKED (the declared lock is not waived because it could not be evaluated). `+…);returntrue;
PM 座位在验收 PR #6711(#6585)时的越界发现,不由该 PR 引入,按 PD #10 立案,未认领、未定级。与 #6713 同现场但不同条(见下「与 #6713 的关系」)。
事实(实测,
origin/main)packages/lint/src/validate-expressions.ts的checkFieldRuleUserRoot对visibleWhen/readonlyWhen/requiredWhen三个槽位共用一条文案,其中的因果句是:但
readonlyWhen的未绑定根在服务端不是 fail-open。packages/objectql/src/validation/rule-validator.ts的模块头自陈(:99-110):实现一致 ——
isReadonlyWhenLocked(:399-428)在unknownVariableOf(res.error)命中时:未绑定根正是这条 carve-out 命中的那一类(而不是「undeclared key / parse error」那一类),所以一个写了
readonlyWhen: "'admin' in user.positions"的字段,服务端的后果是被锁死,不是「对所有人可见」。诊断把失败方向讲反了。边界:哪些已测、哪些没测
诚实分档,不把未测的当已测:
readonlyWhen路径(上引代码),方向确为 LOCKED,与文案相反;evalFieldPredicate对readonlyWhenfault 的取值(Parent-scopedreadonlyWhenis unenforced server-side — the field lock fails open, so a paid invoice's frozen lines can be rewritten over the API #4889 的 carve-out 明写是服务端写门那一侧,ADR-0057 D10「server enforces, client is courtesy」),两侧是否同向需要单独量;requiredWhen—— 该槽位的后果根本不是可见性,「falls back to VISIBLE / showing for everyone」在语义上就不是它的失败面。Parent-scopedreadonlyWhenis unenforced server-side — the field lock fails open, so a paid invoice's frozen lines can be rewritten over the API #4889 明说requiredWhen未被 carve-out 触及,但那只说明它仍是 fail-open,不说明它的 fail-open 表现为可见。所以准确的说法是:这句因果只对
visibleWhen精确,对另外两个槽位至少不精确、对readonlyWhen服务端是反的。为什么记下来(而不是当文案小事)
处方是对的(移到选项级
visibleWhen/ 用权限集 FLS),作者照着做仍能修好。问题在理由那半:本仓反复把「诊断说真话」当契约面对待(ADR-0078 的读者面、validate-security-posture.ts:52-55那段「inert branch 读起来像一个在看着的门」)。一条把失败方向讲反的诊断,会让作者对「不修会怎样」形成相反的心智模型 —— 对readonlyWhen尤其要命:他以为「不修 = 字段泄漏」,实际是「不修 = 字段被锁,写不进去」,两者的紧迫性与排障方向完全不同。不是 PR #6711 引入的
明确记录,免得被当成回归:#6711 的 diff 只把
current_user换成${root}插值。这句因果与「三个槽位共用一条文案」都来自 #6584 / #6290,早于本次改动。#6711 只是让它更容易被命中(拼写从一种变三种),并顺带给它加了钉子(新测试toMatch(/\\w+` reads `user`/)` 断言的是根名,不碰这句因果)。⛔ 不构成阻塞 #6711 的理由,该 PR 是严格的改进。与 #6713 的关系(相邻,不重复)
#6713 讲的是根集合的形状 —— 黑名单 3 项 vs 实测白名单 3 项,其余 ~20 个
SCOPE_ROOTS成员同样静默。本条讲的是已命中之后那句话本身讲错了方向。两者可以各自独立成立:把黑名单翻成白名单(#6713 修法 1)不会自动修好这句因果,反过来改文案也不会多认出一个根。但两者在文案分档上会合流:#6713 修法 1 已经指出「现有处方是用户向的,对
data/vars这类根答非所问,得按根分档」。本条追加一条正交的分档轴 —— 按槽位分(可见性 / 只读 / 必填的失败方向各不相同)。若分诊决定做 #6713 修法 1,建议把这两轴一起设计,否则会改两遍同一段文案。可能的修法(留给分诊,不自选)
visibleWhen保留现文案;readonlyWhen改为「faults ⇒ 该字段被视为 LOCKED(Parent-scopedreadonlyWhenis unenforced server-side — the field lock fails open, so a paid invoice's frozen lines can be rewritten over the API #4889),声明的锁不会因为算不出来而被放行」;requiredWhen按实测结果补。需要先把上面「未测」的两格量出来。*When的未绑定根检查是一张 3 项黑名单,而真相是一张 3 项白名单 —— 其余 ~20 个SCOPE_ROOTS成员同样 fail-open 且全静默 #6713 修法 1 合并设计(推荐先决:若 字段级*When的未绑定根检查是一张 3 项黑名单,而真相是一张 3 项白名单 —— 其余 ~20 个SCOPE_ROOTS成员同样 fail-open 且全静默 #6713 走白名单,文案必然要重写,此时两轴一起做)。关联
#6585、#6584(PR)、#6711(PR,发现现场)、#6713(同现场姊妹条)、#6146、#4889(fail-closed carve-out)、#6457(
parent稀疏)、ADR-0057 D10、ADR-0058 D5(被 #4889 收窄的那条)、ADR-0078。查重:
readonlyWhen fail-closed 文案、falls back to VISIBLE、#4889 diagnostic direction三次检索,无同题单;#6713 如上分析为相邻非重复。