The problem situation is this:
1. The Teacher role is defined with moodle/question:add set to Allow.
2. The Student role is defined with moodle/question:add Not set.
3. The Teacher role is overridden in course CF101 to set the permission on moodle/question:add to Prevent.
4. The Student role is overridden in course CF101 to set the permission on moodle/question:add to Allow.
5. Fred is assigned both the roles Teacher and Student in course CF101.
What is the result of has_capability('moodle/question:add', [Course CF101 context], [Fred])? I have attached a screen shot showing this in my new Explain permissions UI in HEAD.
If you read the description at the bottom of the screen shot, or http://docs.moodle.org/en/How_permissions_are_calculated, you would expect that the Allow and Prevent in cell (20, 20) cancel, so you should carry on along that row to cell (1, 20) where the Allow would determine the answer.
It turns out that has_capability returns false here. What happens is that if a cell contains some Allows and Prevents which exactly cancel out, then the computation misses out the rest of that row, and continues from the start of the next row in the table. Therefore, in this case, it misses the Allow in cell (1, 20).
I suggest we fix this bug.
Fixing has_capability is relatively easy. I don't (yet) know whether get_users_with_capability or other methods suffer from this bug.
I don't believe that anyone out there is relying on the buggy behaviour. (Nor do I believe that the buggy behaviour is causing problems in any likely situation. However, I don't think we should ignore this bug.)