Field note

Teaching a system to say "I don't know"

The most dangerous thing a dashboard can do is answer confidently with stale data. How I taught my tools to admit when they've gone cold, and why I'd rather they say nothing than guess.

A dashboard that's down is annoying. A dashboard that's up and quietly wrong is a trap. The first you distrust on sight; the second you act on. I learned this the embarrassing way once, on a status command that printed the name of a component it hadn't actually been running for weeks: the number was confident, the source was stale, and every decision I made on top of it inherited the lie.

So when I wired my local tools together into one read-only view (a hub that federates state from a handful of separate little services) the failure I designed against wasn't a source going down. That case is loud; you notice immediately. The one that keeps me up is a source that keeps answering after it stopped updating. Same shape of answer, same green checkmark, two days old.

A number with no freshness attached to it isn't data. It's a rumor that happens to be formatted like data.

Two mechanisms do almost all the work of keeping that hub honest.

Contracts as code

Every source declares what it provides and in what shape, as code, checked at the boundary, not as an assumption living in my head about "what that tool returns." It sounds bureaucratic and it is the opposite. The point is that when a source drifts (renames a field, changes a unit, starts returning a slightly different shape after some update) that drift gets caught right there, at the seam, as a contract violation. Not silently absorbed and surfaced three screens later as a wrong figure I'd have trusted.

A contract checked in code is a promise that can't quietly rot. The alternative, a promise that only lives in the integration working today, rots the first time anything on either side changes, and you find out by being wrong in public.

Freshness envelopes

The second mechanism: every answer the hub federates carries its own age, and a sense of how old is too old. A freshness envelope. The hub doesn't just ask a source "what's the value"; it knows when that value was last true, and it has a threshold past which it stops treating the source as current.

Cross that line and the source is marked degraded, out loud. The view doesn't keep serving the last good number with a straight face. It says, plainly, "this is stale," and lets me decide what to do with that, instead of deciding for me by omission. The system is allowed to admit it doesn't know right now.

Three states, all honest. The middle one is the whole game; it's the case most systems collapse into the first one, and that collapse is exactly where the quiet lies live.

Why this buys trust

The payoff is that I can believe the green. When the hub says a thing is fresh, it's fresh, because the not-fresh case has a different, visible shape that I'd have seen. Trust doesn't come from the system never failing. It comes from the failure mode being honest.

I'd rather a tool tell me "I don't know" than guess. Honesty beats uptime, every time it actually counts.

I'll be straight about the limit, because overselling freshness would be its own kind of stale-status-line. Freshness is necessary, not sufficient. Data can be perfectly current and still wrong for a dozen reasons this never touches: a bad upstream, a logic bug, a misread. All these two mechanisms kill is one specific, common, nasty class: the confidently-wrong-because-stale answer. That class is worth killing on its own. It's the one that turns a tool you built to help you into a tool that misleads you, and smiles while it does it.

Adjacent reading

← Field notes · RSS