1717from audit .models import AuditLog
1818from audit .related_object_type import RelatedObjectType
1919from core .dataclasses import AuthorData
20+ from environments .tasks import rebuild_environment_document
2021from experimentation .constants import (
2122 CONTROL_VARIANT_KEY ,
2223 EXPERIMENT_FLAG ,
5758from features .models import FeatureState
5859from features .value_types import BOOLEAN , INTEGER , STRING
5960from features .versioning .dataclasses import FlagChangeSet
60- from features .versioning .versioning_service import update_flag
61+ from features .versioning .versioning_service import (
62+ update_flag ,
63+ update_multivariate_values ,
64+ )
6165from integrations .flagsmith .client import get_openfeature_client
6266from segments .models import Condition , Segment , SegmentRule
6367
@@ -574,6 +578,58 @@ def _sync_rollout_segment(experiment: Experiment, rollout_percentage: float) ->
574578 return segment
575579
576580
581+ def _get_live_rollout_override (experiment : Experiment ) -> FeatureState | None :
582+ return (
583+ FeatureState .objects .get_live_feature_states (
584+ environment = experiment .environment ,
585+ additional_filters = Q (
586+ feature_segment__segment_id = experiment .rollout_segment_id ,
587+ identity__isnull = True ,
588+ ),
589+ feature_id = experiment .feature_id ,
590+ )
591+ .order_by ("-id" )
592+ .first ()
593+ )
594+
595+
596+ def _update_live_feature_state (
597+ feature_state : FeatureState , change_set : FlagChangeSet
598+ ) -> None :
599+ feature_state .enabled = change_set .enabled
600+ feature_state .save ()
601+ feature_state .feature_state_value .set_value (
602+ change_set .feature_state_value , change_set .type_
603+ )
604+ feature_state .feature_state_value .save ()
605+ update_multivariate_values (feature_state , change_set .multivariate_values )
606+
607+
608+ def _update_rollout_in_place (experiment : Experiment , change_set : FlagChangeSet ) -> None :
609+ """Write the rollout-segment override, keeping variant assignment stable.
610+
611+ Under v2 versioning, ``update_flag`` clones the override into a fresh feature
612+ state on every call. Since the multivariate split is salted on the feature
613+ state id, that would re-randomise control/variant for already-enrolled
614+ identities on each rollout update. Once the override exists, mutate it in
615+ place instead and rebuild the environment document by hand (no version is
616+ published). Creating the override, and v1 versioning, still go through
617+ ``update_flag``, which already reuses the feature state.
618+
619+ This is a temporary solution until we find a permanent fix for the
620+ underlying salting issue: https://github.com/Flagsmith/flagsmith/issues/7913
621+ """
622+ if experiment .environment .use_v2_feature_versioning and (
623+ override := _get_live_rollout_override (experiment )
624+ ):
625+ _update_live_feature_state (override , change_set )
626+ rebuild_environment_document .delay (
627+ kwargs = {"environment_id" : experiment .environment_id }
628+ )
629+ return
630+ update_flag (experiment .environment , experiment .feature , change_set )
631+
632+
577633def apply_experiment_rollout (experiment : Experiment , spec : RolloutSpec ) -> None :
578634 if experiment .status == ExperimentStatus .COMPLETED :
579635 raise ValidationError (
@@ -582,9 +638,8 @@ def apply_experiment_rollout(experiment: Experiment, spec: RolloutSpec) -> None:
582638 validate_rollout_spec (experiment , spec )
583639 with transaction .atomic ():
584640 segment = _sync_rollout_segment (experiment , spec .rollout_percentage )
585- update_flag (
586- experiment .environment ,
587- experiment .feature ,
641+ _update_rollout_in_place (
642+ experiment ,
588643 FlagChangeSet (
589644 author = spec .author ,
590645 enabled = spec .enabled ,
@@ -638,9 +693,8 @@ def enable_experiment_rollout(experiment: Experiment, author: AuthorData) -> Non
638693 return
639694
640695 value = rollout ["feature_state_value" ]
641- update_flag (
642- experiment .environment ,
643- experiment .feature ,
696+ _update_rollout_in_place (
697+ experiment ,
644698 FlagChangeSet (
645699 author = author ,
646700 enabled = True ,
0 commit comments