按 #5009 建议 3 顺带核对了邻居规则。`validate-org-axis-red-lines.ts` 的四条已在该单清掉;下面这些是**同一形状、不同文件**的读法,按 Prime Directive #10 单独记账,不在 #5009 的 PR 范围内。 每一条都对着 spec 实际 `.shape` 核过。 ## `packages/lint/src/validate-expressions.ts` | 行 | 读法 | spec 现状 | |:--|:--|:--| | 205 / 421 | `rule.expression ?? rule.predicate ?? rule.condition ?? rule.formula` | 跨字段校验规则声明的键是 `condition`。`formula` / `expression` 是 `validation.zod.ts` 里 `aliases: { formula: 'condition', expression: 'condition' }` 明确**按名拒绝**的别名;`predicate` 连别名都不是。注意这里 canonical 的 `condition` 排在第三顺位 —— 前两个非法别名优先 | | 414 | `obj.validations ?? obj.validationRules` | `ObjectSchema.shape` 只有 `validations`,没有 `validationRules` | | 595 | `rule.condition ?? rule.criteria ?? rule.predicate` | `SharingRuleSchema` 只声明 `condition`;`criteria` 正是 `sharingRuleUnknownKeyError` 里 → `condition` 的被拒别名(#4984 在**另一个**文件里修的就是这一条) | ## `packages/lint/src/validate-security-posture.ts` | 行 | 读法 | spec 现状 | |:--|:--|:--| | 94 | `obj.sharingModel ?? (obj.security)?.sharingModel` | `ObjectSchema.shape` 有 `sharingModel` / `externalSharingModel` / `publicSharing`,**没有 `security`** | | 118 | `def.reference ?? def.reference_to` | `field.zod.ts:331` 把 `referenceTo` / `relatedTo` / `target` 等映射为被拒别名 → canonical `reference` | ## 为什么要记账 两个文件都是 `input: 'parsed'` 注册的 gating 规则(见 `authoring-rules.ts`),看到的是 `ObjectStackSchema` 解析后的产物:stack 根 **strip** 未声明键,`.strict()` 子 schema 直接**整包拒绝**。所以这些别名 limb 对任何 spec 合法 stack 都不执行 —— 和 #4984 / #5009 完全同一种形状。 危险同样不在漏报(canonical limb 通常也在读),在于: 1. 读代码的人会相信 `objects[].validationRules`、`object.security.sharingModel` 是真实的授权/校验面; 2. `validate-expressions.ts:205/421` 那条尤其糟 —— canonical 的 `condition` 排在两个被拒别名之后,只要作者写了被拒的 `expression`,规则读的就是它(在 normalized 层),而 `os validate` 会拒掉整个 stack。producer 和 consumer 对同一份元数据给出两套说法。 ## 建议 1. 三处收敛为声明键:`condition`、`validations`、`sharingModel`、`reference`。 2. 复用 #5009 落地的结构性 meta-guard:规则源码里从某个 surface 上读的每个键,必须出现在该 surface 自己的 Zod `.shape` 里(`validate-org-axis-red-lines.test.ts` 的 `READ_SURFACES` 表)。那条 guard 是通用的,搬过来即可,并且它扫的是**源码**不是行为 —— 不可达分支没有行为可断言,这正是问题本身。 3. 顺带把该 guard 推广成所有 `input: 'parsed'` 规则共用的一条测试,是更彻底的做法(成本更高,可另议)。 ## 参考 - #4984(sharing rule 字段层)、#5009(org-axis 规则剩下四条 + meta-guard 模式)、#4992(meta-guard 模式来源)
Activity
xuyushun441-sys commentedon Aug 3, 2026
认领(PM 分派)
session_018iARDqtrhQgz6fVHDeDkbQclaude/issue-5017-lint-alias-fallback-sweepdomain:devx(落点packages/lint的validate-expressions.ts与validate-security-posture.ts;fix(lint): 删除 validateOrgAxisRedLines 里 spec 合法 stack 到不了的四条分支 (#5009) #5018 已进队,该包空闲)本单是 #4984 → #5009 同族的第三轮扫尾。派发要求:沿用 #5018 的两层 meta-guard 模式(declared-key ⊆ schema.shape + reachability),并注意 #5018 已证明的一点 —— canonical 排
??首位时反向验证方向要反过来(改动前对非法拼法越权判红,改动后让位给 schema)。validate-expressions.ts:205那条 canonicalcondition排第三顺位的,行为可能真的会变(别名短路),要单独测。Generated by Claude Code
xuyushun441-sys commentedon Aug 4, 2026
审核通过 — PR #5046 已取消草稿并挂上 auto-merge(SQUASH);第三轮扫尾收官
议题点名 5 条链,全文 grep 又找出 2 条,7 条全部处置;真实元数据(三个 example app + DEFAULT_PERMISSION_SETS)before/after findings 序列化 byte-identical;7 条被删 limb 逐一 mutation 回放,8 组全红。
三点超出模板的判断,都对:
condition写了record.no_such_field,而报告出来的却是别名键的 null-guard 判定,真缺陷从未被报。消费者的整个答案是关于一个生产者拒收的键。老链在测试里重建为OLD_CHAIN,差异是演示出来的不是断言出来的。def.reference ?? def.referenceTo喂的是计数,删 limb 反而新增诊断(master 计数 1→0 触发下游 parent-scope 门)。逐条写lintAfter而非假设统一沉默 —— "增/减/翻向"三分法进方法论。sharingModel:'read')一条更好的报错 —— 平铺的 safeParse-success 判据会逼着删掉四条正当规则。改成"无unrecognized_keys":被拒的键才是 parsed 路径够不到的东西,被拒的值不是。 键/值之分是承重思想,写进了 docblock。衍生 #5026 的处置尤其克制:
if (f.formula)是无 canonical limb 的纯别名读 —— 收敛它不是删死代码,是激活一条从未跑过的检查(整个 field-formula 校验对 spec 合法 stack 从未生效,真实元数据全写expression:),自带验证义务,不该夹在扫尾 PR 里。立单,且在 declared-key guard 里作为唯一显式TRACKED_UNDECLARED_READS条目带着("此表只许缩不许长")—— 债务可见而非豁免。#5026 属 bug 类,列入下一波(维护者新规:只派 bug)。顺带修的两处见微知著:既有 fixture 六处用被拒的
expression:拼法(测试自己在非法元数据上断言);lazySchema代理函数目标,typeof === 'object'守卫会让所有惰性 schema 静默回答"什么都不声明" —— 每面加了declared.length > 0防空转。议题一处陈述过期照实报:
predicate在当前 main 是别名(四名映射齐全),结论不变。Generated by Claude Code
fix(cli,lint): 把消费被拒别名拼法的 fixture 改为 canonical (#5017)
fix(lint): 删除 validateOrgAxisRedLines 里 spec 合法 stack 到不了的四条分支 (objec…
fix(lint): 收敛 validate-expressions / validate-security-posture 的 spec…
fix(lint): 收敛 validate-rule-compilability 的 spec 不声明键 `??` 别名读法 (obje…
test(lint): excuse the #8116 AST-walk locals in the #5017 receiver-co…
feat(lint): report `controlled_by_parent` with no relation to derive …
feat(spec): sink the objectstack-ai#7865 injected-column provenance d…
docs(spec,lint): a form SECTION `visibleWhen` binds `current_user` to…
fix(lint): scope the bare-reference suppression to ambient roots, dod…
fix(lint): report a controlled_by_parent object whose master is decid…
test(spec-carrier): pin the refusal AND the absence answer at each ro…
fix(security): pass the stack's declared capabilities at every audien…
fix(lint): security-role-word visits objects[].fieldGroups[] — the he…
Refuse an unreadable `reference` carrier at the ten residual readers …
fix(spec-consumers): the implicit-target census — 17 sites judged, fo…
wip(lint): push-style cbp finding sites; table the merged-shape pass …
fix(lint): read the undeclared-hop refusal through the tabled obj/def…
fix(lint): read the refused field's type from the field-type index, n…