@@ -386,10 +386,7 @@ private static <T> ProviderEvaluation<T> resolveVariant(
386386 final ProviderEvaluation <T > result =
387387 ProviderEvaluation .<T >builder ()
388388 .value (mappedValue )
389- .reason (
390- !isEmpty (allocation .rules )
391- ? Reason .TARGETING_MATCH .name ()
392- : !isEmpty (split .shards ) ? Reason .SPLIT .name () : Reason .STATIC .name ())
389+ .reason (resolveReason (allocation , split , flag ))
393390 .variant (variant .key )
394391 .flagMetadata (metadataBuilder .build ())
395392 .build ();
@@ -400,6 +397,25 @@ private static <T> ProviderEvaluation<T> resolveVariant(
400397 return result ;
401398 }
402399
400+ private static String resolveReason (
401+ final Allocation allocation , final Split split , final Flag flag ) {
402+ // ADR-004: SPLIT overrides TARGETING_MATCH when both rules and shard contributed
403+ if (!isEmpty (allocation .rules ) && !isEmpty (split .shards )) {
404+ return Reason .SPLIT .name ();
405+ }
406+ if (!isEmpty (allocation .rules )) {
407+ return Reason .TARGETING_MATCH .name ();
408+ }
409+ if (!isEmpty (split .shards )) {
410+ return Reason .SPLIT .name ();
411+ }
412+ // No rules, no shards (vacuous split). STATIC only when this is the sole allocation
413+ // with no date-window constraints (ADR-003: time-gated result is not permanently stable).
414+ final boolean hasDateWindow = allocation .startAt != null || allocation .endAt != null ;
415+ final boolean isSoleStaticAlloc = flag .allocations .size () == 1 && !hasDateWindow ;
416+ return isSoleStaticAlloc ? Reason .STATIC .name () : Reason .DEFAULT .name ();
417+ }
418+
403419 private static Object resolveAttribute (final String name , final EvaluationContext context ) {
404420 // Special handling for "id" attribute: if not explicitly provided, use targeting key
405421 if ("id" .equals (name ) && !context .keySet ().contains (name )) {
0 commit comments