Skip to content

feat: Add the override layer, the store overlay, and the override source configuration - #222

Draft
kinyoklion wants to merge 6 commits into
rlamb/overrides-java-model-markerfrom
rlamb/overrides-java-override-layer
Draft

kinyoklion wants to merge 6 commits into
rlamb/overrides-java-model-markerfrom
rlamb/overrides-java-override-layer

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Summary

This adds the override layer that the OVERRIDE specification describes: an override source supplies complete snapshots of flag and segment definitions to an override store, and an overlay at the store read boundary returns the store's entry for a key in preference to LaunchDarkly data. Evaluation, prerequisite and segment resolution, and the all-flags state all read through the overlay, so an override is a full definition that evaluates like any other. Overrides do not take part in the data system: they have no effect on initialization status, data availability, or data source status, and they are never written to a persistent store.

Public surface, all marked experimental and subject to change:

  • DataSystemBuilder.overrides(ComponentConfigurer<OverrideSource>) and DataSystemConfiguration.getOverrideSource(). The option lives on the FDv2 data system builder only. An FDv1 data source configuration has no place to supply one.
  • subsystems.OverrideSource (start with a sink, close) and subsystems.OverrideSink (replace the whole layer with one snapshot). The SDK builds the source like any other component, starts it before the data source so its initial load completes during client construction, and closes it with the client; the sink stops accepting snapshots before the source is closed, and an exception from the source's close is logged while the rest of the shutdown proceeds. An offline client does not build or start the override source, and logs a warning that it does not. A source that cannot be built fails client construction the same way other invalid component configuration does. An exception from the source's start does not: the SDK logs it, closes the source, clears anything the source supplied before failing, and the client runs without overrides.

Internals:

  • OverrideLayer holds marked shallow copies of the supplied entities in an immutable map that is swapped on each update. The source's objects are never modified.
  • OverrideOverlayStore implements the read boundary: per-key reads prefer the layer, enumeration is the union with override precedence, and a failure of the base store propagates, as it does without the overlay.
  • OverrideSinkImpl serializes snapshot application and fires the normal flag change notifications for every flag whose merged-view evaluation may have changed, using the dependency tracker over both the old and the new merged views so prerequisite and segment dependents are included.
  • The not-initialized short-circuit consults the layer first: a flag that the layer holds is served before the client has LaunchDarkly data, and any other flag returns the client-not-ready default as before. Before the store is initialized, the all-flags state contains only the flags that the layer holds, read from the layer rather than the merged store so that LaunchDarkly data in a store without an init marker stays out, and it logs once that it returned only override entries; with no override flags the state is invalid, as it is without a source. The all-flags state presents an override-affected flag with trackEvents and trackReason false and no debugEventsUntilDate.

The OVERRIDE specification's test vectors run as a unit test through the full client stack (value, variation index, and reason). The per-evaluation summary marker in the vectors is asserted by the events change that follows.

This PR depends on the model and evaluator marking change (rlamb/overrides-java-model-marker) and is based on that branch; retarget it to feat/overrides once that branch merges.

The existing file data source keeps its current behavior; this change does not touch its code paths.

SDK-3246

Follow-ups 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 DataSourceUpdatesImpl expands 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.
  • isFlagKnown consults the override layer before the not-initialized short circuit, the same as evaluation does, so an override-only flag is known while the client is uninitialized.

Note

Overview
Adds an experimental override layer for the FDv2 data system: an OverrideSource pushes full snapshots of flag/segment definitions into an internal layer, and OverrideOverlayStore serves those entries ahead of LaunchDarkly data at the store read boundary. Configuration is via DataSystemBuilder.overrides() and public OverrideSource / OverrideSink APIs; FDv1 returns no override layer.

Evaluation and client behavior change when overrides are configured: the not-initialized short-circuit and isFlagKnown consult the layer first; allFlagsState before init can return only override flags (without leaking LD data from an uninitialized store); override-affected evaluations disable per-flag event tracking in FeatureFlagsState. Overrides do not affect initialization or data-source status; offline mode skips building/starting the source.

FDv2 wiring starts the override source before the main data source, degrades gracefully on start/close failures, and OverrideSinkImpl broadcasts flag-change events from merged-view dependency diffs. DataSourceUpdatesImpl now fans out LaunchDarkly updates through both the normal dependency tracker and the layer’s ExternalDependencies, so LD segment/prerequisite changes also notify overridden dependents.

Includes broad unit/integration tests and OVERRIDE spec test vectors.

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

Opened from termag

@kinyoklion
kinyoklion force-pushed the rlamb/overrides-java-model-marker branch from 39b0b95 to d82beda Compare September 28, 2026 21:03
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-java-override-layer branch from 3ddbca0 to 51b497a Compare September 28, 2026 21:04
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-java-model-marker branch from d82beda to 1ba722a Compare October 1, 2026 23:43
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-java-override-layer branch from 51b497a to 3e12570 Compare October 1, 2026 23:44
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-java-model-marker branch from 1ba722a to 996b8d3 Compare October 3, 2026 00:58
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-java-override-layer branch from 3e12570 to 2d12762 Compare October 3, 2026 00:58
@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.

Stale Bugbot comment from a previous run.

Comment thread lib/sdk/server/src/main/java/com/launchdarkly/sdk/server/FDv2DataSystem.java Outdated
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-java-model-marker branch from 996b8d3 to ca22bc0 Compare October 13, 2026 21:02
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-java-override-layer branch from e58a4f7 to ee0d2be Compare October 13, 2026 21:02
@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 5a27c9f. Configure here.

…rce configuration

The OVERRIDE specification defines an override layer: a runtime-mutable
collection of flag and segment definitions, supplied by an override
source as complete snapshots, that takes precedence over LaunchDarkly
data on a per-key basis at the store read boundary. Overrides are not a
data source. They have no effect on initialization status, data
availability, or data source status, and they are never persisted.

Public surface, all experimental and subject to change:
DataSystemBuilder.overrides(ComponentConfigurer<OverrideSource>),
DataSystemConfiguration.getOverrideSource(), and the
subsystems.OverrideSource and subsystems.OverrideSink interfaces. The
SDK builds the source like any other component, starts it before the
data source so its initial load completes during client construction,
and closes it with the client. An offline client starts no override
source. A source that cannot be built fails client construction.

OverrideLayer holds marked shallow copies in an immutable map swapped on
each update. OverrideOverlayStore implements the read boundary with
override precedence for per-key reads and enumeration, and serves the
layer alone when the base store fails. OverrideSinkImpl serializes
snapshot application and fires the normal flag change notifications for
every flag whose merged-view evaluation may have changed.

The not-initialized short-circuit consults the layer first, so a flag
that the layer holds is served before the client has LaunchDarkly data.
The all-flags state does the same and presents an override-affected
flag with event tracking off. The specification's test vectors run as a
unit test through the full client stack.
…n changes, and let isFlagKnown see overrides

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.

isFlagKnown consults the override layer before the not-initialized short circuit, the same as
evaluation does, so a caller that gates on it does not skip an override during an outage.
…iling source from stopping the client

allFlagsState returns a state of overrides alone only when the store is
not initialized and the override layer holds flags, and it reads those
flags from the layer itself. Before, any layer contents were enough: a
layer of segments only over a store that held LaunchDarkly data without
an init marker produced a valid state of that data, and with override
flags 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 a client whose store fails reports an invalid state and logs
the failure, as it does without a source.

An exception from the source's start no longer fails client
construction. The SDK logs it, closes the source, clears what the source
supplied before failing, and the client runs without overrides. Closing
the client stops the sink from accepting snapshots before it closes the
source, so a snapshot supplied during shutdown is ignored; an exception
from the source's close is still logged rather than thrown.

An offline client still does not build or start the override source,
and now logs a warning that it does not.
@kinyoklion
kinyoklion force-pushed the rlamb/overrides-java-override-layer branch from 5a27c9f to b5020db Compare October 28, 2026 22:19
@kinyoklion

Copy link
Copy Markdown
Member Author

bugbot review

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