[AI-FSSDK] [FSSDK-12760] add localHoldouts to datafile for backward compatibility#638
Conversation
… HoldoutConfig - Trim verbose doc comments, keep only meaningful ones - Replace JIRA ticket references with generic descriptions - Remove applyFlatList, use applySections directly - Update test call sites to use section-aware constructors Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove allHoldouts property, _allHoldouts backing store, and init(allholdouts:) - No production code used allHoldouts; only test convenience - Update all test sites to use HoldoutConfig(globalHoldouts:localHoldouts:) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Accept master's removal of FeatureGateTests_LocalHoldouts.swift - Accept master's removal of tearDown in DecisionServiceTests_Holdouts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…didSet ordering - Add log warning when stripping non-nil includedRules from global holdouts - Remove redundant project.holdouts dual-write in tests - Fix double space typo in DecisionListenerTest_Holdouts - Fix test ordering where project.sendFlagDecisions didSet wiped holdoutConfig Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Looks good to me. I made clean up on top of the AI workflow:
-
Removed allHoldouts property and init(allholdouts:) — no production code used them; only tests. The sole production path (ProjectConfig.updateProjectDependentProps) already used init(globalHoldouts:localHoldouts:).
-
Removed applyFlatList() — only existed to support allHoldouts. With it gone, all holdout parsing goes through applySections() directly.
-
Cleaned up comments — removed verbose boilerplate doc comments and replaced JIRA ticket references (FSSDK-12760) with generic descriptions.
-
Added warning log when a global-section entry has includedRules set (before stripping it), making datafile misconfiguration visible.
-
Migrated all ~60 test sites from allHoldouts setter to HoldoutConfig(globalHoldouts:localHoldouts:).
-
Removed redundant project.holdouts dual-write in tests — the didSet rebuild was immediately overwritten by the explicit holdoutConfig assignment.
-
Fixed didSet ordering bugs where project.sendFlagDecisions mutations wiped the test's holdoutConfig.
-
Why it's safe: HoldoutConfig is internal (not public API), zero production callers used the removed members, datafile parsing is backward compatible, and holdout evaluation logic is unchanged.
Summary
Adds support for the new top-level
localHoldoutsdatafile section so this SDK can be served the same backward-compatible datafile shape that Python (#517) and Ruby (#400) already consume.Section membership — not
includedRules— is now the sole signal for holdout scope:holdouts→ ALL entries are global. Any strayincludedRuleson these entries is stripped at parse time (with a warning log).localHoldouts→ ALL entries are local. Entries without a non-emptyincludedRuleslist are invalid and skipped with an error log (no fallback to global application).Old datafiles that emit only
holdoutscontinue to parse unchanged.Why this is safe in production
HoldoutConfigis internal (struct, nopublicmodifier). No SDK consumer can referenceallHoldouts,init(allholdouts:), or any removed member.localHoldoutsis decoded withdecodeIfPresentdefaulting to[]. Old datafiles without the key parse identically to before; every holdout lands in the global section, matching pre-change behavior.allHoldoutsandapplyFlatListwere only used in tests; zero production callers existed. The only production init path (ProjectConfig.updateProjectDependentProps) already calledinit(globalHoldouts:localHoldouts:).DefaultDecisionServicereadsholdoutConfig.getGlobalHoldouts()andholdoutConfig.getHoldoutsForRule(ruleId:), both of which are preserved with identical semantics.includedRuleson a global-section entry, the SDK now logs a warning and strips it (rather than silently broadening scope). This makes misconfiguration visible without changing behavior.Changes
Sources/Data Model/Project.swift— decode the new top-levellocalHoldoutsarray; default to[]when absent. Include it inCodingKeysandEquatable.Sources/Data Model/HoldoutConfig.swift— section-aware mapping. Newinit(globalHoldouts:localHoldouts:). Global-section entries getincludedRulesstripped (with warning if non-nil); local-section entries withoutincludedRulesare logged and excluded. RemovedallHoldoutsproperty andinit(allholdouts:)(no production callers).Sources/Data Model/ProjectConfig.swift— constructHoldoutConfigfrom bothproject.holdoutsandproject.localHoldoutssections.Sources/Data Model/Holdout.swift— docstring onisGlobalclarifies that section membership is the source of truth.Tests
Tests/OptimizelyTests-DataModel/HoldoutConfigTests.swift— new tests for the section-aware semantics: global-sectionincludedRulesare stripped/ignored, local section withoutincludedRulesis excluded, mixed valid/invalid local entries, both-sections partition enforced, backward compat with nolocalHoldoutssection.Tests/OptimizelyTests-DataModel/ProjectTests.swift— new tests forlocalHoldoutsdecoding (missing key defaults to[]; present key decodes alongsideholdouts).Tests/OptimizelyTests-DataModel/ProjectConfigTests.swift— updated existingtestGenerateProjectConfigMapWithHoldoutsto put local holdouts in the newlocalHoldoutssection.allHoldoutssetter toHoldoutConfig(globalHoldouts:localHoldouts:).project.holdoutsdual-write pattern in tests.didSetordering issues whereproject.sendFlagDecisionsmutation wipedholdoutConfig.Reference PRs
Jira
FSSDK-12760