Evaluation Model
This page explains how Konditional chooses a value when multiple rules exist.
Evaluation order
When you call feature.evaluate(context):
- Rules are sorted by specificity (highest first).
- Each rule is checked against the Context.
- If a rule matches, ramp-up is applied (if configured).
- The first matching rule that passes ramp-up wins.
- If nothing matches, the default value is returned.
Specificity system
Specificity is the sum of targeting constraints and custom predicate specificity.
Base targeting specificity (0-3+):
locales(...)adds 1 if non-emptyplatforms(...)adds 1 if non-emptyversions { ... }adds 1 if boundedaxis(...)adds 1 per axis constraint
Custom predicate specificity:
-
A custom
Predicatecan define its ownspecificity() -
Default predicate specificity is 1
-
Guarantee: More specific rules are evaluated before less specific rules.
-
Mechanism: Rules are sorted by
rule.specificity()in descending order before evaluation. -
Boundary: Ramp-up percentage does not affect specificity.
Deterministic ramp-ups
Ramp-ups are deterministic and reproducible.
-
Guarantee: The same
(stableId, featureKey, salt)always yields the same bucket assignment. -
Mechanism:
- Hash the UTF-8 bytes of
"$salt:$featureKey:${stableId.hexId.id}"with SHA-256. - Convert the first 4 bytes to an unsigned 32-bit integer.
- Bucket =
hash % 10_000(range[0, 9999]). - Threshold =
(rampUp.value * 100.0).roundToInt()(basis points). - In ramp-up if
bucket < threshold.
- Boundary: Changing
stableId,featureKey, orsaltchanges the bucket assignment.
Example
object AppFeatures : Namespace("app") {
val checkout by string<Context>(default = "v1") {
rule("v3") { platforms(Platform.IOS); versions { min(3, 0, 0) } } // specificity 2
rule("v2") { platforms(Platform.IOS) } // specificity 1
rule("v1") { always() } // specificity 0
}
}
For an iOS user on version 3.1.0, the v3 rule is evaluated first and wins if it matches.