fix(lint): 收敛 validate-expressions / validate-security-posture 的 spec 不声明键 ?? 别名读法 (#5017) - #5046
Conversation
… 不声明键 `??` 别名读法 (#5017) #4984 → #5009 同族第三轮。两条规则都以 `input: 'parsed'` 注册,看到的是 `ObjectStackSchema` 解析后的产物,所以读一个 spec 不声明的键对任何能解析的 stack 都不执行。议题点名五条,全包 grep 又找出同形的两条,一并处置为只读 canonical;`obj.security?.sharingModel` 整段删除 —— `ObjectSchema` 根本没有 `security` 键。 其中 `rule.expression ?? rule.predicate ?? rule.condition ?? rule.formula` 不是死代码而是活着的错:canonical 排第三,同时写了 `condition` 和被拒别名 `expression` 的规则,lint 校验的是 schema 会拒绝的那个,作者声明的那个从未 被读。测试里重建旧链演示该差异。 三个 example 与平台 default permission sets 上,改动前后 findings 逐字相同。 补两层结构性 meta-guard(declared-key ⊆ schema.shape + reachability),七条 读法各自通过变异测试。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…t-alias-fallback-sweep
#5046 把 validate-expressions / validate-security-posture 收敛为只读 spec 声明的键之后,两处 fixture 用的正是被拒的别名拼法,规则不再读它们: - `packages/cli/test/authoring-rule-command-parity.test.ts:90` —— CI Test Core 判红点。`validations: [{ name: 'r', expression: … }]` 里 `expression` 是 `validation.zod.ts` 按名拒绝的四个别名之一,收敛后没人读,`expression-invalid` 在三命令上都不再触发。改为 canonical `condition`,并补上 `type` / `message` 让 fixture 除了那条**刻意种下的**裸引用缺陷之外完全 spec 合法 —— parity 测试 本就该跑在 spec 合法元数据上,原来的 fixture 等于种了两个缺陷。 - `packages/lint/src/runtime-gate.test.ts:125,150` —— 这两处此前是绿的,但绿得 没有意义:`validationRules` + `expression` 双重别名,使得"上下文里有一条坏 验证规则"的 fixture 实际产生 0 条 finding(实测),所以 D4 那条断言 `result.errors).toEqual([])` 通过的原因是**没有东西可减**,而不是减法正确。 改为 canonical 后上下文真的产出 1 条 `invalid CEL predicate` finding,减法 逻辑第一次被真正跑到 —— 且仍然通过。 全包 grep 过 `expression:` / `predicate:` / `formula:` 作 validation 键、 `criteria:` 作 sharing 键、`validationRules:`、`security: { sharingModel }`、 `reference_to` / `referenceTo`:其余命中要么是 #5046 里刻意 pin 住 schema 拒绝 的别名 fixture,要么属于本 PR 未改动的规则(`validate-rule-compilability.ts` 自己读 `validationRules`,已另开 #5096),要么是 objectql / driver 等包自己的 内部形状,与本次收敛无关。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ
CI Test Core (3/3) 修复 —— 跨包 fixture 消费被拒别名拼法
判红点
validations: [{ name: 'r', expression: 'lead_score > 100' }]
改为 canonical,并补上 validations: [{ type: 'script', name: 'r', message: 'Score out of range', condition: 'lead_score > 100' }]实测收敛后的规则仍然读到并判红:
顺手 grep 又抓到一处「绿得没有意义」
validationRules: [{ name: 'bad', expression: 'record.owner ==', message: 'x' }]双重别名(集合键 + 谓词键)。实测: 也就是说 D4 那条「上下文里的既有违规不该赖到本次写入头上」的用例,断言 全包 sweep 结果grep 过
另开的单#5096 —— 全包 grep 的第八处,落在第三个文件 顺带确认了一件可能推翻本 PR 前提的事
验证Generated by Claude Code |
Fixes #5017
#4984 → #5009 同族第三轮。两条规则都以
input: 'parsed'注册(authoring-rules.ts),看到的是ObjectStackSchema解析后的产物 —— 未声明的键要么被 strip,要么被 strict 子 schema 按名整包拒绝,所以读它的分支对任何能解析的 stack 都不执行。议题点名五条,按派发要求全包 grep 同形
??别名链,又找出两条,一并处置。七条链,逐条核实
每条都对着 live
.shape+safeParse实测过。validate-expressions.ts205/421rule.expression ?? rule.predicate ?? rule.condition ?? rule.formulavalidation.zod.ts的aliases: { formula/expression/predicate/rule: 'condition' }里按名拒绝;canonical 排第三rule.conditionvalidate-expressions.ts414obj.validations ?? obj.validationRulesObjectSchema.shape只有validations;拒绝信息 "Did you meanvalidationRules→validations?"validate-expressions.ts595rule.condition ?? rule.criteria ?? rule.predicateSharingRuleSchema.shape=accessLevel, active, condition, description, label, name, object, sharedWith, type;criteria是运行时criteria_json的拼法(#3896),predicate直接拒绝validate-security-posture.ts94obj.sharingModel ?? (obj.security)?.sharingModelObjectSchema没有security键 ——sharingModel/externalSharingModel/publicSharing是平铺的,且 strict:嵌套写法被整包拒绝validate-security-posture.ts118def.reference ?? def.reference_tofield.zod.ts:331把reference_to映射为referencevalidate-expressions.ts178def.reference ?? def.referenceTovalidate-expressions.ts555action.objectName ?? action.objectobjectName;"Did you meanobject→objectName?"关键差异:第一条不是死代码,是活着的错
其余六条 canonical 都排首位,别名 limb 纯属不可达。这一条 canonical 排第三,所以别名会短路掉 canonical。改动前实测:
也就是说:一条同时写了
condition和被拒别名的规则,lint 校验的是 schema 会拒绝的那个,而作者声明的那个从未被读。producer 和 consumer 对同一份元数据给出两套说法。分两层说清:
os compile/build/validate):三个别名都让整包 stack 被拒,所以恒为undefined,链必然落到condition—— 与只读 canonical 完全等价,零行为差异。os lint不 parse,两个 tier 都跑在 normalized 上):别名可达,上面的短路就在这里发生。改动后 lint 读 canonical,真实缺陷浮出来。测试里重建了旧链(
OLD_CHAIN)来演示这个差异,而不是只描述它。反向验证:两个方向都测了
#5018 的教训是方向会反过来。这里两种都有,各自 pin 住:
def.reference ?? def.referenceTo(178):这条喂的是计数不是谓词,所以删掉别名 limb 反而会让下游parent-scope gate 新增一条诊断(master 数从 1 变 0)。只发生在 pre-parse 层、且该 stack 已因这个键被 schema 按名拒绝 —— 是无效 stack 上的噪音,不是有效 stack 上的新判定。诚实 pin 住而不是留给后人发现。action.objectName ?? action.object:删掉别名丢的是 action 的对象绑定而非谓词,所以对象无关的那半(裸引用作用域)照旧判红,字段存在性那半不再可能。lintAfter逐条写明,没有统一假设。真实元数据零新红
app-crm/app-showcase/app-todo+plugin-security的 default permission sets,改动前后两条规则的 findings 逐字 diff 为空(1 + 5 + 0 + 0 条安全 finding,0 条表达式 finding,全部不变)。两层 meta-guard(#4992 模式,#5018 形状)
.shape,且expected精确匹配(改名 loop 变量会静默解除扫描,所以强制回访表格)。另加一条 "covers every receiver" 元测试,防止新 receiver 整个溜出表格。validateSecurityPosture全部 15 个findings.push落点都被 fixture 触达。判据上有一处刻意不照抄 #5018:这里不要求 fixture
safeParse全绿,而是要求 schema 不报unrecognized_keys。因为这条规则被文档明确设计成也跑在 parse 前,好让os lint对 zod 会拒绝的值(sharingModel: 'read'→invalid_value)给出更好的信息 —— 强求 fixture 全绿会直接删掉四条正当规则。被拒的值和被拒的键是两回事:后者在 parsed 路径上压根到不了,而它出现在源码里就等于宣称"存在这么一个 authoring 面"。覆盖范围写明:
validate-security-posture.ts两层 guard 全覆盖(15 个落点、除两个有据可查的非 schema receiver 外的全部 receiver)。validate-expressions.ts的 declared-key guard 覆盖除 flow nodeconfig外的全部 receiver(cfg/startCfg排除的理由本身是 schema 事实:node config 按type判别、表达式槽走resolveFlowNodeExpressions描述符注册表;而functionName是schemaless-node-config.zod.ts声明的键、由 ADR-0087 D2 转换flow-node-script-config-aliases(#3796)在 load 时改写,退役五键是刻意在 pre-parse 层识别以给出具名替代 —— 读一个键是为了拒绝它,与本单的缺陷正好相反)。reachability 那半限定在本单改动的读法及其所在 surface:该规则 660 行、issues.push落点不带 rule id / path 模板(finding 形状早于{ rule, path, hint }),#5018 式的落点扫描不能直接搬;完整落点清单属于议题自己的建议 3(所有input:'parsed'规则共用一条 guard)。七条读法各自做过变异测试:任意一条加回去,都至少有一条测试转红(M1–M8,分别 2/5/2/2/2/1/3/2 条红)。
顺带发现,已另开单不在本 PR 修
#5026 —— 同文件的字段公式校验读
f.formula,而FieldSchema声明的是expression(formula正是它按名拒绝的别名)。形状同族但不是??链:代码里根本没有 canonical 读法,所以这段对任何 spec 合法 stack 从未跑过(真实元数据全部用expression:拼法)。收敛过去等于启用一条从未跑过的检查 —— 是覆盖面扩大而非删死代码,可能对现有元数据判红,不该由本 PR 顺手带上。本 PR 里它是 declared-key guard 中唯一一条显式记账的欠债(
TRACKED_UNDECLARED_READS,带 issue 号),并注明"这个列表只能缩短,不能变长"—— 而不是把f整个排除出表格。验证
改动限于
packages/lint。未触碰content/docs/releases/。Generated by Claude Code