Skip to content

Conversation

Copy link

Copilot AI commented Dec 5, 2025

Summary

  • Modified holdout assignment to return at most one holdout per flag (previously returned all applicable holdouts)
  • Implemented explicit holdout precedence: flags with includedFlags entries return only that holdout, ignoring global holdouts
  • Flags without explicit holdouts return first applicable global holdout (not in excludedFlags)

This prevents double bucketing and ambiguous decision logic. Previously:

# Before: multi_variate_feature got 3 holdouts
config.get_holdouts_for_flag('multi_variate_feature')
# => [specific_holdout, global_holdout, holdout_empty_1]

# After: only the explicit holdout
config.get_holdouts_for_flag('multi_variate_feature')
# => [specific_holdout]

Matches swift-sdk behavior for consistent cross-SDK experience.

Test plan

  • All 1021 existing tests pass
  • Updated 3 test cases to assert single holdout expectations
  • Manual verification confirms correct single-holdout mapping for all flag types

Issues

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • cdn.optimizely.com
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/ruby-sdk/ruby-sdk/vendor/bundle/ruby/3.2.0/bin/rspec (dns block)
  • coveralls.io
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/ruby-sdk/ruby-sdk/vendor/bundle/ruby/3.2.0/bin/rspec spec/config/datafile_project_config_spec.rb -e get_holdouts_for_flag (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/ruby-sdk/ruby-sdk/vendor/bundle/ruby/3.2.0/bin/rspec spec/config/datafile_project_config_spec.rb -e get_holdouts_for_flag by-3.2.0 --global kward credential.helpebash (dns block)
    • Triggering command: /usr/bin/ruby3.2 ruby3.2 /home/REDACTED/work/ruby-sdk/ruby-sdk/vendor/bundle/ruby/3.2.0/bin/rspec spec/decision_service_holdout_spec.rb git conf�� by-3.2.0 --global s/prism-1.6.0/in/usr/include/ruby-3.2.0/ruby/backward pull.rebase s/prism-1.6.0/exls-files (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

The current logic in DatafileProjectConfig assigns multiple holdouts—global and specific—to a single feature flag in @flag_holdouts_map. The get_holdouts_for_flag returns an array of all applicable holdouts, which can lead to ambiguity, double bucketing, and inconsistent decision logic.

Expected Fix:

  • During holdout mapping (initialization), for each flag, only assign the most specific holdout: if a holdout explicitly includes the flag via includedFlags, ONLY this holdout should be returned by get_holdouts_for_flag.
  • If no explicit holdout applies, then assign the global holdout (with empty includedFlags and not excluded).
  • Update get_holdouts_for_flag so it returns at most one relevant holdout per flag, with explicit precedence over global.
  • Update related tests to assert only a single holdout (prefer explicit) per flag.

Example:

  • If both a global and an included holdout apply to 'featureA', only the included holdout should be returned.
  • If only a global holdout applies (not excluded), return the global holdout.
  • If no holdouts apply, return an empty array.

This will match the logic of the swift-sdk and fix user bucketing bugs due to ambiguous holdout mapping.

Reference: #373

This pull request was created as a result of the following prompt from Copilot chat.

The current logic in DatafileProjectConfig assigns multiple holdouts—global and specific—to a single feature flag in @flag_holdouts_map. The get_holdouts_for_flag returns an array of all applicable holdouts, which can lead to ambiguity, double bucketing, and inconsistent decision logic.

Expected Fix:

  • During holdout mapping (initialization), for each flag, only assign the most specific holdout: if a holdout explicitly includes the flag via includedFlags, ONLY this holdout should be returned by get_holdouts_for_flag.
  • If no explicit holdout applies, then assign the global holdout (with empty includedFlags and not excluded).
  • Update get_holdouts_for_flag so it returns at most one relevant holdout per flag, with explicit precedence over global.
  • Update related tests to assert only a single holdout (prefer explicit) per flag.

Example:

  • If both a global and an included holdout apply to 'featureA', only the included holdout should be returned.
  • If only a global holdout applies (not excluded), return the global holdout.
  • If no holdouts apply, return an empty array.

This will match the logic of the swift-sdk and fix user bucketing bugs due to ambiguous holdout mapping.

Reference: #373


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 3 commits December 5, 2025 17:46
…recedence

Co-authored-by: muzahidul-opti <129880873+muzahidul-opti@users.noreply.github.com>
Co-authored-by: muzahidul-opti <129880873+muzahidul-opti@users.noreply.github.com>
Co-authored-by: muzahidul-opti <129880873+muzahidul-opti@users.noreply.github.com>
@muzahidul-opti muzahidul-opti changed the title [WIP] Fix holdout mapping to ensure single assignment per flag [FSSDK-xxxx][WIP] Fix holdout mapping to ensure single assignment per flag Dec 5, 2025
Copilot AI changed the title [FSSDK-xxxx][WIP] Fix holdout mapping to ensure single assignment per flag Fix holdout mapping to return single holdout per flag with explicit precedence Dec 5, 2025
Copilot AI requested a review from muzahidul-opti December 5, 2025 18:00
Copilot finished work on behalf of muzahidul-opti December 5, 2025 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants