Skip to content

[AI-FSSDK] [FSSDK-12760] add localHoldouts to datafile for backward compatibility#412

Open
jaeopt wants to merge 1 commit into
masterfrom
ai/jaeopt/FSSDK-12760-local-datafile
Open

[AI-FSSDK] [FSSDK-12760] add localHoldouts to datafile for backward compatibility#412
jaeopt wants to merge 1 commit into
masterfrom
ai/jaeopt/FSSDK-12760-local-datafile

Conversation

@jaeopt

@jaeopt jaeopt commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Emit local holdouts under a new top-level localHoldouts datafile section, separate from the existing holdouts section. The holdouts section now carries ONLY global holdouts; older Gen 1/Gen 2 SDKs ignore the unknown localHoldouts key, so a single datafile artifact serves every SDK version without misapplying local holdouts as global.

Section membership is the sole signal for scope:

  • holdouts → ALL entries are global. Any includedRules on entries here is stripped at parse time and ignored.
  • localHoldouts → ALL entries are local (rule-scoped via includedRules). Entries missing includedRules are logged and excluded from evaluation.

Changes

  • OptimizelySDK/ProjectConfig.cs: Added Holdout[] LocalHoldouts { get; set; } to the interface and updated the Holdouts docstring to note section-based scoping.
  • OptimizelySDK/Config/DatafileProjectConfig.cs:
    • Added [JsonProperty("localHoldouts")] public Holdout[] LocalHoldouts { get; set; } deserialized from the new datafile key.
    • Initialize LocalHoldouts to an empty array when absent (backward-compat).
    • Variation maps now include both Holdouts and LocalHoldouts so the decision service can resolve local holdouts' variations.
    • New BuildCombinedHoldouts() helper: strips IncludedRules from global-section entries (section membership is the sole signal), validates localHoldouts entries (missing IncludedRules → error log + skip, no fallback to global), and passes a combined, validated array to HoldoutConfig.
    • Updated GetGlobalHoldouts() / GetHoldoutsForRule() docstrings to reflect section-based contract.
  • OptimizelySDK/Entity/Holdout.cs: Updated IncludedRules and IsGlobal docstrings to note that section membership (not this field alone) is the source of truth; the property remains consistent because parsing strips the field on global-section entries.
  • OptimizelySDK.Tests/TestData/HoldoutTestData.json: Added a datafileWithLocalHoldoutsSection fixture with both global and local entries, an entry with stray includedRules in the global section (to verify stripping), and an invalid local entry missing includedRules (to verify error log + exclusion).
  • OptimizelySDK.Tests/LocalHoldoutsSectionTest.cs (new): Dedicated [TestFixture] covering: top-level LocalHoldouts exposure, rule-map registration, exclusion from globals, id-map retrieval across sections, variation-map population for local holdouts, stripping of stray includedRules on global entries, validation/logging of missing includedRules, backward compatibility with datafiles lacking the section, datafiles missing both sections, mixed section partitioning, and the valid-but-empty includedRules edge case.
  • OptimizelySDK.Tests/OptimizelySDK.Tests.csproj: Registered the new test file (legacy MSBuild project requires explicit <Compile Include>).

Backward Compatibility

  • Datafiles without a localHoldouts key parse exactly as before — LocalHoldouts defaults to [], every entry in holdouts is treated as global, no errors, no log noise.
  • Entity-level Holdout.IsGlobal remains correct because DatafileProjectConfig.BuildCombinedHoldouts() strips IncludedRules from global-section entries at parse time.
  • Per-rule decision ordering (Forced Decision → Local Holdout → Regular Rule) is unchanged; existing enforcement test TestForcedDecisionBeats100PercentLocalHoldout still applies.

Reference Implementations

Same backward-compat shape applied across SDKs:

Jira Ticket

FSSDK-12760

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.

1 participant