Coverage and backlog close a third of the cases before any debugging starts.
👋 Hi, I'm Anton - a software engineer working mostly in PHP/Symfony and Go, currently carving a live PHP monolith into Go services.
This series follows one question that keeps coming back from that work: why does this entity have no result? Part 1 counted the empty screens, Part 2 split the path into links with owners. This part is about the two conditions that live outside the system entirely, and what happened when I ignored them. Running notes live on my GitHub: github.com/brilliant-almazov.
This is how I do it right now, with the price attached - maybe you handle it differently, and I'd like to hear how.
The thesis
Part of the empty cases should not be taken apart at all. They are closed by two external conditions, and neither of them is returned by any handle in the system.
Everything below is a consequence of that one sentence.
Factor one: coverage
The provider of the result data does not operate in some regions at all. Not slowly, not partially - it has no coverage there.
That changes what the decision is made from. If the configuration sits in a region the provider does not cover, the answer is settled by the region alone.
There is no point opening the task, reading its counters, or checking whether anything was ever queued. The chain of links has nothing to say here, because the chain never had an input.
In the vertical I took apart, that was 4 configurations - removed from the investigation by one attribute of the configuration, before any measurement.
The uncomfortable part: coverage is not a fact about my system. It is a fact about somebody else's product, and it can change without anyone telling me.
Factor two: the queue
The second condition is the provider's own backlog. A task can be created, accepted, and simply waiting - queued, not lost. From inside, both look identical: no rows, empty result.
What separates them is the status of the task, and there are three of them:
| Task status | What an empty result means here |
|---|---|
| waiting to be queued | the task exists; nothing has been collected yet |
| receiving data | collection is in progress - empty is a point in time, not an outcome |
| completed | collection is over - this is the only status where empty is a question |
An empty result on a task that is receiving data is a wait. There is nothing in it to debug, nothing to file, and nobody to hand it to.
Read as a failure, it produces a defect report against a service that is doing exactly what it should.
┌────────────────────────────────────────────────────────────┐
│ all empty cases │
│ every entity a user reported as having no result │
└────────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ region not covered 4 removed │
│ decided by the configuration's region alone │
└────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────┐
│ queued at the provider 21 removed │
│ a wait, not a loss - collection │
│ is in progress │
└────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────┐
│ worth debugging │
│ the only ones opened link by link │
└────────────────────────────────────┘
neither cut-off is returned by any handle in the system
The one-field trap
Here is the part I got wrong, and it is the reason this article exists.
I reported seven configurations as "the task never reached the provider".
The evidence was a single field: on the row of the keyword query, the field naming the provider was empty. Empty field, no provider, task never sent. It reads like a fact.
Then I ran the same read against a control object - one where the data had already arrived, where everything demonstrably worked. The same field was empty there too. And the task behind it was in status receiving data.
┌──────────────────────────────────┐ ┌──────────────────────────────────┐
│ suspect │ │ control - data already arrived │
│ reported as broken │ │ demonstrably working │
├──────────────────────────────────┤ ├──────────────────────────────────┤
│ key row │ │ key row │
│ provider: - │ │ provider: - │
├──────────────────────────────────┤ ├──────────────────────────────────┤
│ task, one level down │ │ task, one level down │
│ status: receiving data │ │ status: receiving data │
└──────────────────────────────────┘ └──────────────────────────────────┘
7 configurations reported as "the task never reached the provider"
on the evidence of a field that is empty in the working case too
So the field was never the fact. It says nothing about whether a task was sent, because it is empty in the working case as well. The fact was one level away, in the status of the task itself.
The rule that comes out of it is short: read the task status, not the key row.
The cost was not the debugging time. It was a rewritten report and a separate correction in the tracker: the conclusion had already been handed to people who would have gone looking for a defect that did not exist.
A wrong conclusion travels. You have to go and catch it.
The shape of the mistake is worth naming exactly, because it is not "I misread a field". It is: a conclusion drawn from one field, with no control case next to it.
One field can only tell you what it contains. Whether that content means anything is answered by looking at the same field where things work.
Why these conditions can't be derived from the system
Neither factor can be read out of the system at all. Not the provider's coverage, not the depth of its queue, not which type of schedule a configuration is on.
No handle returns any of it, and no amount of careful measurement will produce it, because it isn't there.
That knowledge lives with people. And while it lives only with people, every new breakdown rediscovers it - usually late, usually after a wrong conclusion has been written down.
Hence the practical consequence: such conditions belong in the brief handed to whoever runs the check, as plain text, on the same footing as the shape of the request.
Not as background, not as "ask me if something looks odd" - as a stated condition, next to the endpoint and its parameters. That is the subject of a later part of this series, on what an executor needs before it can test production.
What it does to the numbers
Two cut-offs, applied before any link is read:
- 4 configurations removed by coverage - decided by the region alone.
- 21 configurations moved out of "broken" and into "waiting for collection" - tasks queued at the provider, being collected right now.
What is left after both cut-offs is a different question. Not why is this empty, but when does it arrive. That question has an answer, an owner, and no defect attached to it.
What it costs
Both conditions are external, and that is not a detail - it is the whole price.
I cannot verify either of them with my own code. There is no assertion I can write that fails when the provider adds a region or drains its backlog.
And I cannot keep them current automatically: a note about coverage is only true on the day it was written.
So the honest statement is this: a stale record of coverage is exactly as damaging as no record at all - worse, in one respect, because it looks like knowledge.
It will close cases that should have been opened, and it will do it quietly, in the same voice as the correct record.
I do not have a solution to that. The best I have is keeping the record small enough that its age is obvious, and writing down when it was last confirmed.
The part that generalises
An executor of any speed - a teammate, a new hire, an automated one - will fill in a missing condition on its own.
Not out of carelessness: an analysis needs the condition to reach a conclusion, so if it isn't supplied, it gets inferred from whatever field is closest to hand. That is precisely how "the task never reached the provider" was produced.
So the arithmetic is simple. Writing the condition down once costs a sentence. Unpicking a conclusion built on its absence costs a report, a correction, and other people's time spent looking for a defect that was never there.
Speed doesn't change that balance. It just makes the second outcome arrive sooner.
This is my experience and my price, on one vertical, in one system halfway through a migration.
If you do this better, tell me where external conditions like these live in your setup and how you keep them from going stale. If you have been through this, tell me which single field turned out to be lying. If you see it differently, say where reading one field without a control case is actually good enough.
How is this solved on your side, and what broke there?
Why there is no result - Part 3.
Next: processing and completeness are two different aspects and deserve two different verdicts - what a service computes on the data it has, versus whether the data arrives at all.



Top comments (0)