fix(cli): gate the two decision-routing shapes that can never work, flag the inert config.condition (#4414) - #4493
Merged
Conversation
…nert condition (#4414) Two follow-ups to #4440, both about metadata that reads like a guard and is not one. `flow-branch-label-unmatched` and `flow-default-edge-with-condition` now FAIL the build. The bar is restated at the top of the file, because the old one — "a guaranteed runtime failure" — no longer described the set: it is now **no reading of the author's metadata does what it says, deterministically, on every run**. A branch label no out-edge carries cannot route; an edge that is both `isDefault` and conditional always lets the condition win, so the marker routes nothing. Neither FAILS; both are wrong every time and silently. The other two stay advisory, and the policy now says why. A decision with one guarded and one unconditional out-edge is usually a guard that does not guard, but it is also a legal "maybe notify, always continue" fan-out, and two default edges can genuinely mean "when nothing matched, do both". The bar is provability, not severity of consequence — failing a customer's build on a shape we cannot prove wrong is the worse trade. No wiring change: the rule is already `tier: 'gating'` across all three commands (#4409). `flow-inert-node-condition` is new. `config.condition` is the trigger gate on a `start` node and is read by no other node type — the engine parse-validates it everywhere and then ignores it, so on a `decision`, where the name makes it read as the branch predicate, it is a guard that gates nothing. Two of the three bundled apps had one: app-todo's `check_recurring` and app-showcase's `needs_exec`, each a third copy of a predicate its out-edges were already enforcing. The showcase even carried a comment saying the node condition "is not evaluated by the engine" and kept it anyway — the residue this rule exists to stop accumulating. Both are now plain exclusive gateways. Advisory: the surrounding edges usually still route correctly, so it is dead weight rather than a provable misroute. The node-type list is a closed set of builtins whose executors were actually read, not "everything but `start`" — ADR-0018 keeps `node.type` open and a plugin executor may legitimately declare and read its own `config.condition`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q8as8yR67v41xEdomiTba9
…on-routing-bug-aufqaj
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 20 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#4414 的两项收尾,都是「读起来像守卫、实际不是守卫」的元数据。承接 #4440(运行时)与 #4474(账本)。
配套 objectui:objectstack-ai/objectui#3148(Decision 检查器的两处文案)。
① 两条规则升
errorflow-branch-label-unmatched和flow-default-edge-with-condition现在打断构建而不是警告。真正需要改的不是接线,而是那句判据。文件头原来写的是 error 只留给「保证运行时失败」的形状 —— 按字面这两条都不该升,因为它们不失败,它们走错路。所以判据如实重述为:
两条都符合:一个没有任何出边携带的分支 label 不可能路由;一条同时是
isDefault又带condition的边,永远是 condition 赢、标记什么也不做。它们不失败 —— 它们每次都错,而且是静默的,这更糟。另外两条故意留 warning,政策里现在写明了理由:
flow-decision-unconditional-branch通常是守卫不守,但「一条守卫边 + 一条无条件边」也是合法的「可能通知、总是继续」扇出;flow-multiple-default-edges确实可以表示「都不匹配时两件事都做」。判据是可证伪性,不是后果严重程度 —— 拿一个我们证不了错的形状去打断客户构建,是更差的交易。接线无需改动:
lintFlowPatterns已经是tier: 'gating'且跑在全部三个命令上(#4409),而这正是authoring-rule-wiring.test.ts存在要守的那道缝。② 新规则
flow-inert-node-conditionconfig.condition只在start节点上是活的(触发门),其它任何节点类型都不读 —— 引擎在注册时对每个节点都 parse-validate 它(所以畸形的会被抓),然后就忽略。而在decision上,这个名字让它读起来就是分支谓词,这也正是它被写出来的原因。三个 bundled app 里有两个中招:
app-todo的check_recurring;app-showcase的needs_exec—— 而且它旁边的注释已经写明「nodeconfig.conditionalone is not evaluated by the engine」,知道是死的还是留着了。这比前一个更能说明规则的价值:人知道了也会留下残留,只有 gate 会清掉。两者都是各自出边已经在执行的谓词的第三份拷贝。现在都改成纯 exclusive gateway。
Advisory 而非 gating:周围的边通常仍然路由正确,所以它是死重量,不是可证明的误路由。
节点类型用的是我实际读过其执行器的内置类型闭集,不是「所有非
start」—— ADR-0018 让node.type保持开放,插件执行器完全可以合法地声明并读取自己的config.condition,我们只能对自己出的那些类型下这个断言。新增内置类型必须被检查,而不是默默继承。验证
start不误报、空/缺省不误报),4 处断言补上 severity —— 两条 gating 断言'error',两条 advisory 断言undefined,免得将来有人顺手全升。create_recordnode value semantics ambiguous (literal vs CEL vs macro vs ref) #1315 的插值规则跳过 CEL condition,而新规则在那个形状上正确地触发了;改成只断言它真正针对的两条规则。pnpm build+pnpm test整仓 132/132 全绿,pnpm typecheck122/122,pnpm lint干净。相关
🤖 Generated with Claude Code
https://claude.ai/code/session_01Q8as8yR67v41xEdomiTba9
Generated by Claude Code