soma.cell

E0098: duplicate_edge

E0098 duplicate_edge — the same transition declared twice.

A compile-time error of soma check, the checker of the Soma programming language (soma explain E0098).

The rule

soma check reports E0098 when two edges of a state machine cover the same move from the same state with different guards, here the wildcard * -> cancelled and paid -> cancelled { guard { refunded } }. A transition() takes the first edge that matches, so the later edge's guard is never evaluated (or, in the other order, the first edge's guard refuses a move the second allows). Declare each move once: keep the guarded state out of the wildcard with except [...], adding it to the states already listed there.

A program that triggers it

wrong.cell

cell Orders {
    face {
        signal cancel(id: String, refunded: Bool) -> String
    }
    state order {
        initial: placed
        placed -> paid
        paid -> shipped
        * -> cancelled except [shipped]
        paid -> cancelled { guard { refunded } }
    }
    on cancel(id: String, refunded: Bool) {
        transition(id, "cancelled")
        return get_status(id)
    }
}

What soma check prints:

$ soma check wrong.cell
error: `paid -> cancelled { guard … }` repeats the move paid -> cancelled that `* -> cancelled` already declares with no guard — a transition takes the first edge that matches, so this guard is never evaluated there. Declare each move once, or keep the state out of the wildcard: `* -> cancelled except [paid]`
  --> wrong.cell:10:9
   |
10 |         paid -> cancelled { guard { refunded } }
   |         ^
  = code: E0098
✗ 1 error(s), 0 warning(s).

The fix field of soma check --json: a transition takes the first edge that matches, so this guard is never evaluated there. Declare each move once, or keep the state out of the wildcard: `* -> cancelled except [paid]`

On line 9, * -> cancelled except [shipped] becomes * -> cancelled except [shipped, paid].

wrong.cell → right.cell

--- wrong.cell
+++ right.cell
@@ -7,5 +7,5 @@
         placed -> paid
         paid -> shipped
-        * -> cancelled except [shipped]
+        * -> cancelled except [shipped, paid]
         paid -> cancelled { guard { refunded } }
     }

With the fix, soma check accepts the program:

$ soma check right.cell
✓ All checks passed.