Repository navigation
feat: Add the override store, overlay, source interfaces, and data system wiring - #364
Conversation
aff3a67 to
ae86267
Compare
49ba8e2 to
d9e0211
Compare
ae86267 to
533647f
Compare
d9e0211 to
092ed5c
Compare
533647f to
511de7f
Compare
092ed5c to
716b005
Compare
|
bugbot review |
511de7f to
33e2973
Compare
716b005 to
041e7a5
Compare
33e2973 to
22c3c01
Compare
041e7a5 to
91ecadb
Compare
22c3c01 to
426dbe4
Compare
91ecadb to
5d664c1
Compare
426dbe4 to
167a54a
Compare
5d664c1 to
c875758
Compare
167a54a to
6894b3d
Compare
c875758 to
8ad94ca
Compare
6894b3d to
f0fca10
Compare
8ad94ca to
b47e5ff
Compare
f0fca10 to
f083d90
Compare
b47e5ff to
0864b85
Compare
|
bugbot review |
f083d90 to
c3adb5b
Compare
625311a to
0d26639
Compare
c3adb5b to
ff43cdf
Compare
0d26639 to
c1ce57c
Compare
ff43cdf to
bcbfead
Compare
c1ce57c to
c93aba9
Compare
…stem wiring Adds IOverrideSource and IOverrideSink, the DataSystemBuilder.Overrides option, and the override layer: an immutable store of marked entries, an overlay at the store read boundary that returns the override entry for a key in preference to LaunchDarkly data, and a sink that applies snapshots one at a time and sends flag change notifications for the flags an update may have changed, with dependency fan-out through prerequisites and segments. The FDv2 data system builds the configured source, starts it before the data sources so its initial load completes inside the client constructor, and disposes it with the client. The client consults the override store before its not-initialized short-circuit, so an overridden flag is served before LaunchDarkly data arrives. AllFlagsState reads through the overlay and presents override-affected flags with event tracking off. The OVERRIDE specification's test vectors run as a unit test.
A definition whose dependencies cannot be computed, such as a prerequisite without a key or a segment match on a value that is not a string, is logged and contributes no dependency edges, so every directly changed key is still notified. A failure to determine or deliver the notifications is logged instead of reaching the override source.
…uit and dispose on a failed start Before the client is initialized, an evaluation proceeds only when the override layer holds the flag; otherwise it takes the same path as without an override source. The exception paths of evaluation and of the all-flags state keep the override indicator when the definition came from the layer. An override source whose Build or Start throws fails the client constructor after the components created so far are disposed.
…n changes A flag override can reference LaunchDarkly data: a rule that matches a LaunchDarkly segment, or a prerequisite that is a LaunchDarkly flag. DataSourceUpdatesImpl computes flag change notifications from a dependency tracker built from LaunchDarkly data alone, so when such a segment or flag changed, the flags whose overrides depend on it were not notified. The override layer now keeps the dependency edges of its own entries, rebuilt on every replacement, and the data source updates ask it for the override entries that depend on a changed item. The chain can cross in both directions, so each newly reached item is expanded through both sets of edges until nothing new is found. An entry whose references cannot be read contributes no edges, the same as the change computation skips it.
An exception from the override source's Start no longer fails the client constructor. The data system logs it, disposes the source, and the client runs as it does without a source: an application in an incident is better served by a client that runs without its overrides than by one that does not start. The SDK's own file-based source never throws from Start, since it logs a failed load and retries; a custom source could. The interface documents the behavior.
A source can supply a snapshot through the sink and then throw from Start. The data system now closes the sink after disposing the failed source: the sink clears the layer, notifying the flags that were briefly overridden, and ignores a late snapshot from the disposed source. The client then runs without overrides, as the log says, and its all-flags state agrees.
4c1e37c to
87c85e3
Compare
…source down safely AllFlagsState returns a state of overrides alone only when the client is not initialized, the store is not initialized, and the override layer holds flags, and it reads those flags from the layer itself. Before, a configured source was enough: an empty layer over a store that held LaunchDarkly data without an init marker produced a valid state of that data, and with overrides present the same data entered the state. The overlay no longer serves the layer's entries alone when the base store fails to list its items, so an initialized client whose store fails reports an invalid state and logs the failure, as it does without a source. Disposing the client closes the override sink before it disposes the source, so a snapshot supplied during shutdown is ignored, and an exception from the source's Dispose is logged instead of stopping the rest of the shutdown. MigrationVariation keeps the override-affected marking when it replaces the reason of an unrecognized stage with WRONG_TYPE.
|
bugbot review |
|
On the Agent comment: written by Claude Code (an AI coding agent) on behalf of @kinyoklion. |
An offline client does not build or start the override source, so its overrides are not served and a configuration error in it is not reported. The SDK now logs a warning when that happens, and the Overrides configuration method documents it.
|
bugbot review |
A source logs and retries a failed load, and reports a configuration error from Build. If Start throws anyway, the SDK logs it, disposes the source, removes its snapshot, and the client runs without overrides.
|
bugbot review |
… store fails When the store cannot list its flags, the state without overrides is invalid and empty. Overrides only add to it, so the state now holds the override layer's flags and stays invalid, and the failure is still logged. A client-side SDK bootstrapped from the state uses its flags whatever its validity says, so the overridden values reach it. With no override flags the state is invalid and empty, as without a source.
|
bugbot review |
There was a problem hiding this comment.
✅ 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 58ef4ac. Configure here.
…ha.1 (#382) 🤖 I have created a release *beep* *boop* --- ## [9.0.0-alpha.1](LaunchDarkly.ServerSdk-v9.0.0-alpha.0...LaunchDarkly.ServerSdk-v9.0.0-alpha.1) (2026-10-29) ### Features * Add a shared file data reloader, poller, and watcher ([#361](#361)) ([4eed241](4eed241)) * Add the file-based override source ([#367](#367)) ([b834131](b834131)) * Add the override marker to the flag and segment models and mark evaluations ([#363](#363)) ([21f666a](21f666a)) * Add the override store, overlay, source interfaces, and data system wiring ([#364](#364)) ([e1748a1](e1748a1)) * Mark evaluation events as override-affected ([#366](#366)) ([7c873a0](7c873a0)) * Split summary counters by overrideAffected and suppress individual events for marked evaluations ([#365](#365)) ([c9b327b](c9b327b)) * Support the flag-overrides capability in the contract test service ([#368](#368)) ([b80d6aa](b80d6aa)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Overview** > This is a **Release Please** cut that publishes **LaunchDarkly.ServerSdk `9.0.0-alpha.1`**. The diff only updates release metadata: `.release-please-manifest.json`, `LaunchDarkly.ServerSdk.csproj` `<Version>`, `PROVENANCE.md` verification example, and a new **`9.0.0-alpha.1`** section in `CHANGELOG.md`. > > The changelog entry documents what ships in this alpha (already merged elsewhere): **local flag overrides** via override store/overlay/source wiring and a **file-based override source** with shared file reloader/poller/watcher; **override markers** on flag/segment models and evaluations; **analytics** changes that mark override-affected evaluations, split summary counters by `overrideAffected`, and suppress individual events when appropriate; plus **contract-test** support for the flag-overrides capability. > > Reviewers can treat this as a **versioning and release-notes sanity check**—no application logic changes appear in the diff itself. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 81f0588. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
Adds the override store, the overlay at the store read boundary, the override source interfaces, and the data system wiring for flag overrides, as defined by the OVERRIDE specification. With this change an application can configure an override source on the FDv2 data system and have its flags and segments take precedence over LaunchDarkly data at evaluation time.
Configuration surface:
DataSystemBuilder.Overrides(IComponentConfigurer<IOverrideSource>)andDataSystemConfiguration.OverrideSource. At most one source is accepted.IOverrideSource(Start(IOverrideSink),IDisposable) andIOverrideSink.SetOverrides(FullDataSet<ItemDescriptor>)follow the shape of the data source interfaces, so an application can supply a custom source the same way it supplies a custom data source. The SDK builds the source when the client is created, starts it before the data sources so that its initial load completes inside the client constructor, and disposes it with the client. An exception from the source'sDisposeis logged and the rest of the client still shuts down, and a snapshot that the source supplies during shutdown is ignored. A configuration error in the source propagates from the constructor like any other invalid component configuration. An exception from the source'sStartdoes not: the SDK logs it, disposes the source, removes any snapshot the source supplied before failing, and the client runs without overrides, because a client in an incident is better off without its overrides than not starting. The SDK's own file-based source never throws fromStart; it logs a failed load and retries. The source is not built when the client is offline, so no overrides are served and a configuration error in it is not reported; the SDK logs a warning saying so. Without a configured source the SDK is unchanged.The override layer (
Internal/Overrides):OverrideLayerholds the entries as an immutable map swapped on every snapshot, marking copies of the supplied definitions so the source's own objects are never modified.OverrideOverlayimplements the data system's read-only store: a read returns the override entry when one exists and the LaunchDarkly entry otherwise, an enumeration returns the union with override entries winning (a failure of the base store propagates, as it does without the overlay), and initialization status still comes from the base store.OverrideSinkapplies snapshots one at a time and computes the flags an update may have changed, including dependency fan-out through prerequisites and segments over both the old and the new merged views, then sends the same flag change notifications a LaunchDarkly change would.Client behavior: the not-initialized short-circuit consults the override layer first, so an overridden flag is served before the client has LaunchDarkly data while a flag the layer does not hold takes exactly the path it took before this feature. An evaluation that fails with an unexpected exception keeps the marking when the flag came from the layer. A malformed override definition is logged and skipped during change computation so the other notifications still fire, a source whose Build throws leaves nothing started, and a source whose Start throws is disposed while the client runs without overrides.
AllFlagsStatereads through the overlay, so it includes override-only flags. When neither the client nor the store is initialized and the layer holds flags, it returns a state of those flags alone (with a warning logged once), read from the layer rather than the merged store so that LaunchDarkly data in a store without an init marker stays out; with no override flags the state is invalid, as it is without a source. Without a source this state is empty, because the SDK reports no flags before the store is initialized, so the override flags only add to what it reports; overrides never remove a flag from the state. When the store fails to list its flags, the failure is logged and the state holds the override layer's flags and is invalid: without a source that state is invalid and empty, and a client-side SDK bootstrapped from the state uses its flags whatever its validity says, so the overridden values still reach it. A flag whose evaluation is override-affected keeps its value, version, and marked reason in the state but hastrackEventsandtrackReasonoff and nodebugEventsUntilDate, because a consumer of the state must not send individual events for it. A wrong-type result keeps the marking on its reason, including the reasonMigrationVariationreports for an unrecognized stage.The specification's test vectors run as a unit test against the full client stack (value, variation index, and reason; the summary-counter contribution is asserted once the events change lands). Unit tests cover the layer, overlay, sink, the builder, and the client behaviors above.
It depends on the model and evaluator marking change, #363, which is merged into
dotnet-server-sdk-v9.SDK-3248
Follow-up from review
A LaunchDarkly change now notifies the flags whose overrides depend on it. The override layer keeps the dependency edges of its own entries, rebuilt on every replacement, and
DataSourceUpdatesImplexpands each changed LaunchDarkly item through both its own tracker and the layer's until nothing new is reached, so an override that matches a LaunchDarkly segment or has a LaunchDarkly prerequisite is notified like the LaunchDarkly flags that depend on it. An entry whose references cannot be read contributes no edges, the same as the change computation skips it.Note
Overview
Adds experimental flag overrides for the FDv2 data system:
DataSystemBuilder.Overrides()configures an optionalIOverrideSourcethat pushes full snapshots throughIOverrideSink, without affecting initialization or data-source status.When a source is configured, the SDK builds an override layer and wraps the read-only store with
OverrideOverlayso evaluations, prerequisites, segments, andAllFlagsStatesee override entries before LaunchDarkly data.OverrideSinkreplaces the layer atomically and notifies flag listeners using the same path as LaunchDarkly updates, including dependency fan-out viaOverrideChangesandIExternalDependenciesonDataSourceUpdatesImpl(LaunchDarkly changes can now reach overridden flags that reference them).LdClientis updated so overridden flags can be served before the client initializes;AllFlagsStatecan return override-only flags (with one-time warnings), fall back to overrides whenGetAllfails, and disable per-flag event tracking whenoverrideAffectedis set. Start/dispose failures on the source are logged and the client continues without overrides; offline mode skips starting the source.FDv1DataSystemexposes empty override APIs for parity. Extensive unit tests andoverride-vectors.jsoncover the stack.Reviewed by Cursor Bugbot for commit 58ef4ac. Bugbot is set up for automated code reviews on this repo. Configure here.
Opened from termag