I added a default value to a database function specifically so a deploy could not break. Then I probed production and found the default had never once applied.
The feature was small and honest: show the average solve time on the games stats tile. That tile could only ever render a dash, because the time was always null -- every board submitted its result with timeMs: null and always had. The number wasn't cosmetic either. The daily leaderboard ranks by guesses first, then by solve time as the tiebreak, so the time had been sitting there inert, deciding nothing, for as long as the board existed.
Why the server holds the stopwatch
I couldn't just let the client send its own time. A client-supplied duration means anyone willing to POST time_ms: 1 owns the top of the leaderboard forever. So the server starts the clock on your first accepted guess and derives the time itself, ignoring whatever the client says. A tab left open overnight would otherwise log an eight-hour solve and poison the average, so runs over six hours record null -- abandoned, not solved.
The submit function needed a new signature to do this. Changing a live function's signature is dangerous: for the window between the migration running and the new code deploying, the database and the shipped code disagree, and every submission in that gap fails. The textbook fix is to keep the old parameter and give it a default, so both the old call shape and the new one resolve to the same function -- safe to run before or after the deploy. I did exactly that.
The safety net had a hole
After the migration ran I probed prod, calling the function the way the new code would: with only the six keys that actually matter, letting the default cover the seventh. It returned PGRST202 -- no such function. PostgREST resolves an overloaded function by the exact set of keys you POST. If you don't send the parameter, it doesn't fall back to the default; it looks for a function with that precise key set, doesn't find one, and fails. The default never gets a chance to apply.
Which means the safety net wasn't a safety net. Had I merged on the strength of it, every single result submission on the live site would have broken -- the exact failure the default was added to prevent. The fix was almost silly: have the callers pass the parameter explicitly as null. The server still ignores it and still holds the stopwatch, so the client still has no say. But now the key sets match.
I kept the parameter in the function anyway, defaulted, to drop later once the old code is fully gone. Belt and suspenders, except I now know the suspenders were decorative. The belt is the callers sending the right keys.

