Skip to content

Unknown & Three-Valued Logic

Why there is a third value

Most rule languages only know true and false. Atfinity adds a third value: unknown.

A condition is unknown when Atfinity does not yet have the information it needs to decide it. For example, the condition p.is_pep = true is neither true nor false as long as nobody has answered whether the person is a politically exposed person - it is unknown.

This matters because a rule does not just switch something on or off. Its three possible outcomes drive three different behaviors:

Result What Atfinity does
true The rule applies - the document, section or field is required.
false The rule does not apply - the attached item is left out.
unknown The outcome still depends on missing information - Atfinity keeps the item pending and asks for the data it needs.

So unknown is what makes Atfinity ask the next question: it is the engine's way of saying "I cannot decide this yet, give me more information."

Internally unknown is represented as a missing/None value, but conceptually it always means the same thing: not known yet.

The idea: stay decisive, only stay unknown when it matters

Atfinity follows Kleene three-valued logic. The guiding principle is:

If the final result is already determined by the values we do know, return that result. Only fall back to unknown when the missing information could still change the outcome.

This keeps rules decisive: a rule resolves to a definite true or false whenever the values we already have settle the matter, instead of staying unknown (and pending) when the missing data could not have changed the result anyway.

A worked example

p is Person
p.is_pep = true
and
p.lives_in_sanctioned_country = true

Suppose we already know the person is not a PEP (so p.is_pep = true evaluates to false), but we have not yet asked whether they live in a sanctioned country (unknown).

  • false and unknown -> false. Because an and needs both sides to be true, a single false already settles it - the unknown side cannot change a false and. The rule resolves to a definite false, so the document or section it is attached to is left out, instead of staying pending.

The mirror case holds for or:

p.is_pep = true
or
p.lives_in_sanctioned_country = true

If we already know the person is a PEP (true), then true or unknown -> true regardless of the country, so the rule applies straight away instead of waiting on the missing answer.

Truth tables

and - false wins, because one false is enough to make the whole expression false:

and true false unknown
true true false unknown
false false false false
unknown unknown false unknown

or - true wins, because one true is enough to make the whole expression true:

or true false unknown
true true true true
false true false unknown
unknown true unknown unknown

not:

value not
true false
false true
unknown unknown

Comparisons spread the unknown

For comparison operators (=, !=, <, <=, >, >=) there is never enough information to decide an outcome when one side is missing. So if either side of a comparison is unknown, the whole comparison becomes unknown:

p.age >= 18

If p.age has not been provided, this is unknown (not false) - which is why Atfinity asks for the age instead of silently treating the rule as not applicable.

Checking for unknown on purpose

When you specifically want to react to missing information, use the dedicated operators rather than a comparison:

These always return true or false, never unknown.

and then / or else are different on purpose

The strict and and or evaluate both sides, so they only stay unknown when the missing side could still change the result.

Their counterparts and then and or else deliberately look at the left side first and only evaluate the right side when the left does not already settle the question. Use them when you do not want to ask the second question before the first has been answered.