A system that quietly proceeds when it cannot tell what is safe is gambling with a decision nobody asked it to make. Refusing outright, on purpose, is sometimes the correct behaviour.
I built a signup flow once where the sensible-sounding default, when the system could not confidently tell whether an address should be allowed to create an account, was to let it through anyway and sort out any mess afterwards. That default felt reasonable while I was writing it, because refusing someone who has done nothing wrong feels like a cost, and it is one you can see immediately, right there in the support queue, in a way the alternative cost is not visible at all in the moment you are actually making the decision. Every instinct at the keyboard pushed towards letting the uncertain case through rather than turning a real person away for a reason I could not fully justify to them on the spot.
The alternative cost only shows up later, and by then it is much harder to undo. An account created in a moment of uncertainty is a real account, with a real row in a real database, real emails sent to it, and possibly real activity attached to it by the time anyone notices something was wrong with how it got there. Reversing that requires deciding whether to delete something a person might now be relying on, which is a far larger and more awkward decision than the one I was originally trying to avoid. A signup that was refused in that same moment of uncertainty costs the person one retry, which is annoying and forgotten within the day. Those two costs are not remotely the same size, and treating them as roughly equivalent was the actual mistake, not the specific default I happened to pick.
So I changed the shape of the decision itself rather than trying to make the uncertain case smarter. Wherever the system cannot tell what the safe answer is (a missing configuration row, a database it cannot currently reach, a check it cannot currently run) the fallback for anything that creates an account now refuses rather than proceeds. Missing information is treated as a reason to say no, not as a coin flip quietly resolved in favour of whichever branch happened to be easier to write that day. The system no longer has to be clever about the edge case. It only has to notice that it does not know, and stop there.
The refusal that actually earned its keep was a deploy check that would not confirm success until a health endpoint reported the version just shipped, and the one time that mattered it caught a route that had silently failed to bind, which would otherwise have shipped looking perfectly fine. The same check has also fired for nothing worse than a slow warm-up, costing a needless re-run more than once. I have never regretted keeping it strict enough to do both.
I do not think refusing is free, and I am wary of anyone who treats it as an unqualified win over allowing. A system that refuses too readily trains the people using it to expect friction, to route around it, or to stop trusting it does anything useful at all, and every one of those responses is a real cost that shows up whether or not any individual refusal was ever justified on its own terms. A support inbox full of complaints about being locked out is exactly as real a problem as a database full of accounts that should never have existed in the first place, and I am not willing to pretend only one of those queues matters.
The judgement that actually matters is not refuse-versus-allow in the abstract, it is which side of a specific decision is cheaper to be wrong about, worked out case by case rather than applied everywhere as one blanket rule. Creating something is often harder to undo than declining to create it, and when that asymmetry holds, refusing on uncertainty is the boring, correct choice even though it never looks like a feature worth mentioning in anything I would show off. Where the asymmetry runs the other way, the right default runs the other way too, and treating refusal as always the safe answer would just be swapping one lazy default for another.
Nobody writes a release note that says the system successfully declined to do something today, and that is exactly why this gets underbuilt. A feature that lets something happen is visible the moment someone uses it. A feature that quietly stops the wrong thing from happening is invisible by construction, because success looks identical to nothing occurring at all. It is still, on the days that matter, the best thing the system did, and I have started giving the refusal path the same care I give the path that actually lets something through.