--- opened: 2026-10-08 --- # 309. Two order rules ordered one pair both ways ## Symptom On 2026-10-08 the delivery group `feat/a-machine-joins-through-the-tunnel` held three pull requests: the controller's mesh-controller #132, the node-engine's `mesh-host` #52 and the lab's mesh-lab #61. Each was `ready` on its own. The delivery's `show` said of the group: - `"state": "rejected"`; - `"why": "its order contradicts itself: novox/mesh-controller@…, novox/mesh-host@…"`. The group's `pairs` held two orderings of the same two members: - the controller's member before the node-engine's, `why: "built by"`; - the node-engine's member before the controller's, `why: "engine before controller"`. Nothing would ever deliver the group. The person merged it by hand, the controller first, since that member only added a verb the new node-engine calls. ## Why it is a design issue ADR 0239 decision 4 states four order rules and says what to do when a declared order contradicts an inferred one. It does not say what to do when two inferred rules contradict each other. The planner treated that as a cycle, so a group that moved the controller's repository and the node-engine together could never be delivered. The refusal named two members and no rule, so the person could not tell what had clashed or what to write. A declared `after:` line could not help, because a declared order against an inferred one was a cycle too. ## Cause The controller's pull request moved the build agent, which lives in the controller's repository. Every source-built module is built by the build agent, including the node-engine. *Built by* therefore put the controller's member first. *Engine before controller* put the node-engine's member first. Both were added as pairs, and the planner's sort found no member with nothing before it. ## Fix ADR 0249 gives the rules a precedence: *declared*, then *built by* and *version skew*, then *engine before controller*. - **The controller (novox/mesh-controller PR #134).** `orderOf` collects each rule's claim, and for each two members the claims of the highest rank decide. The pair carries the rules it won over. Claims of one rank in opposite directions are a contradiction. Both pairs are kept, the members are the cycle, and the contradiction names both rules, the member each puts first and the `after:` line that settles it. - **mesh-delivery (novox/mesh-catalog PR #119).** It keeps the rules each pair won over and the contradictions, lists them in `groups` and the plan note, and refuses a group in the contradiction's words. A cycle through three members or more names the pairs in it. An older controller's answer reads as before. Replayed, the group is ordered: the controller's member, then the node-engine's, then the lab's. The pair reads "built by, over engine before controller". That is the order the person chose by hand. ## How it is checked **The check:** the replay R309 in mesh-lab's register (novox/mesh-lab PR #64), `TestReplay309` in the controller. It replays this group's members, reach and graph using only what `orderOf` had before the fix. Proved with the register's `cmd/prove`: it fails on the commit before the fix (the group refused as a cycle) and passes on it. The controller's `TestAnOrderRuleYieldsToAStrongerOne` checks the precedence and the refusal's words. mesh-delivery's `TestAGroupSaysWhichOrderRuleWonOrWhyNoneDid` checks that they reach `groups` and the group's state. **Not checked:** that a new inferred rule is given a rank. One added without a rank sorts below every other rule, and nothing refuses that.