Evaluation Semantics
This section defines how the SAPL engine evaluates expressions, with particular attention to evaluation order, short-circuit behavior, and how cost strata interact with streaming attribute subscriptions.
For how individual policies and policy sets map evaluation results to decision values (PERMIT, DENY, SUSPEND, NOT_APPLICABLE, INDETERMINATE), see Policy Structure and Policy Sets.
Cost-Stratified Short-Circuit Evaluation
All AND and OR operators (&, &&, |, ||) use cost-stratified short-circuit evaluation. The compiler flattens chains of AND/OR operators into N-ary operations. For example, a && b && c && d is compiled into a single conjunction rather than a chain of nested binary operations. This enables the engine to sort all operands by cost stratum, regardless of how many there are. If any operand in a lower (cheaper) stratum short-circuits the result, all operands in higher (more expensive) strata are never evaluated and their subscriptions are never created.
Within the streaming stratum, the two operator forms differ in their subscription strategy. &&/|| (lazy) subscribe to attribute sources sequentially, while &/| (eager) subscribe to all sources in parallel. See Lazy vs Eager for details.
The Three Strata
SAPL categorizes expressions into three strata based on their evaluation cost:
- Constants (e.g.,
true,false,1 + 2): Evaluated at compile time. This stratum also includes PDP variables: values configured by the operator in the PDP configuration are known when policies are loaded and are automatically constant-folded into this stratum. A condition referencing a PDP variable (e.g., comparing against a configured tenant name or feature flag) is as cheap to evaluate as a literaltrueorfalse. - Pure expressions (e.g.,
subject.isActive,resource.type): Evaluated at runtime without external subscriptions. This includes the four authorization subscription fields (subject,resource,action,environment), which are only known when a concrete subscription arrives. - Streaming expressions (e.g.,
<pip.sensor>,subject.<geo.location>): Require asynchronous subscription to external data sources
Evaluation Rules
-
Cross-strata ordering: Lower (cheaper) strata are always evaluated before higher (more expensive) strata, regardless of operand position in the source.
-
Within-strata ordering: Within the same stratum, operands are evaluated strictly left-to-right as they appear in the source.
-
Short-circuit behavior: Only the dominating value short-circuits:
falsefor AND,truefor OR. When a dominating value is found, evaluation stops and the remaining operands are not evaluated. An error or undefined operand does not short-circuit, so it never lets the engine skip the remaining operands or their subscriptions. -
Kleene three-valued logic: Boolean operators follow Kleene strong three-valued logic. An operand that is not
trueorfalse, an error,undefined, or any other non-boolean value, acts as a third value, unknown. Errors and undefined are treated alike. AND isfalseif any operand isfalse, otherwise unknown if any operand is unknown, otherwisetrue. OR istrueif any operand istrue, otherwise unknown if any operand is unknown, otherwisefalse. Only when no operand carries the dominating value does an unknown operand determine the result, in which case the operator yields an error (the original error, or a type-mismatch error forundefinedor another non-boolean). Because the dominating value wins regardless of operand position or stratum, the result does not depend on evaluation order: a dominatingfalse(AND) ortrue(OR) in any stratum rescues an unknown in any other.
Examples
Constant short-circuits subscription access
subject.isActive && false
Since false is a constant (lower stratum) that determines the AND result, subject.isActive (higher stratum) is never evaluated. This is equivalent to just false.
Subscription access short-circuits attribute finder
subject.isAdmin || <pip.externalAuthCheck>
If subject.isAdmin is true, the attribute finder <pip.externalAuthCheck> is never subscribed to. The external system is never contacted.
Operand position does not matter for cross-strata
<pip.sensor> && false
Even though <pip.sensor> appears on the left, the constant false is evaluated first. The attribute stream is never subscribed to. This may be surprising if you expect strict left-to-right evaluation as in imperative programming languages.
Left-to-right within the same stratum
true || (1/0 > 0)
Both operands are constants (same stratum). Left-to-right order applies: true is evaluated first and short-circuits. The division by zero is never evaluated, so no error occurs.
(1/0 > 0) || true
Again both are constants, but now the error-producing expression comes first. Under Kleene logic the error does not short-circuit, and the dominating true wins regardless of position, so the result is true. The division-by-zero error never surfaces, because a dominating value is present.
A dominating value rescues an error in any stratum
subject.isActive || (1/0 > 0)
Here subject.isActive is a pure expression (higher stratum) and 1/0 > 0 is a constant (lower stratum) that produces an error. The error does not short-circuit, so subject.isActive is still evaluated. If it is true, the dominating true wins and the result is true: the pure expression rescues the constant error. Only if subject.isActive is false, leaving no dominating value for the OR, does the error become the result.
Implications for Policy Authors
Why this matters for attribute finders: Attribute finders subscribe to external data sources. Skipping their evaluation when unnecessary avoids unnecessary network calls, reduces latency, and prevents side effects from unused subscriptions. This is particularly valuable when combining quick checks with expensive external lookups.
Constant errors do not by themselves determine the result: Constant expressions that produce errors (like 1/0) are evaluated at compile time, but under Kleene logic a constant error is only the third value unknown. It is carried alongside the expression, and a dominating value in any operand or stratum still wins. The constant error becomes the result only when no operand carries the dominating value (no false in an AND, no true in an OR). To avoid an error result, ensure a dominating operand is reachable, or guard the constant with conditional logic.
Body Condition Evaluation
Each semicolon-terminated statement in a policy body is an operand of an implicit conjunction. The body is equivalent to connecting all its conditions with && (lazy AND). The compiler flattens them into a single N-ary AND operation, exactly like an explicit a && b && c expression. This means body conditions participate fully in cost-stratified short-circuit evaluation: all conditions are sorted by cost stratum, and if any condition in a cheaper stratum evaluates to false, conditions in more expensive strata are never evaluated and their subscriptions are never created. On the streaming stratum, body conditions use the lazy (resource-optimized) subscription strategy. To use eager (latency-optimized) evaluation, combine conditions explicitly using & within a single expression.
Combined with the recommended condition ordering (fast local checks first, PIP lookups later), this ensures that expensive external calls are avoided whenever possible.