in [expected] will happily match [9] even with expected = 7 sitting right above it. It reads like "accept only seven". It isn't. A bare variable in a pattern binds whatever arrives, so expected quietly becomes 9 and the branch runs.
in [^expected] is the fix, and it compares against the value you already have. That reads nicely once you know it. It's also a lot of meaning for one tiny hat.
For a single comparison I'm happy with the pin. Once the condition grows, in [value] if value == expected shows the binding and the test as two visible steps, and I'd take that over a clever compact pattern that hides what is being checked. Where exactly a condition gets too big for the pin, I honestly can't say yet.