Every dashboard eventually asks you for a number you cannot honestly produce. Mine asked three times in one day.
The temptation each time was the same, and it is not laziness — it is helpfulness. You can nearly always get the number. You just have to add a credential, or a join key, or accept a proxy that is probably fine. The number appears, the card looks finished, and nobody ever asks again.
I want to argue for the other move: work out what you can actually justify, publish that, and say on the page that it is a bound. In all three cases below the bound turned out to be more useful than the number would have been — and in one of them, the "confident" version I shipped first was actively lying to me.
Case 1: the total that would have been wrong by an unknowable amount
I have an audience spread across four systems: a newsletter list, paying readers, a shop, and people who request access to my CV. The obvious card is "total audience". One big number, top left.
It is not computable. The same person can be on the list, hold a shop account
and have asked for the CV, and nothing in any of those systems knows that. To
know it, I would need a join key — the same person recognisable in all four
databases. The standard trick is a salted hash of the email address: each system
emits sha256(salt + email), and matching hashes mean matching people.
That works. It also copies a stable per-person identifier for every subscriber into a third system whose entire job is to be read by a dashboard. If you are thinking about doing this, run one of your own subscriber lists through a text anonymizer and look at what a "pseudonymous" identifier still lets you do: a stable hash is a primary key with extra steps. It joins across systems, survives forever, and is only anonymous until someone has a list of candidate emails to hash.
So there is no join key, and therefore no total. What I shipped instead is five populations side by side, plus this, in the place a reader would look for the total:
There is no total here, and that is the finding rather than a gap. These populations overlap by an unknown amount, and nothing in this estate can tell by how much.
Then, separately, the overlap I can justify — the cases where a single system knows both sides of the relationship without anyone joining anything:
- The newsletter platform knows how many of its paying readers are not on its own list. That is a real number, computed inside one database.
- The shop knows how many buyers checked out as guests, so they are in the buyer count and not the account count.
- The shop knows how many buyers have more than one order.
Each is a floor for one specific relationship, labelled as a floor, and silent about every other pair. The "paying but not subscribed" number turned out to be the most actionable thing on the page — it is a to-do, not a metric. A fabricated grand total would have buried it under something impressive and wrong.
There is a test in the repo that fails if anyone adds a total field to that
data structure. Not because a future me would be careless, but because the pull
toward that number is strong enough that it deserves a tripwire.
Case 2: the version I would have needed a token to know
Second card, same day: which of my projects are behind on my shared internal packages?
The honest version of this needs the newest published version of each package, which lives in a private registry. Reading it means putting a registry token inside the dashboard container — a credential that can read every private package I publish. For one derived number, in the one container I had spent that morning deliberately narrowing the blast radius of.
I said no, and then a reasonable objection landed: that still seems like useful information — can we get it another way?
Yes, and the reframe is the whole trick. I was solving for the wrong number.
"How far behind is each project?" needs the registry. "Who is behind whom?" does
not — every project's pin is already sitting in a package.json I can read.
So "newest" became the highest pin observed across all consumers. That is a floor: if every project is behind the same release, this cannot tell. It is never wrong about one project being behind another, which is the part I would actually act on.
The first run reproduced a version matrix I had been maintaining by hand in a notes file, which was the moment I stopped missing the registry. It also surfaced something the "real" number would have obscured, because the useful distinction was never the size of the gap:
@snlans/seo newest seen 0.6.0
blog ^0.6.0 current
webshop ^0.5.0 needs a hand-edited pin
tool-sites ^0.3.0 needs a hand-edited pin
cv-site ^0.2.0 needs a hand-edited pin
@snlans/auth newest seen 4.4.0
cv-site ^4.0.0 behind, but its caret floats to 4.4.0
A 0.x caret does not float minors. ^0.3.0 will never accept 0.4.0 —
that is semver's rule for pre-1.0 packages, where the
minor slot carries breaking changes. So those three gaps stay open forever, with
a green build on both sides, until a human edits a string. Meanwhile ^4.0.0
already accepts 4.4.0 and closes itself at the next install.
Sorting those together by "how far behind" would have put a row that fixes itself next to three that never will. The bound gave me a better card than the number would have, because deriving it forced me to ask what the number was for.
Case 3: the log line that was not evidence
The third case is older and it is the one that taught me the rule.
I had a card reporting whether my nightly backups ran. Version one mounted the
backup scripts' log files and looked for each script's success line. It worked.
It also measured the wrong thing: a log line is what a script said, not
evidence that a backup exists. A script that prints wrote backup.enc and then
dies before flushing, a log that gets rotated, an artefact deleted afterwards —
all of them read as healthy.
Version two stats the encrypted artefacts themselves. Newest file's mtime, exact size in bytes, how many rotations deep, written to a plain JSON file by a cron job and mounted read-only — no API, no token, no client library. Those are facts about the world rather than testimony about it. Dumping that file into a JSON formatter and reading it as a human is still the fastest way I know to catch a field that means something other than what you assumed.
Two details from that rewrite have outlived it:
- Exact bytes, never
du -h.dureports allocated blocks, so a 752-byte export displays as4.0Kand a backup silently shrinking toward empty looks perfectly stable. The rounding hid the exact failure mode the card existed to catch. - The offsite push needed a fact no log could carry. The push script prints "pushed" before anything downstream could fail. So the card reads the backup repo's real last commit and how far ahead of its upstream it is. More than zero unpushed commits means every backup is on one disk — which is the failure, and the log said success.
The rule, with nothing to do with my stack
When you cannot compute a quantity honestly, do not fabricate it and do not buy it with a new privilege. Find the tightest bound your existing evidence supports, publish that, and say on the page that it is a bound and in which direction it can be wrong.
The last clause is what makes it work, and it is the one people skip. A floor that is not labelled becomes a number in the reader's head within a week — including when the reader is you, six months later, quoting your own dashboard in a decision. "Newest seen 0.6.0" and "newest is 0.6.0" are different claims, and only one of them survives contact with a package nobody has adopted yet.
The direction matters too. My package card can only be wrong by understating drift. That is a sentence a reader can reason with. "This number might be off" is not.
The failure mode on the other side
There is a mirror image of this, and I shipped it the same week.
A security note in my architecture doc used to end with a sentence claiming the dashboard could not reach a particular thing. It was plausible. It was reasoned from a config flag. It was also wrong, and an audit demonstrated it in about ten minutes by simply making the request.
That is the same error as the fabricated total, run backwards: a bounding claim I had not measured. Both of them assert something the evidence does not carry. The number version flatters your dashboard; the security version flatters your architecture. The security one is worse, because nobody re-reads it.
So the rule has a second half: if you have written down what something cannot do, there is a command that checks. If there is no such command, you have written a hope.
And there is a third failure I hit that neither half covers. When I rebuilt the
backup card, one code path asserted health from the raw reading while another
re-derived it from the stored row — and a ?? in the middle turned "readable,
nothing wrong" into "we could not read this". Both halves were individually
correct. Nothing compared them, so my alerts stayed green while the page a human
reads showed seven failures. If two paths in your system can answer the same
question, something has to assert they agree; otherwise you have two dashboards
and no way to know which one is lying. The fix was a test that runs both paths
over the same inputs and compares status and wording, because the wording is
what a person actually reads.
What I still don't know
Whether the labelling actually survives. Every one of these bounds is
communicated in prose on a card, and prose is the first thing to get skimmed. The
number sits in a big font; the caveat sits in a small one. I suspect the honest
version of this eventually needs the bound in the value — rendering
≥ 0.6.0 rather than 0.6.0 with a paragraph underneath — and I have not done
that yet.
I also do not know where the line is. "Publish the bound" is clearly right when the alternative is a new broad-read credential. It is much less obvious when the alternative is a small, well-scoped one, and I have talked myself into both answers on different days.
The version I trust is this: if buying the number requires a capability you would not otherwise grant, the bound is not a compromise. It is the correct answer, and the number was a bad idea you had not finished evaluating.