Skip to content
Mohit Sharma
Engineering notes

Seven predicates for one question

Four screens showed an aircraft's status and three of them were wrong. The fix was not to pick the best of the seven definitions.

Note
002
Published
Reading
5 min
Part
2 of 3

The question

Ground an aircraft, then look at it on four different screens. One of them said it was grounded. The other three said Serviceable.

The data was not corrupt. Every screen was reading real values out of the real database and rendering them correctly. They simply did not agree about what the question meant.

What I thought was happening

I assumed this was a caching problem, or a refresh problem. Something had written the status and three readers had not noticed yet. That is the ordinary shape of "the screens disagree", and it has an ordinary fix.

Underneath that was a more basic assumption: that "is this aircraft available" had an answer, stored somewhere, and the job was making sure everyone read it.

What I found

There was no single answer to read. The audit counted seven mutually incompatible predicates for "is this aircraft available", scattered across the surfaces that needed to ask.

They were not copies of one rule that had drifted apart. They were seven different rules, each written at the moment some screen needed to know, each correct for the case its author had in front of them, and each subtly different about the cases its author was not thinking about.

Exactly one surface computed it correctly, a deriveDisplayStatus function behind the aircraft summary endpoint. And here is the detail that made the whole thing legible: that surface was also the only one displaying the status correctly. The correct derivation and the correct display were the same place, because the derivation had been written for that display and had never left it.

Two more findings came with it. Six write paths bypassed the transition() function that was supposed to be the choke point for status changes. And the aircraft state machine, along with the guard that was meant to stop an aircraft with an open grounding order being dispatched, was dead code. It existed, it was correct, and nothing called it.

That last one is worth sitting with. We had a state machine enforcing the rule. It had simply been routed around, one write path at a time, by people who needed to set a status and found a more direct way to do it.

The wrong fix I nearly made

The obvious repair is to add the missing states to the stored enum. If the enum had reserved, in_flight, pre_flight and the rest, every screen could read one field and agree.

I got some way down that road before noticing it makes the disease worse. Every value added to a stored enum is another thing that has to be written at the right moment by every code path that could cause it. Adding in_flight means every departure path must set it and every arrival path must clear it, and a missed clear leaves an aircraft permanently airborne. The seven predicates existed because writing state correctly from many places is hard; the answer to that is not more state to write from more places.

The disease is drift, not vocabulary.

The decision

Split the question in two, along the line of what is durable.

The stored aircraft_status enum stays, with its five values, and its meaning is narrowed to the durable reason the aircraft cannot fly: serviceable, aog, maintenance, parked, leased_out. That is a fact about the aircraft that outlives any particular flight, and it is genuinely written by a human decision.

The transient operational state, Reserved, In Flight, Pre-flight, Awaiting Post-flight, Awaiting Approval, is not stored at all. It is derived, by one server-side function, from the records that already exist. Every surface calls that function. And all status writes route through transition(), which is now the only way in.

The property that matters: one derivation cannot disagree with itself. Seven predicates can drift because they are seven things. A function is one thing, and if it is wrong it is wrong everywhere at once, which is a much easier failure to notice and a much easier one to fix.

What it cost

Every surface now has to call the shared function, and a list query needs a join it did not need before. That is a real cost on the list endpoints and it is the reason the derivation is cached.

Caching then brings back a smaller version of the original problem, staleness, which has to be invalidated on the aircraft tag. I would rather have one cache with one invalidation rule than seven predicates, but it is not nothing, and it is the part most likely to bite later.

And one thing the derivation deliberately does not solve. in_flight still needs a hard uniqueness constraint in the database, independent of any derivation, because the audit proved live that two flights could be airborne on the same tail. A derived view of the world tells you what the records say. It cannot stop the records saying something impossible. That needs a constraint, at the level where constraints are enforced.

The model that made it click

If a fact can be computed from records you already have, computing it is safer than storing it, because stored state has to be written correctly by every path that could change it, and derived state has to be written correctly by nobody.

The corollary is the useful part. When you find several incompatible definitions of one concept, the fix is not to choose the best one and propagate it. Propagating it creates the eighth. The fix is to make it impossible for there to be more than one, which usually means making it a function rather than a field.

What I would remember in six months

Counting is diagnostic. "How many places in this codebase answer this question?" is a cheap query and the answer is often startling. Seven was not a number anyone would have guessed, including the people who wrote all seven.

And when you find a state machine or a guard that is correct but dead, do not just wire it back up. Ask how it came to be bypassed, because six independent write paths did not appear by accident. They appeared because the choke point was harder to use than going around it, and if that is still true after you reconnect it, it will happen again.

  • 2026.07.25

    The defect owns the finding

    A pilot who found a fault mid-inspection could abandon the checklist or lie. Fixing that meant resisting the obvious second blocker.

    4 minSystem designBackend

  • 2026.08.29

    The interface lied, so the catch never fired

    One missing file took the whole API down, past five correctly written try/catch blocks, because the driver could not fail where they were looking.

    4 minBackendFailure modes