Consystence
Start free
Back to blog

Bad data must never look live

A value can be wrong two ways: old, or broken. Screens that collapse both into a last-known-good number train operators to trust a lie. Every value on our wire now carries one of five quality states — and "bad" can never be rendered as live, stored as history, or satisfy a command precondition.

Garry Thomas
ArchitectureSafetyEngineering

A screen shows discharge pressure at 4.2 bar. Steady, plausible, green. The transmitter failed twenty minutes ago. What the operator is reading is the last value the system happened to hold — rendered exactly the way a live reading is rendered, because the display layer was never told the difference. Every controls engineer has a version of this story, and the ending is always the same: the operator stops trusting the screen, or worse, keeps trusting it.

The root cause is a modelling failure, not a display bug. A value can be wrong in two fundamentally different ways — old or broken — and most systems collapse both into “the number I have”. So we made quality a first-class dimension of every value the platform carries, with a vocabulary that refuses the collapse.

Five states, and the one that outranks the rest

Every value moves through the platform with one of five quality states: good, uncertain, cached, stale, or bad. The middle three are shades of confidence — a read the driver flags as questionable, a value served from memory while a fresh one is fetched, a value that was valid but has aged past its welcome.

Bad is different in kind, not degree. Bad means the data is broken — comms to the device lost, the sensor faulted, the read failed — and it carries two hard rules:

  • Bad is never collapsed to good. A falsely-live value is prohibited by design. There is no rendering path, no fallback, no convenience shortcut that presents a faulted reading as a current one.
  • Bad is never collapsed to stale. This is the subtler rule. Stale says this was true and it is old. Bad says this is a fault. Fold bad into stale and a failed transmitter dresses itself as mere latency — an urgent problem wearing a boring costume.

On screen, bad renders as the strongest non-live state a value can have: unmistakably a fault, never a number an operator might act on. The quality descriptor travels on the value itself, over the same wire, to every renderer — the browser and the terminal show the same verdict because neither is allowed to decide for itself. Quality is stamped where the read happens, at the driver, and survives the whole journey up.

What the historian refuses to remember

Quality changes what we store, not just what we show. Bad samples are not written to the historian at all — a broken reading has near-zero value as history, and a trend line faithfully plotting a faulted transmitter is a lie with an x-axis.

But the transition to bad is recorded, as a marker event rather than a sample. That distinction earns its keep months later, when someone asks why a trend has a gap: the record can answer “quality went bad here” versus “the collection chain was down here” — two very different investigations that an undifferentiated gap would leave you guessing between. (Telling those apart is also what the chain-liveness heartbeat exists for; the two designs interlock.)

What a command refuses to do

The rule reaches its sharpest point at the write path. Device commands can carry tag-guarded preconditions — only start the pump if the discharge valve reports closed. A precondition is a safety claim, and a safety claim proven against broken data is not proven at all.

So the contract is fail-closed: a guarded command must prove its precondition against good-quality, live values, and when it cannot — the guard tag is bad, the read is stale, the evidence simply is not there — the command is refused, with the reason stated. We have watched this behaviour do its job on our own workshop rig, refusing commands the moment the quality of the guarding data could not be established. An inconvenient refusal is the feature. The alternative is a pump that starts because a transmitter died at the right moment.

Quality is not provenance

The platform already stamps every sample with its provenance — measured, simulated, imported, and the rest. The two dimensions are deliberately orthogonal: provenance says where a value came from; quality says whether to trust it right now. A measured value can go bad; a simulated one can be perfectly good. Collapsing those axes into one field is how systems end up unable to say “this real sensor is broken” and “this rehearsal is healthy” at the same time.

To be precise about the line:

  • Shipped: the five-state quality model on the value envelope, stamped at the driver and carried to every renderer; bad excluded from the historian with transition markers in its place; fail-closed evaluation of tag-guarded command preconditions.
  • Rolling out: bad is the newest ruling in the set, and it is still working its way through every surface that renders a value — the vocabulary lands everywhere, in order, and we will not call that finished until it is.

A control system earns trust by being honest about its own ignorance. Five words on every value is what that honesty costs — and a screen that says I don’t know is worth more than one that guesses in green.