Skip to content

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

Merged
jaeopt merged 6 commits into
masterfrom
ai/jaeopt/FSSDK-12760-local-datafile
Jun 16, 2026
Merged

[AI-FSSDK] [FSSDK-12760] add localHoldouts to datafile for backward compatibility#638
jaeopt merged 6 commits into
masterfrom
ai/jaeopt/FSSDK-12760-local-datafile

Conversation

@jaeopt

@jaeopt jaeopt commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds support for the new top-level localHoldouts datafile 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 stray includedRules on these entries is stripped at parse time (with a warning log).
  • localHoldouts → ALL entries are local. Entries without a non-empty includedRules list are invalid and skipped with an error log (no fallback to global application).

Old datafiles that emit only holdouts continue to parse unchanged.

Why this is safe in production

  1. No public API changesHoldoutConfig is internal (struct, no public modifier). No SDK consumer can reference allHoldouts, init(allholdouts:), or any removed member.
  2. Backward-compatible datafile parsinglocalHoldouts is decoded with decodeIfPresent defaulting to []. Old datafiles without the key parse identically to before; every holdout lands in the global section, matching pre-change behavior.
  3. No removed production code pathsallHoldouts and applyFlatList were only used in tests; zero production callers existed. The only production init path (ProjectConfig.updateProjectDependentProps) already called init(globalHoldouts:localHoldouts:).
  4. Holdout evaluation logic unchangedDefaultDecisionService reads holdoutConfig.getGlobalHoldouts() and holdoutConfig.getHoldoutsForRule(ruleId:), both of which are preserved with identical semantics.
  5. Global holdout stripping is defensive — if a backend bug places includedRules on 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-level localHoldouts array; default to [] when absent. Include it in CodingKeys and Equatable.
  • Sources/Data Model/HoldoutConfig.swift — section-aware mapping. New init(globalHoldouts:localHoldouts:). Global-section entries get includedRules stripped (with warning if non-nil); local-section entries without includedRules are logged and excluded. Removed allHoldouts property and init(allholdouts:) (no production callers).
  • Sources/Data Model/ProjectConfig.swift — construct HoldoutConfig from both project.holdouts and project.localHoldouts sections.
  • Sources/Data Model/Holdout.swift — docstring on isGlobal clarifies that section membership is the source of truth.

Tests

  • Tests/OptimizelyTests-DataModel/HoldoutConfigTests.swift — new tests for the section-aware semantics: global-section includedRules are stripped/ignored, local section without includedRules is excluded, mixed valid/invalid local entries, both-sections partition enforced, backward compat with no localHoldouts section.
  • Tests/OptimizelyTests-DataModel/ProjectTests.swift — new tests for localHoldouts decoding (missing key defaults to []; present key decodes alongside holdouts).
  • Tests/OptimizelyTests-DataModel/ProjectConfigTests.swift — updated existing testGenerateProjectConfigMapWithHoldouts to put local holdouts in the new localHoldouts section.
  • All holdout test files migrated from allHoldouts setter to HoldoutConfig(globalHoldouts:localHoldouts:).
  • Removed redundant project.holdouts dual-write pattern in tests.
  • Fixed didSet ordering issues where project.sendFlagDecisions mutation wiped holdoutConfig.

Reference PRs

Jira

FSSDK-12760

jaeopt and others added 6 commits June 12, 2026 13:17
… 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>

@muzahidul-opti muzahidul-opti left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@jaeopt jaeopt merged commit d241e44 into master Jun 16, 2026
32 of 40 checks passed
@jaeopt jaeopt deleted the ai/jaeopt/FSSDK-12760-local-datafile branch June 16, 2026 15:57
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