Seventeen weeks marked asked, and nobody was asked
Our availability nudge stamped a week as asked before checking it could send. With no text provider, every week burned quietly, seventeen on the live venue.
Every minute, a background job in our worker looks for staff whose availability for an upcoming week has not been collected, and once per person per week it sends a text asking for it. The once is important. Nobody wants the same question every minute for twelve hours, so the job stamps the row before it sends, and the stamp is checked first on every later pass. The file says so in its own notes: a text that fails is better than one that repeats.
That rule is right. The bug was one line in the wrong order underneath it.
Stamp, then check whether you can send
The loop stamped the week as asked, and then checked whether a text could go out at all: whether the venue had a text provider configured and whether the person had a phone number. If either was missing, it counted a skip and moved on. The stamp was already down. That person would never be asked about that week again, by anybody, because the first line of the loop skips any row that carries a stamp.
The venue in question has no text provider configured yet. The credentials are still outstanding. So every week the job ran, it burned every pending week for every person and sent nothing, and it had been doing so since the middle of September.
Read from production, not reasoned
The availability table on the live system held seventeen rows, and all seventeen carried the stamp. The first was from the thirteenth of September. The most recent was from twelve minutes past noon on the day it was found, seven hours earlier. The message log, which every channel writes to, held zero rows. Seventeen weeks marked as asked. Nobody asked.
Worse than the seventeen is what would have happened when the credentials arrived. From that day forward the feature would have looked like it worked, because only weeks stamped after that day could still fire, and nobody would have known that a month of availability was never requested.
The distinction the fix rests on
The existing rule is about a send that was attempted and failed. A text can fail after it leaves, and stamping first means it is not retried every minute. The missing-provider case is a different thing: no attempt was made and none could be, so there is nothing a repeat would cost. The job now reaches the question of whether anyone is due and stops there when it cannot send, writing nothing. The stamp still precedes every real send.
Two tests failed on the change, and both were using the stamp as their way of observing that somebody was due. That conflated two facts: the job decided this person is due, and this person was asked. The return value already separates them, so the test for the horizon bug now asserts on the skip count, which tells not-due from due-but-unsendable exactly as the stamp used to. The test for the stamp rule itself now runs with credentials present and the outbound call stubbed, which are the only conditions under which that rule applies. It covers the send path properly for the first time.
A prediction, and the seventeen
The job runs at eleven minutes past each hour. At the deploy, the live table read seventeen rows, seventeen stamped, with the last stamp at twelve past noon. While no provider is configured, none of those three numbers may move again. If the count of stamped rows ever grows, the fix did not reach production. The next pass checked, after the next tick, and all three were unchanged.
The seventeen stay stamped. Clearing them is a write to live data whose effect depends on whether each week's window is still open; most would simply never fire, and any that did would arrive late, which the file itself describes as a telling-off. Nobody is harmed by leaving them. The cost is seventeen weeks of availability that were never actually requested, and whether to ask for them now is the owner's call, not something a background job should decide.