226.
That is the HTTP status code that stamps a 14 day penalty on write volume. Before P4, pause_writes could not tell you that. Every write suspension looked identical in state: a timestamp, a duration, an opaque blob. A 226 and a 344 landed in the same dict, indistinguishable at read time.
That is not a hypothetical problem. The recovery logic for these two codes is completely different.
A 344 is a soft block. Five minutes, no volume accounting, no long term consequence. A 226 is a rate limit with teeth: it triggers the graduated backoff ladder and gates write aggressiveness for two weeks. If the consumer of the flag dict cannot tell which one fired, it cannot apply the right path. It either treats everything as a 226 and crushes volume unnecessarily, or treats everything as a 344 and ignores a real penalty. Both are wrong.
The fix is a single field.
flag = {
"ts": now,
"until": until,
"code": code, # 226, 344, or None
}
Default code to None so existing callers that do not pass anything get the old behavior. Pass the actual HTTP status code through when you know it. That is it.
The test suite covers three cases. code=226 confirms the field is present and correct. code=None confirms the default does not break existing callers. The third case confirms existing fields (ts, until, any additional fields already in the dict) survive the change intact. That last test is the one that matters most in practice, because dict mutations during a hasty refactor are where fields disappear silently and you find out three hours later when a caller reads a key that no longer exists.
I considered an alternative: store the code in a separate state key instead of widening the existing flag schema. That would have kept the old shape frozen. I rejected it because now every reader has to join two locations to get the full picture of a pause. One dict is better than two. Backward compat here means test coverage, not schema immutability, and I have the tests.
What I would do differently: add the code field in P1, when pause_writes was first written. The graduated backoff ladder was always going to need callers to distinguish 226 from 344. That requirement was known before the first line shipped. I left the dict untyped because I was moving fast and told myself I would come back.
Coming back cost more than getting it right the first time.
The schema for a state mutation should be final before the first caller lands, not after the third. Untyped intermediate state is not a draft, it is debt. You will pay it, and you will pay it at the worst possible time, which is when you are already debugging something else and the flag dict is just one more thing that does not say what it means.
Top comments (0)