Repository navigation
validateOrgAxisRedLines 读的 sharing-rule 键是 spec 拒收的:ADR-0105 D6 ① 在 criteria 路径上从不触发 #4984
Description
发现于 #4698 的实现过程。同一缺陷类,只是方向反过来:不是 producer 写了没人读的键,而是 consumer 读了没人写的键。
事实
packages/lint/src/validate-org-axis-red-lines.ts 判 ADR-0105 D6 ①(禁止沿 org 树做权限继承)时,对 sharing rule 这样取值:
asArray(cfg.sharingRules ?? cfg.sharing).forEach((rule, rIndex) => {
const criteria = JSON.stringify(rule.criteria ?? rule.filter ?? '');
const sharedTo = JSON.stringify(rule.sharedTo ?? rule.recipient ?? '');
if (criteria.includes(ORG_PARENT_FIELD) || sharedTo.includes(ORG_PARENT_FIELD)) { /* error */ }
});但 SharingRuleSchema(packages/spec/src/security/sharing.zod.ts)是 .strict(),声明的键集是:
name, label, description, object, active, accessLevel, sharedWith, type, condition
criteria / filter / sharedTo / recipient 全部只作为被拒别名出现在 sharingRuleUnknownKeyError 的 aliases 映射里 —— 那是给报错信息用的处方,不是被接受的键。
实测(build 后的 spec):
condition => ACCEPTED keys=name,object,active,accessLevel,sharedWith,type,condition
criteria => REJECTED: Invalid input
sharedTo => REJECTED: Invalid input
后果
validateOrgAxisRedLines 在 registry 里是 input: 'parsed',即跑在 Zod parse 之后。对任何 spec-valid 的 stack,rule.criteria / rule.filter / rule.sharedTo / rule.recipient 恒为 undefined,JSON.stringify(undefined ?? '') 得 '""',includes('parent_organization_id') 恒为 false。
于是:一条声明为 error、以 ADR-0105 D6 红线为依据的 gate,在 sharing-rule criteria 这条路径上永远不会触发。 作者写
defineSharingRule({
name: 'hq_sees_children',
type: 'criteria',
object: 'work_order',
sharedWith: { type: 'team', value: 'hq' },
condition: "record.parent_organization_id == 'org_hq'", // ← 正是 D6 ① 禁的形状
})会一路绿灯通过 os validate / os build / os lint。
同一文件里 RLS 那两段(permissions[].rowLevelSecurity[].using/check、objects[].rowLevelSecurity[])读的键是对的,只有 sharing-rule 这段错。ORG_AXIS_CROSS_ORG_BU_GRANT(②)也读 rule.sharedTo ?? rule.recipient,同样恒空 —— 那条同样从不触发。
修法
把两处取值改到 spec 的键上:rule.condition(而不是 criteria/filter)、rule.sharedWith(而不是 sharedTo/recipient)。
两点值得在修的时候一起想清楚,不要机械替换:
- 是否保留别名读法。 这条规则也会被
os lint在 normalized(pre-parse)层跑一次,那一层理论上还能看见作者写的别名。但按 Prime Directive Add comprehensive test suite for Zod schema validation #12,别名不该在 consumer 侧用??容忍 —— 别名已经在strictUnknownKeyError里有明确处方并被 reject,parse 就是那道门。建议只读 canonical 键,别名交给 spec 的拒收信息。 condition是ExpressionInputSchema。 parse 之后它是{ dialect: 'cel', source }信封,pre-parse 时可能是裸字符串。两种形状都要能取到 source(可参照 validate/lint have no check for "declared but never read" metadata — three instances found in one app in a day #4698 那条新规则里的toCompilerInput)。
回归测试
现有的 validate-org-axis-red-lines.test.ts 里 sharing-rule 那几例用的正是 criteria / sharedTo,所以测试是绿的而规则是死的 —— 测试 fixture 和 schema 一起漂移了。修的时候必须把 fixture 也改成 spec-valid 的形状,否则换个键名照样测不到真东西。建议加一条元测试:每个 sharing-rule fixture 先过一遍 SharingRuleSchema.safeParse,不通过就 fail —— 这类 fixture-schema 漂移只有这样才不会再来一次。
相关:#4698(母议题)、ADR-0105 D6、ADR-0057 D5。
Activity
xuyushun441-sys commentedon Aug 3, 2026
认领(PM 分派)
session_018iARDqtrhQgz6fVHDeDkbQclaude/issue-4984-org-axis-redlines-dead-keysdomain:devx(落点packages/lint)authoring-rules.ts注意它加过一条注册。本单要害:error 级红线门禁读的四个键(
criteria/filter/sharedTo/recipient)在.strict()schema 下只作为被拒绝的别名存在 —— 规则对任何 spec 合法的 stack 永不触发,而它自己的测试 fixture 用的正是死键,测试全绿、规则全死。修复除了改键名,必须带上 #4984 正文建议的 meta-test(fixture 必须先过SharingRuleSchema.safeParse,fixture/schema 漂移即红),否则同样的漂移还会再来一次。Generated by Claude Code
xuyushun441-sys commentedon Aug 3, 2026
审核通过 — PR #4992 已取消草稿并挂上 auto-merge(SQUASH)
死态复现是双层的,比议题写的更深:fixture 不止外层键漂移(
criteria/sharedTo被拒),sharedTo内层的id相对 canonical 的value也是被拒别名 —— 只改外层键名仍过不了 schema。D6 ① ② 两条红线在 parsed 与 raw 两层都实测判绿(死),不是推断。语义搬迁不是换名字,这正是派发文里要求"逐条核对 D6 原意"的原因:
condition是ExpressionInputSchema的三形态(裸串 /{dialect,source}/{dialect,ast}),新expressionText()三种都取;并且刻意不读meta.rationale—— 若用JSON.stringify(整个 condition),一条"本规则刻意不使用 parent_organization_id"的正当注释就会触发 error 级误报。error 门禁被散文误伤,比漏报更糟。别名一律不做??兜底(PD#12),且有测试钉住"写别名不判红,交给 spec parse 拒收"。meta-test 是结构性修复,且证明了守卫本身不死:每个 fixture 先过真的
SharingRuleSchema.safeParse,把 #4984 之前的两个死 fixture 原样喂进去断言必须抛错。反向验证把规则改回读死键:18 测试 7 红 —— 断言承重。实测无新红:两个 example app 与 default-permission-sets 全扫,没有"一直没被拦的真实违规",也没有误伤。
两个衍生发现,处置各得其所:
objects[].rowLevelSecurity段读的键 ObjectSchema 根本不声明 —— 议题说"RLS 那两段是对的"只对了一半。删该段还是给 spec 加对象级 RLS 授权面是 ADR 级取舍,立案不动手,对。business_unit,漏了 ADR-0105 D6 ② 原文点名的unit_and_subordinates(授权面更大 —— 整棵子树)。error 门禁的扩张按 scope 纪律未随本单做,且已预核过扩张后现有元数据不产生新红。这单是 fix(lint): org-axis 红线读 spec 声明的 sharing-rule 键 —— ADR-0105 D6 两条门禁此前从不触发 (#4984) #4992 落地后的直接后续,同文件,排队串行。落点 3 文件全在
packages/lint,零共享文件。Generated by Claude Code
feat(lint): gate a sharing-rule condition the runtime can only skip (o…
fix(lint): org-axis 红线读 spec 声明的 sharing-rule 键 —— D6 两条门禁此前从不触发 (obj…
fix(lint): 删除 validateOrgAxisRedLines 里 spec 合法 stack 到不了的四条分支 (objec…
fix(lint): 收敛 validate-expressions / validate-security-posture 的 spec…
fix(lint): 收敛 validate-rule-compilability 的 spec 不声明键 `??` 别名读法 (obje…
fix(lint): validateFormLayout 走视图容器阶梯,两条规则不再对真实 app 全盘报绿 (objectstack…
spec(migrations): register the distinct narrowing in the ADR-0087 led…
fix(spec): os migrate meta guidance for the driver-*, kernel-* and sy…