fix(lint,formula): 字段级 *When 的根检查翻成白名单,因果句按槽位分档 (#6713, #6716) - #6798
Conversation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AZgRyPVwi1jLb1mNNuUQ9o
…ld-rule-root-allowlist
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AZgRyPVwi1jLb1mNNuUQ9o
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
|
座位验收( 两条新测量是本 PR 的承重结论(不是继承来的),我逐条独立复核:
范围扩张(㉕)判定:接受。 自捉扫描器漏报,是这份报告里我最看重的一段。 第一版正则漏了 tagged template( 反向验证三轮全部命中,B 轮的部分反转是预先声明的:共用那句话本就是
CI 收敛后由我摘草稿并入队。 Generated by Claude Code |
Fixes #6713
Fixes #6716
两单合一、同一次设计通过:它们改写的是
checkFieldRuleUserRoot里同一段文案,分开做等于把那段写两遍,而且第二遍会推翻第一遍的分档。#6713 给的是按根分档轴,
#6716 给的是按槽位分档轴,两轴正交。
轴一:根集合从 3 项黑名单翻成 3 项白名单(#6713)
字段级
visibleWhen/readonlyWhen/requiredWhen实测只绑record、previous、parent。三处独立证据,本 PR 逐条复验过:packages/objectql/src/validation/rule-validator.ts——isReadonlyWhenLocked绑{ record, previous, extra: { parent } };requiredWhen块绑{ record, previous, ...parentScope }evalFieldPredicate绑record+previous+ 调用方scope,而 objectui 五个字段级调用点(form.tsx三处、WizardForm.tsx、GridField.tsx)传的scope只可能是undefined或{ parent }ObjectFieldInspector.tsx的FIELD_RULE_ROOTS = ['record', 'previous', 'parent'],注释明写 "nothing else"而此前的检查是一张黑名单(#6584 一项、#6711 三项)。黑名单在这个面上结构性地追不上
SCOPE_ROOTS:每新增一个根都要有人记得抄过来。实测 21 个根落在这条缝里 —— 同样未绑定、同样 fault、而且同样静默(它们都在
SCOPE_ROOTS里,所以裸引用检查也从不报它们)。其中两个是高可信度作者笔误:
os.user.id—— ADR-0068 D1 的第四种用户拼写。stdlib.ts的buildScope把同一个EvalUser同时挂在current_user/user/ctx.user/os.user下,fix(lint): 字段级 *When 的用户根拒绝覆盖 ADR-0068 的全部三种拼写 (#6585) #6711 收了三种,os这一支没收;data.status == 'x'——data是元数据表单里同一个visibleWhen键的合法根(
view.zod.ts的.describe():"Root:record… in runtime forms, ordatain metadataforms")。两种表单、同一个键名、不同的根。
判定改为
SCOPE_ROOTS成员减去白名单,列表从@objectstack/formula取而不在消费端重述,因此
SCOPE_ROOTS将来新增的成员自动被覆盖。处方随之按根分档:用户根保留选项级visibleWhen与权限集 FLS 两条用户向处方;data给元数据表单 vs 运行期表单的解释;其余根给通用的「改写成
record谓词」。此前只有用户向处方,对写了data.type == 'select'的作者是答非所问。
轴二:因果句按槽位分档(#6716)
三个槽位此前共用一句「falls back to VISIBLE … showing for everyone」,而这句只对其中一个
精确。派单要求「不得沿用未测的表述」,所以三格每格量了两端:
visibleWhenConditionalFieldDef无此成员,fieldsNeedPrior只看requiredWhen || readonlyWhen ||选项可见性fallback: truereadonlyWhenisReadonlyWhenLocked命中unknownVariableOf返回true= LOCKED(#4889 carve-out),stripReadonlyWhenFields随即把该字段从 payload 删除fallback: false⇒ 表单仍渲染为可编辑requiredWhencontinue(#4977 明确没采用 #4889 的 carve-out),该次写入不强制fallback: false⇒ 表单也不标必填两格「未测」现已实测,结论都与 #6716 的预判一致:客户端
readonlyWhen与服务端不同向(这正是 ADR-0057 D10 要裁决的情形),
requiredWhen的失败面根本不是可见性。conditionalRequired在FieldSchema里是retiredKey(按名字拒绝),解析后的编译路径上该分支惰性,因此给它一条与槽位无关的通用句,而不是编造第四格测量。
范围扩张披露(纪律 ㉕)
本 PR 动了
packages/formula(简报落点是packages/lint):把SCOPE_ROOTS改为公开导出。这是继承自上一位 dev 的改动,我按自己的判断复验后保留,理由:
SCOPE_ROOTS.filter(...),测试的残余根表也由同一个import 生成,lint 里不再有任何一份根名副本。新增消费者只有两个,都在 lint,都为这条规则。
firstUndeclaredReference不能替代,且这是实测而非偏好 —— 严格环境同时声明 CEL 的类型名,
type(record.x) == string里的string会被判成「能解析的根」。见下方反向验证 C:按可解析性判定会误杀这条合法谓词,恰好 1 例。
按 #6584 先例(同样 formula + lint 两包),changeset 为两包各
patch。爆炸半径(白名单严格宽于黑名单,必测)
自写扫描器跑遍
examples/+packages/与下游objectui,按槽位名与根拼写双向扫。结果:字段级
*When读取任一新拒绝根的用例为零。值得记一笔:扫描器第一版漏报。本仓谓词写作 tagged template(
visibleWhen: P`record.status == 'paid'`),而第一版正则既没允许引号前的标签,又用
[^'"]排掉了谓词内部的单引号 —— 两个 bug 叠加,examples/的 41 处全部隐形。修正后解析出的When槽位从 82 涨到 **273**,命中新拒绝根的 非测试用例 **46 处**,但**全部**落在packages/spec/**/.form.ts元数据表单(data在那里是 合法根)与 objectui 的metadata-admin/anchors.ts。这些面本规则**够不着**:规则只从stack.objects[].fields[]的字段走查里调用,而元数据表单是defineForm({ sections: [{ fields: [...] }] })`的表单视图,两者容器与形状都不同。
反向验证(三轮,预测写在跑之前)
visibleWhen必绿visibleWhen绿 ✅SCOPE_ROOTS成员)does NOT reject a CEL TYPE name used as a value✅B 轮的方向是部分反转的,并且是预先声明的:共用的那句话本来就是
visibleWhen的,所以visibleWhen断言不可能因还原而变红 —— 真正承重的钉子是readonlyWhen/requiredWhen的两条否定断言。这不是漏测,是这条改动的真实反向形状。
三轮变异每轮结束后都以
git diff --quiet校验过还原是逐字节干净的。继承工作的处置
本卡是再派发:上一位 dev 死于配额墙,留下 4 个未提交文件,且死前最后一句是「Restoring.」——
存在未还原变异残留的风险。处置:
SCOPE_ROOTS成员判据、白名单、按槽位映射俱在),且全量 lint 套件 1702/1702 绿 —— 变异树会在新测试上变红。C 轮变异正是它死前那轮,我从零
重跑并复现了同样的 1 例。
os.user第四拼写、conditionalRequired的retiredKey身份、data的元数据表单出处),三轮反向验证全部从零重跑,爆炸半径重新自测 —— 并在重测中发现并修掉了扫描器的漏报 bug。
验证
pnpm --filter @objectstack/lint test—— 65 文件 / 1702 用例全绿pnpm --filter @objectstack/formula test—— 21 文件 / 561 用例全绿pnpm --filter @objectstack/lint typecheck、pnpm --filter @objectstack/formula typecheck—— 均干净pnpm check:nul-bytes(6326 文件)、check:empty-changeset、check:adr-anchors、check-changeset-no-major.mjs、改动文件的eslint --no-inline-config—— 均 exit 0origin/main(f6cd63549);其中 fix(formula): converge the CEL pushdown parser onto the canonical front end, with an rc grace window (#6132) #6766 恰好改到cel-engine.ts,合并后 formula已重建、两包测试重跑。
Generated by Claude Code