Repository navigation
FormField/FormSection visibleWhen describe 宣称 runtime 表单绑定 current_user,但字段级条件规则的求值面从未绑过它——按文档写的谓词必然 fault 并 fail-open #6146
Description
Activity
Triage:
pm:queue+domain:spec+target:v17.Landing site (read from code, not from the title). The over-claim is four strings in
packages/spec/src/ui/view.zod.ts— verified verbatim onorigin/mainjust now::1411(field JSDoc),:1414(fielddescribe),:1505(section JSDoc),:1508(sectiondescribe), each carryingRoot: `record`+`current_user` (runtime forms). That ispackages/spec⇒domain:spec. The evaluation surfaces the body cites (objectuiresolveFieldRuleStatecall sites,rule-validator.ts) are evidence that the claim is false, not the fix site — under direction 1 nothing outsidepackages/specchanges.Why
pm:queueand not the decision box. The contract question this looks like it raises is already answered, twice, and both answers point the same way: objectui#1582 pinnedFIELD_RULE_ROOTS = ['record', 'previous', 'parent']with an explicit "nothing else (nocurrent_user)", and ADR-0068 / objectui#2284 scopeEvaluateRulesOptions.currentUserto per-optionvisibleWhen. So direction 1 (narrow the describe/JSDoc torecord+previous/parent, leavecurrent_userdocumented only on per-optionvisibleWhen) sits inside rulings that already exist — it is a concrete defect with a named location, not an appetite question.Direction 2 (actually bind
current_useron the field/section evaluation surface) is the half that would need a maintainer call, and it is deliberately not queued here: it is cross-repo, needs client and server to land together or verdicts drift, and the issue itself conditions it on "有真实业务拉力". If that pull appears, it is a fresh card, not a re-scope of this one.target:v17. Criterion ② — a public contract surface whose declaration is a lie. It is also a member of the family #5149 already carriestarget:v17for: a predicate written from this documentation faults and fails open, so the field stays visible instead of hiding. The doc does not merely omit the root, it names it as an example.Stale-premise check.
git log origin/main -- packages/spec/src/ui/view.zod.ts— most recent touch is820ea5d(a CI change, unrelated); all four line anchors still read as filed.Dedup. Full paginated enumeration of open issues and PRs in all three repos (the GitHub search API is not reachable from this seat — repository-scoped sessions get
This GitHub API path is not available). Nearest neighbours are #5149 (parent fail-open family,pm:queue+repo:objectui+target:v17) and #6128 (the build-time bare-identifier lint split out of #5149's ruling,domain:devx). Neither overlaps: #5149/#6128 make a broken predicate visible, this one stops the schema from teaching readers to write one. No duplicate; cross-linked only.Serialization note for whoever claims it. #6128 lands in
packages/lint, not inview.zod.ts— no file collision. But adescribeedit inpackages/specregenerates docs/baselines; check themerge=os-regenpaths before batching this alongside another spec-lane item.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
补一份构建期实测,给本单的裁决多一个面(不构成认领)。来自 #6128 / PR #6248 —— 那里为了给 view/page 谓词的裸标识符闸门定合法根集合,把
current_user逐层测了一遍。本单记的是运行时从未绑过
current_user。构建期这一侧方向相反、同样是坏的:lint 会因为作者按文档写了current_user而直接拒收构建,并给出一条错误的修法。const { validateStackExpressions } = require('@objectstack/lint'); validateStackExpressions({ objects: [{ name: 'acct', fields: { tier: { type: 'select', visibleWhen: "'admin' in current_user.positions" } } }] });
实测输出:
error | object 'acct' · field 'tier' visibleWhen bare reference `current_user` — a formula/validation expression binds the record as the `record` namespace, not at top level, so `current_user` resolves to nothing and the expression silently evaluates to null. Write `record.current_user`.三点值得记进本单:
- 被拒的写法正是 spec 自己给的示例。
packages/spec/src/data/field.zod.ts:136的 describe 原文就把P`'admin' in current_user.positions`列为 optionvisibleWhen的例子;字段级visibleWhen的绑定环境按:125与 view.zod.ts:1416 也写着record+current_user。 - 给出的修法是错的。
record.current_user在任何一层都不是绑定 —— 这条消息会把作者推到一个更坏的写法上。 - 根因在一个包内部自相矛盾。
packages/formula/src/cel-engine.ts的SCOPE_ROOTS列了user但没有current_user;而同包validate.ts的introspectScope对外宣告的 roots 包含current_user,checkRoleCatalog的四条正则也全是按current_user写的。同一个包,一边宣告它是合法根,一边把它判成裸引用。
之所以至今没人撞上:仓内示例只在每个选项的
visibleWhen上用过这个写法(examples/app-showcase/src/data/objects/cascading-select.object.ts:85),而validate-expressions.ts只遍历字段级visibleWhen,不遍历 option 级 —— 差一层就绕过了。所以本单裁
current_user时,建议把三件事一起裁,否则会各修一半:运行时绑不绑、spec 文档怎么写、以及SCOPE_ROOTS要不要收下它。若裁「保留current_user并在运行时绑上」,SCOPE_ROOTS加一个词即可;若裁「退役这个根」,则 field.zod.ts:136 的示例、view.zod.ts 两处 describe 和introspectScope的宣告都要一起改。PR #6248 的新闸门在这期间把
current_user声明为合法根、不报它 —— 那个根到底解不解析是本单的裁决,新规则不替它先斩。
Generated by Claude Code
- 被拒的写法正是 spec 自己给的示例。
Triage:
domain:spec→domain:spec-surface(seat #6298, split approved 2026-08-07; existing-stock migration executed by this pass on the maintainer's instruction). Admission test met — the describe teaches a binding that was never bound, so correcting it leaves every metadata document byte-identically as legal or illegal as before; changeset patch, zero migration. This card is named in the seat post itself as the surface half of the #6146 / #6290 pair (#6290 staysdomain:spec-tooling: the lint judging wrongly and prescribing the wrong fix).
Generated by Claude Code
现象
packages/spec/src/ui/view.zod.ts的字段级与 section 级visibleWhen文档(origin/main)::1410-1412(JSDoc)与:1414(describe):「Root:record+current_user(runtime forms) ordata(metadata forms)」:1505-1508:FormSection 级同样宣称但 字段级条件规则的实际求值面没有任何一处绑定
current_user:resolveFieldRuleState的三个生产调用点(packages/components/src/renderers/form/form.tsx渲染门与 error-clearing effect、packages/plugin-form/src/WizardForm.tsx必填门)都不传 scope,evalFieldPredicate只绑record/previous/(经 extra 的)parent。objectui 的授权 autocomplete 也是这么钉的:packages/app-shell/src/views/metadata-admin/celAuthoring.ts的FIELD_RULE_ROOTS = ['record', 'previous', 'parent'],注释明说 "nothing else (nocurrent_user)"(objectui#1582 的决定);packages/objectql/src/validation/rule-validator.ts的EvaluateRulesOptions.currentUser按其 JSDoc 只供 per-optionvisibleWhen(objectui#2284,ADR-0068);readonlyWhen求值(isReadonlyWhenLocked)绑record/merged +parent,不含current_user。per-option
visibleWhen(选项级)确实绑current_user——文档对「选项级」为真,对「字段级/section 级」为假。后果
照 describe 写
visibleWhen: "current_user.positions.exists(p, p == 'admin')"的字段级谓词,引用未绑定根 → fault → fail-open(字段恒可见)。这正是 #5149 的「文档诱导写出永不生效谓词」类别(Repro 1 是裸标识符,这里是文档亲自点名的根)。objectui PR objectstack-ai/objectui#3541(#5149 诉求 2)落地后该 fault 会以每谓词一条console.warn显形(undeclared reference to current_user一类),不再静默,但谓词本身依旧不工作。两个修法方向(留给分诊)
record(+previous/parent),current_user只保留在 per-optionvisibleWhen的文档上——与 objectui#1582 钉的授权面一致,改动最小;resolveFieldRuleState调用点传 host predicate scope;服务端 rule-validator 同步)真的绑current_user——有真实业务拉力(按角色显隐字段)时才值得,且需两端同时落,否则客户端/服务端 verdict 漂移。按仓规不自选方向,留 PM 分诊。发现于 #5149 诉求 4 验收(该验收本身成立:
record./data.绑定说明与示例齐全,本单只针对current_user这一半句的过度宣称)。Refs: #5149、objectstack-ai/objectui#3541、objectui#1582、objectui#2284、ADR-0068