↓Skip to main content
  1. Posts/
Case study

Obvious Rules Are Exactly the Ones That Need Clarifying

·8 mins
A straight path above a network of hidden branches and conditions

The most dangerous conditions are often the ones everyone thinks they understand.

“When all tasks are done, notify the process owner.”

In a meeting, that sentence needs no explanation. Everyone has a rough idea of what “all” means, what “done” means, and who should be notified. That is exactly why it sounds like a requirement that can be turned into a few lines of code without much thought.

Software, however, cannot work with what a sentence “roughly means.” It needs to know exactly which tasks count, which version of the process they belong to, whose responsibility matters, which states mean the work is finished, and what should happen if a completed task is reopened.

A simple notification thus became a useful example of a condition that seemed clear right up until it had to be defined precisely.

This article is based on a real implementation. Identifying information and nonessential operational details have been deliberately generalized.

“All” is a set that has to be defined first #

The application works with a process record that can generate tasks for multiple users. It also supports revisions: if the requirements change during the process, a new revision is created and new tasks belong to that revision.

That immediately changes the meaning of the word “all.”

Tasks from an older revision remain in the history, but they must not influence decisions about the current state of the work. When a new revision is created, unfinished tasks from the previous revision are closed by the system, and the new revision gets its own set of tasks.

The check therefore cannot simply scan every task associated with the process record. It has to work only with tasks from the current revision.

That is the first clarification of a sentence that previously “everyone understood.”

Even “all tasks” did not mean every task #

The purpose of the notification was specific: the process owner should receive a message when they were no longer waiting for other people to finish their work and could move on to the next step.

Tasks for which the process owner was responsible therefore had to be excluded from this condition.

That distinction matters.

The system was not supposed to ask:

Are all tasks done?

It was supposed to ask:

Is there still an open task for which someone other than the process owner is responsible?

Those two questions sound similar, but they are not the same.

The process could still contain open tasks owned by the process owner and the notification could still be correct. Its meaning was not “the entire process is complete,” but “you are no longer waiting for anyone else.”

The responsible person is not the same as a solver #

The next clarification came directly from the task model.

A task had one responsible user, but it could have several solvers. The process owner could therefore appear as one of the solvers even though someone else was responsible for completing the task.

If the rule looked only at the list of participants, it could incorrectly exclude such a task.

The decisive question was therefore not “is the owner also working on this task?” but “who is responsible for this task?”

If the owner was only a solver and another user was responsible, the task still counted.

This is another typical property of seemingly obvious rules: natural language uses phrases such as “my task,” “their task,” or “they are working on it,” while the data model has to distinguish precisely between participation and responsibility.

“Done” is not a single state either #

Then came the word “done.”

The usual terminal state was a successfully completed task. The process also allowed a task to be explicitly rejected by a user.

Those outcomes are different from a business perspective, but from the perspective of waiting for further work they share one property: the task no longer requires processing.

The check therefore could not be based on a simple condition such as:

state = done

It had to work with the meaning of the states:

the task is no longer active

There was also a system-closed state. It was used, for example, when a new revision was created and unfinished tasks from the older revision were no longer supposed to continue.

Such a closure does not mean that a user completed the work. It only means that the task lost its relevance in the new process context.

That is why all terminal states could not automatically be treated as equivalent.

The task state alone was not enough. What also mattered was why the task entered that state and which revision it belonged to.

Even zero open tasks can be the wrong answer #

Then came a case that is technically subtle but illustrates the difference between program logic and process meaning very well.

Imagine that, in the current revision, there is no task for which anyone other than the process owner is responsible.

The number of open relevant tasks is zero.

Formally, one could say that all relevant tasks are complete. After all, there is no task that is not complete.

But an email saying that “everyone else has finished their work” would make no sense. There was no work by anyone else in the first place.

The rule therefore needed one more condition: at least one relevant task must exist.

It is a small detail, but a good example of why a mathematically true condition is not necessarily a business-true message.

State is not enough. The event matters too #

Even a precise definition of the set and the terminal states was still not enough.

It was also necessary to decide when the condition should be evaluated.

One option would be to scan the database periodically and look for processes where all relevant tasks were complete. Without additional tracking, however, the system could keep sending the same notification repeatedly.

In this case, it made more sense to evaluate the condition when a task actually transitioned into a relevant terminal state.

When a task was closed, the system checked all relevant tasks in the current revision. If none remained open, the process owner received the notification.

But even here, a user event had to be distinguished from a technical event.

If the system automatically closed a task because, for example, a new revision had been created, that was not the moment when someone finished their work. Such a transition therefore should not trigger the same notification.

Once again, the resulting state alone was not enough. The path by which the system arrived there also carried meaning.

“Done” does not necessarily stay done #

In process systems, a condition that becomes true once does not necessarily remain true forever.

A completed task can be reopened. Another relevant task can be created later.

At that point, the statement “everyone else is done” stops being true again.

If the last open task is then closed once more, the notification becomes meaningful again.

That is why it was not appropriate to think of a state such as:

all_tasks_completed = true

as a one-time, permanent process flag.

“All work is done” is a derived property of the current data. It can be true for a while, become false, and later become true again.

That is another reason to treat business shorthand like this as a rule rather than as a simple stored flag.

One sentence became a precise rule #

After removing the concrete names of states and entities, the resulting logic can be summarized roughly like this:

relevant tasks =
    tasks in the current revision
    for which someone other than the process owner is responsible

other people's work is complete =
    at least one relevant task exists
    and every relevant task is in an accepted terminal state

notify the process owner =
    a task has just genuinely transitioned into a relevant terminal state
    and other people's work is complete
    and the process itself is not already closed or rejected

Only at this point is the original requirement precise enough to implement and test reliably.

And yet the business requirement itself has not changed.

It is still the same sentence:

When everyone else is done, notify the owner.

The only difference is that the meaning of “everyone,” “done,” and “when” is no longer left to intuition.

Process synchronization is business logic #

This kind of rule is not specific to one application.

It appears wherever several activities run in parallel and the next step depends on their completion: order approvals, employee onboarding, production preparation, documentation checks, or complaint handling.

The BPMN 2.0 standard treats the synchronization of parallel branches as an explicit part of the process model. A parallel gateway, for example, does not continue because it intuitively “looks like everything is probably done”; it waits for the relevant incoming branches according to defined semantics.

In a business application, the situation is often even more complicated because the set of activities does not have to be static. Tasks can be added, move between revisions, become irrelevant, or be reopened.

That is why storing the outcome of such a rule as a simple flag and trusting it later can be dangerous. A more reliable approach is to define the condition precisely and derive it from the current data when needed.

The obvious sentences are often the hardest ones #

The database check that asks whether any relevant open task remains is not technically exceptional.

The harder part was deciding what counts as a relevant task, what a terminal state means, whose responsibility matters, and at which event the result becomes meaningful.

That is exactly why conditions that “everyone understands” can be dangerous.

Nobody naturally asks for definitions because they appear clear enough to implement immediately.

Software eventually forces every unstated assumption to be made explicit.

“All tasks are done” is therefore not one condition.

It is shorthand for an entire business rule. The code should come only after it is clear what that shorthand actually means.

Sources and Further Reading #