Fair enough. I fully agree with the last point about adding options as a last resort but only when the first part ("plenty of extension points" is true). Let's see.
There are in total 3 possible "entry points" where the logic could be changed: the 'spin' event, the 'start' event and the 'incremental' option - those are the only possibilities to trigger user function that could be used to manipulate the widget before it gets new value assigned.
I tried to use the suggested 'incremental' spinner option but failed to find a way to make it work (I'd like to be corrected) and here is why. When that 'incremental' event is fired its handler gets passed nothing but a 'number of spins' which is an absolute value. Without any reference to the event that originated spinning it is impossible to see which direction the spinning is going to go (because the spinner's new value has not been calculated yet). No luck here.
Unlike the 'incremental' option, the 'spin' event does offer you an object where the newly obtained but not yet assigned spinner value is ready and available (and all of that is nicely documented too). You can see which way the spinning is going and you can easily manipulate the final value. This way of doing things has been deemed abusive so we also discount it.
That leaves us with the 'start' event which does not offer the newly computed value so getting the spinning direction is harder (you need to check which button started the event). In addition, just like the 'spin' event, it does not fire when stepUp()/Down() methods are used. Therefore it is in no way better than the 'spin' event, in fact it is worse.
So, talking about "multiple ways of doing what I want". Not really. It seems there isn't any other way except what's already been suggested above: pointing 'spin' event to my own custom function that will mangle the values and then calling the same function instead of the built-in stepUp()/stepDown() methods. I would like to be corrected but while it may look like "spinner has plenty of extension points" the only possible way to achieve what I want is deemed abusive and there is no other way no matter how you spin it (pun intended).
Now I have also established that neither 'spin' nor 'start/stop' events fire when programmatic steps are used and yet, there is still no clarity on the main topic of this ticket: WHY is it bad to be able to prevent programmatic steps?