Repository navigation
feat: Add the override store, overlay, and data system wiring - #451
kinyoklion wants to merge 3 commits into
Conversation
0f89736 to
4810ac9
Compare
e2df553 to
e4d05bb
Compare
4810ac9 to
20eea70
Compare
e4d05bb to
09915cc
Compare
20eea70 to
8072ee4
Compare
09915cc to
4f4c242
Compare
8072ee4 to
ed8fccf
Compare
4f4c242 to
54278dc
Compare
|
bugbot review |
54278dc to
0dee958
Compare
bb04f10 to
6ad2419
Compare
0dee958 to
18341e8
Compare
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.
6ad2419 to
7579ddb
Compare
18341e8 to
45c6daa
Compare
… changes The override layer keeps a dependency graph of its entries in each snapshot. The store expands every changed LaunchDarkly item through both its own graph and the layer's, to a fixpoint, so a change to a prerequisite flag or a segment that an override entry references notifies that entry's flag as well. This matches how dependent flags behave for LaunchDarkly data. The store's own dependency graph now keys items by string, the form in which the data model names prerequisites and segments. Changes keyed by symbol, which is what the change set carries, previously reached no dependents at all.
|
bugbot review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 54fd470. Configure here.
| end | ||
| return base_items if overrides.empty? | ||
|
|
||
| base_items.merge(overrides) |
There was a problem hiding this comment.
Overrides drop client-side flags
Medium Severity
Overlay#all replaces the LaunchDarkly flag with the override entry, and all_flags_state then filters on clientSide. Value-only overrides omit that field, so client_side_only: true drops flags that were client-side in LaunchDarkly data. Front-end bootstrap would omit the overridden flag instead of serving the forced value.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 54fd470. Configure here.


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::OverrideSourceandOverrideSinkdefine 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::Layerholds 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::Overlaysits 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::Sinkapplies 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#overridesaccepts one override source builder, the samebuild(sdk_key, config)protocol as the data source builders. The FDv2 data system builds the source at construction, so an invalid configuration raises fromLDClient.newlike 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_statereads 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.LaunchDarkly changes reach override dependents.
Layerkeeps a dependency graph of its entries in each snapshot, andStoreexpands every changed LaunchDarkly item through both its own graph and the layer's, to a fixpoint. A change to a prerequisite flag or a segment that an override entry references therefore notifies that entry's flag, the same way dependent flags are notified for LaunchDarkly data, and a chain may cross between LaunchDarkly data and overrides any number of times.This also fixes an existing defect in
Impl::DataStore::Store: it seeded its dependency fan-out with symbol keys whileImpl::DependencyTrackerindexes dependents under the string keys that prerequisite and segment references use, so a LaunchDarkly data change through the FDv2 store never notified dependent flags. The store now keys its graph by string, which the fixpoint needs in any case, and a spec covers symbol-keyed changes.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 plug in an
OverrideSourceviaDataSystem::ConfigBuilder#overridesso per-key flag/segment definitions win at read time without affecting initialization or data-source status.FDv2 builds the source at client construction, starts it before the main loop (initial snapshot visible immediately), exposes reads through an overlay (
Layer+Overlay+Sink), and stops the source on shutdown. LDClient checks overrides before the not-initialized path so overridden flags evaluate (andall_flags_statecan return override-only flags) while non-overridden keys still getCLIENT_NOT_READY.Change notifications fan out across LaunchDarkly data and the override layer: the store links an external dependency graph and fixes dependency tracking to use string keys (fixing missed dependent-flag notifications on symbol-keyed updates). OVERRIDE spec vectors and broad unit coverage exercise precedence, uninitialized behavior, and dependency fan-out.
Reviewed by Cursor Bugbot for commit 54fd470. Bugbot is set up for automated code reviews on this repo. Configure here.