Skip to content

feat: Add the override store, overlay, and data system wiring - #451

Draft
kinyoklion wants to merge 2 commits into
rlamb/overrides-ruby-model-evaluatorfrom
rlamb/overrides-ruby-store-overlay
Draft

kinyoklion wants to merge 2 commits into
rlamb/overrides-ruby-model-evaluatorfrom
rlamb/overrides-ruby-store-overlay

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Summary

This PR is stacked on #450 because it uses the override marker, the evaluator marking, and the reason indicator that PR adds.

This change adds the override layer that the OVERRIDE specification describes: a runtime-mutable collection of flag and segment definitions that takes precedence over LaunchDarkly data at the store read boundary, populated by an override source through a sink. With no override source configured the SDK behaves exactly as before. The file-based source, the events changes, and the contract test service follow in later PRs.

LaunchDarkly::Interfaces::Overrides::OverrideSource and OverrideSink define how a source supplies complete snapshots to the SDK. The sink accepts data model objects or hashes in the flag and segment data model. The SDK marks the entries itself.

Impl::Overrides::Layer holds marked entries in an immutable hash that is swapped on update, so the layer holds exactly one snapshot at any instant and reads are lock-free. Impl::Overrides::Overlay sits at the store read boundary: a read for a key returns the override entry when one exists and the LaunchDarkly entry otherwise, an enumeration is the union with the override entry winning, and initialization status is the base store's alone. Impl::Overrides::Sink applies snapshots and notifies flag change listeners of every flag whose merged-view evaluation may have changed, including flags that depend on an overridden prerequisite or segment. It computes dependency edges over both the old and the new merged view, as the Go reference does.

DataSystem::ConfigBuilder#overrides accepts one override source builder, the same build(sdk_key, config) protocol as the data source builders. The FDv2 data system builds the source at construction, so an invalid configuration raises from LDClient.new like other invalid component configuration. It starts the source before its run loop, so the initial load is in effect before the constructor returns. It serves reads through the overlay and stops the source when the client closes. An offline client builds no source.

The client consults the override store before the not-initialized short-circuit. An overridden flag is served before LaunchDarkly data arrives, and a flag that is not overridden still returns the client-not-ready default. all_flags_state reads through the overlay and, before initialization, returns only the overridden flags, with a once-per-client warning. A wrong-type migration result keeps the marking of the evaluation it replaces.

A value-only override expands, through the file loading code from #449, into a flag that is on and serves its single value by fallthrough, which reports the FALLTHROUGH reason kind as the specification describes.

The OVERRIDE specification's test vectors (spec/fixtures/override-vectors/vectors.json) run as a spec through the full client stack, checking value, variation index, and reason under the vectors' comparison rules. The vectors' per-evaluation summary marker is asserted once the event processor carries the marking, in the events PR.

One existing defect found while writing the sink is left as is and noted here: Impl::DataStore::Store seeds its dependency fan-out with symbol keys while Impl::DependencyTracker indexes dependents under the string keys that prerequisite and segment references use, so a LaunchDarkly data change through the FDv2 store never notifies dependent flags. The sink normalizes its own keys and is not affected.

Verification: specs for the layer (marking, replacement, snapshot atomicity under concurrent writers), the overlay (precedence, union, deleted base items, base failure, initialization), the sink (added, removed, changed, and unchanged entries, dependency fan-out through prerequisites and nested segments, both merged views, no-listener path, base failure, serialized updates), the FDv2 wiring (construction, start order, overlay reads, stop, offline, availability unaffected), the client (not-ready gate, all-flags state, change notifications through the flag tracker, migration marker, lifecycle), and the specification vectors. Full suite and RuboCop are clean. Each new spec was checked against a deliberate defect in the code it covers.

The existing FDv1 and FDv2 file data sources keep their current behavior. This series does not change them; the override feature is additive.

SDK-3249


Note

Overview
Adds experimental flag overrides: operators can configure an OverrideSource via DataSystem::ConfigBuilder#overrides so per-key flag/segment definitions take precedence at evaluation time without affecting initialization or data-source status.

FDv2 builds the source at construction (invalid config fails at LDClient.new), starts it before the main loop, and exposes reads through a Layer + Overlay on the store boundary. Sink applies full snapshots and fans out flag-change notifications using merged-view dependency tracking.

LDClient skips the usual not-initialized short-circuit when overrides are configured: overridden keys evaluate (and all_flags_state can return override-only flags) before LaunchDarkly data is available; non-overridden keys still get client-not-ready behavior. Includes OVERRIDE spec vectors and broad unit coverage.

Reviewed by Cursor Bugbot for commit 54278dc. Bugbot is set up for automated code reviews on this repo. Configure here.

@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-model-evaluator branch from 0f89736 to 4810ac9 Compare September 25, 2026 22:51
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-store-overlay branch from e2df553 to e4d05bb Compare September 25, 2026 22:51
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-model-evaluator branch from 4810ac9 to 20eea70 Compare September 28, 2026 20:36
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-store-overlay branch from e4d05bb to 09915cc Compare September 28, 2026 20:36
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-model-evaluator branch from 20eea70 to 8072ee4 Compare October 1, 2026 23:43
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-store-overlay branch from 09915cc to 4f4c242 Compare October 1, 2026 23:43
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-model-evaluator branch from 8072ee4 to ed8fccf Compare October 3, 2026 00:46
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-store-overlay branch from 4f4c242 to 54278dc Compare October 3, 2026 00:46
@kinyoklion

Copy link
Copy Markdown
Member Author

bugbot review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 54278dc. Configure here.

@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-store-overlay branch from 54278dc to 0dee958 Compare October 3, 2026 01:54
Adds the override layer that the OVERRIDE specification describes: a
runtime-mutable collection of flag and segment definitions that takes
precedence over LaunchDarkly data at the store read boundary.

- `LaunchDarkly::Interfaces::Overrides::OverrideSource` and `OverrideSink`
  define how a source supplies complete snapshots to the SDK.
- `Impl::Overrides::Layer` holds marked entries in an immutable hash that is
  swapped on update, so the layer holds exactly one snapshot at any instant.
- `Impl::Overrides::Overlay` sits at the store read boundary. A read for a
  key returns the override entry when one exists and the LaunchDarkly entry
  otherwise. An enumeration is the union with the override entry winning.
  Initialization status is the base store's alone.
- `Impl::Overrides::Sink` applies snapshots and notifies flag change
  listeners of every flag whose merged-view evaluation may have changed,
  including flags that depend on an overridden prerequisite or segment.
- `DataSystem::ConfigBuilder#overrides` accepts one override source builder.
  The FDv2 data system builds it at construction, so an invalid
  configuration raises from `LDClient.new`, starts it before its run loop
  so the initial load is in effect before the constructor returns, serves
  reads through the overlay, and stops it when the client closes. Offline
  clients build no source.
- The client consults the override store before the not-initialized
  short-circuit: an overridden flag is served before LaunchDarkly data
  arrives, and a flag that is not overridden still returns the
  client-not-ready default. `all_flags_state` reads through the overlay and,
  before initialization, returns only the overridden flags. A wrong-type
  migration result keeps the marking of the evaluation it replaces.
- `FileData.make_flag_with_value` gains an off form, which the override
  source uses so that a value-only override reports the OFF reason kind.
  The file data sources keep their existing form.

The OVERRIDE specification's test vectors run as a spec through the full
client stack. The per-evaluation summary marker in the vectors is asserted
once the event processor carries it.

Flag overrides are currently experimental and subject to change.
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-model-evaluator branch from bb04f10 to 6ad2419 Compare October 8, 2026 16:54
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-ruby-store-overlay branch from 0dee958 to 18341e8 Compare October 8, 2026 16:54

This branch has not been deployed

No deployments
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